1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.services.cassandra;
7 cassandraPackage = cfg.package.override {
8 jre = cfg.jre;
9 };
10 cassandraUser = {
11 name = cfg.user;
12 home = "/var/lib/cassandra";
13 description = "Cassandra role user";
14 };
15
16 cassandraRackDcProperties = ''
17 dc=${cfg.dc}
18 rack=${cfg.rack}
19 '';
20
21 cassandraConf = ''
22 cluster_name: ${cfg.clusterName}
23 num_tokens: 256
24 auto_bootstrap: ${if cfg.autoBootstrap then "true" else "false"}
25 hinted_handoff_enabled: ${if cfg.hintedHandOff then "true" else "false"}
26 hinted_handoff_throttle_in_kb: ${builtins.toString cfg.hintedHandOffThrottle}
27 max_hints_delivery_threads: 2
28 max_hint_window_in_ms: 10800000 # 3 hours
29 authenticator: ${cfg.authenticator}
30 authorizer: ${cfg.authorizer}
31 permissions_validity_in_ms: 2000
32 partitioner: org.apache.cassandra.dht.Murmur3Partitioner
33 data_file_directories:
34 ${builtins.concatStringsSep "\n" (map (v: " - "+v) cfg.dataDirs)}
35 commitlog_directory: ${cfg.commitLogDirectory}
36 disk_failure_policy: stop
37 key_cache_size_in_mb:
38 key_cache_save_period: 14400
39 row_cache_size_in_mb: 0
40 row_cache_save_period: 0
41 saved_caches_directory: ${cfg.savedCachesDirectory}
42 commitlog_sync: ${cfg.commitLogSync}
43 commitlog_sync_period_in_ms: ${builtins.toString cfg.commitLogSyncPeriod}
44 commitlog_segment_size_in_mb: 32
45 seed_provider:
46 - class_name: org.apache.cassandra.locator.SimpleSeedProvider
47 parameters:
48 - seeds: "${builtins.concatStringsSep "," cfg.seeds}"
49 concurrent_reads: ${builtins.toString cfg.concurrentReads}
50 concurrent_writes: ${builtins.toString cfg.concurrentWrites}
51 memtable_flush_queue_size: 4
52 trickle_fsync: false
53 trickle_fsync_interval_in_kb: 10240
54 storage_port: 7000
55 ssl_storage_port: 7001
56 listen_address: ${cfg.listenAddress}
57 start_native_transport: true
58 native_transport_port: 9042
59 start_rpc: true
60 rpc_address: ${cfg.rpcAddress}
61 rpc_port: 9160
62 rpc_keepalive: true
63 rpc_server_type: sync
64 thrift_framed_transport_size_in_mb: 15
65 incremental_backups: ${if cfg.incrementalBackups then "true" else "false"}
66 snapshot_before_compaction: false
67 auto_snapshot: true
68 column_index_size_in_kb: 64
69 in_memory_compaction_limit_in_mb: 64
70 multithreaded_compaction: false
71 compaction_throughput_mb_per_sec: 16
72 compaction_preheat_key_cache: true
73 read_request_timeout_in_ms: 10000
74 range_request_timeout_in_ms: 10000
75 write_request_timeout_in_ms: 10000
76 cas_contention_timeout_in_ms: 1000
77 truncate_request_timeout_in_ms: 60000
78 request_timeout_in_ms: 10000
79 cross_node_timeout: false
80 endpoint_snitch: ${cfg.snitch}
81 dynamic_snitch_update_interval_in_ms: 100
82 dynamic_snitch_reset_interval_in_ms: 600000
83 dynamic_snitch_badness_threshold: 0.1
84 request_scheduler: org.apache.cassandra.scheduler.NoScheduler
85 server_encryption_options:
86 internode_encryption: ${cfg.internodeEncryption}
87 keystore: ${cfg.keyStorePath}
88 keystore_password: ${cfg.keyStorePassword}
89 truststore: ${cfg.trustStorePath}
90 truststore_password: ${cfg.trustStorePassword}
91 client_encryption_options:
92 enabled: ${if cfg.clientEncryption then "true" else "false"}
93 keystore: ${cfg.keyStorePath}
94 keystore_password: ${cfg.keyStorePassword}
95 internode_compression: all
96 inter_dc_tcp_nodelay: false
97 preheat_kernel_page_cache: false
98 streaming_socket_timeout_in_ms: ${toString cfg.streamingSocketTimoutInMS}
99 '';
100
101 cassandraLog = ''
102 log4j.rootLogger=${cfg.logLevel},stdout
103 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
104 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
105 log4j.appender.stdout.layout.ConversionPattern=%5p [%t] %d{HH:mm:ss,SSS} %m%n
106 '';
107
108 cassandraConfFile = pkgs.writeText "cassandra.yaml" cassandraConf;
109 cassandraLogFile = pkgs.writeText "log4j-server.properties" cassandraLog;
110 cassandraRackFile = pkgs.writeText "cassandra-rackdc.properties" cassandraRackDcProperties;
111
112 cassandraEnvironment = {
113 CASSANDRA_HOME = cassandraPackage;
114 JAVA_HOME = cfg.jre;
115 CASSANDRA_CONF = "/etc/cassandra";
116 };
117
118in {
119
120 ###### interface
121
122 options.services.cassandra = {
123 enable = mkOption {
124 description = "Whether to enable cassandra.";
125 default = false;
126 type = types.bool;
127 };
128 package = mkOption {
129 description = "Cassandra package to use.";
130 default = pkgs.cassandra;
131 defaultText = "pkgs.cassandra";
132 type = types.package;
133 };
134 jre = mkOption {
135 description = "JRE package to run cassandra service.";
136 default = pkgs.jre;
137 defaultText = "pkgs.jre";
138 type = types.package;
139 };
140 user = mkOption {
141 description = "User that runs cassandra service.";
142 default = "cassandra";
143 type = types.string;
144 };
145 group = mkOption {
146 description = "Group that runs cassandra service.";
147 default = "cassandra";
148 type = types.string;
149 };
150 envFile = mkOption {
151 description = "path to cassandra-env.sh";
152 default = "${cassandraPackage}/conf/cassandra-env.sh";
153 defaultText = "\${cassandraPackage}/conf/cassandra-env.sh";
154 type = types.path;
155 };
156 clusterName = mkOption {
157 description = "set cluster name";
158 default = "cassandra";
159 example = "prod-cluster0";
160 type = types.string;
161 };
162 commitLogDirectory = mkOption {
163 description = "directory for commit logs";
164 default = "/var/lib/cassandra/commit_log";
165 type = types.string;
166 };
167 savedCachesDirectory = mkOption {
168 description = "directory for saved caches";
169 default = "/var/lib/cassandra/saved_caches";
170 type = types.string;
171 };
172 hintedHandOff = mkOption {
173 description = "enable hinted handoff";
174 default = true;
175 type = types.bool;
176 };
177 hintedHandOffThrottle = mkOption {
178 description = "hinted hand off throttle rate in kb";
179 default = 1024;
180 type = types.int;
181 };
182 commitLogSync = mkOption {
183 description = "commitlog sync method";
184 default = "periodic";
185 type = types.str;
186 example = "batch";
187 };
188 commitLogSyncPeriod = mkOption {
189 description = "commitlog sync period in ms ";
190 default = 10000;
191 type = types.int;
192 };
193 envScript = mkOption {
194 default = "${cassandraPackage}/conf/cassandra-env.sh";
195 defaultText = "\${cassandraPackage}/conf/cassandra-env.sh";
196 type = types.path;
197 description = "Supply your own cassandra-env.sh rather than using the default";
198 };
199 extraParams = mkOption {
200 description = "add additional lines to cassandra-env.sh";
201 default = [];
202 example = [''JVM_OPTS="$JVM_OPTS -Dcassandra.available_processors=1"''];
203 type = types.listOf types.str;
204 };
205 dataDirs = mkOption {
206 type = types.listOf types.path;
207 default = [ "/var/lib/cassandra/data" ];
208 description = "Data directories for cassandra";
209 };
210 logLevel = mkOption {
211 type = types.str;
212 default = "INFO";
213 description = "default logging level for log4j";
214 };
215 internodeEncryption = mkOption {
216 description = "enable internode encryption";
217 default = "none";
218 example = "all";
219 type = types.str;
220 };
221 clientEncryption = mkOption {
222 description = "enable client encryption";
223 default = false;
224 type = types.bool;
225 };
226 trustStorePath = mkOption {
227 description = "path to truststore";
228 default = ".conf/truststore";
229 type = types.str;
230 };
231 keyStorePath = mkOption {
232 description = "path to keystore";
233 default = ".conf/keystore";
234 type = types.str;
235 };
236 keyStorePassword = mkOption {
237 description = "password to keystore";
238 default = "cassandra";
239 type = types.str;
240 };
241 trustStorePassword = mkOption {
242 description = "password to truststore";
243 default = "cassandra";
244 type = types.str;
245 };
246 seeds = mkOption {
247 description = "password to truststore";
248 default = [ "127.0.0.1" ];
249 type = types.listOf types.str;
250 };
251 concurrentWrites = mkOption {
252 description = "number of concurrent writes allowed";
253 default = 32;
254 type = types.int;
255 };
256 concurrentReads = mkOption {
257 description = "number of concurrent reads allowed";
258 default = 32;
259 type = types.int;
260 };
261 listenAddress = mkOption {
262 description = "listen address";
263 default = "localhost";
264 type = types.str;
265 };
266 rpcAddress = mkOption {
267 description = "rpc listener address";
268 default = "localhost";
269 type = types.str;
270 };
271 incrementalBackups = mkOption {
272 description = "enable incremental backups";
273 default = false;
274 type = types.bool;
275 };
276 snitch = mkOption {
277 description = "snitch to use for topology discovery";
278 default = "GossipingPropertyFileSnitch";
279 example = "Ec2Snitch";
280 type = types.str;
281 };
282 dc = mkOption {
283 description = "datacenter for use in topology configuration";
284 default = "DC1";
285 example = "DC1";
286 type = types.str;
287 };
288 rack = mkOption {
289 description = "rack for use in topology configuration";
290 default = "RAC1";
291 example = "RAC1";
292 type = types.str;
293 };
294 authorizer = mkOption {
295 description = "
296 Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
297 ";
298 default = "AllowAllAuthorizer";
299 example = "CassandraAuthorizer";
300 type = types.str;
301 };
302 authenticator = mkOption {
303 description = "
304 Authentication backend, implementing IAuthenticator; used to identify users
305 ";
306 default = "AllowAllAuthenticator";
307 example = "PasswordAuthenticator";
308 type = types.str;
309 };
310 autoBootstrap = mkOption {
311 description = "It makes new (non-seed) nodes automatically migrate the right data to themselves.";
312 default = true;
313 example = true;
314 type = types.bool;
315 };
316 streamingSocketTimoutInMS = mkOption {
317 description = "Enable or disable socket timeout for streaming operations";
318 default = 3600000; #CASSANDRA-8611
319 example = 120;
320 type = types.int;
321 };
322 repairStartAt = mkOption {
323 default = "Sun";
324 type = types.string;
325 description = ''
326 Defines realtime (i.e. wallclock) timers with calendar event
327 expressions. For more details re: systemd OnCalendar at
328 https://www.freedesktop.org/software/systemd/man/systemd.time.html#Displaying%20Time%20Spans
329 '';
330 example = ["weekly" "daily" "08:05:40" "mon,fri *-1/2-1,3 *:30:45"];
331 };
332 repairRandomizedDelayInSec = mkOption {
333 default = 0;
334 type = types.int;
335 description = ''Delay the timer by a randomly selected, evenly distributed
336 amount of time between 0 and the specified time value. re: systemd timer
337 RandomizedDelaySec for more details
338 '';
339 };
340 repairPostStop = mkOption {
341 default = null;
342 type = types.nullOr types.string;
343 description = ''
344 Run a script when repair is over. One can use it to send statsd events, email, etc.
345 '';
346 };
347 repairPostStart = mkOption {
348 default = null;
349 type = types.nullOr types.string;
350 description = ''
351 Run a script when repair starts. One can use it to send statsd events, email, etc.
352 It has same semantics as systemd ExecStopPost; So, if it fails, unit is consisdered
353 failed.
354 '';
355 };
356 };
357
358 ###### implementation
359
360 config = mkIf cfg.enable {
361
362 environment.etc."cassandra/cassandra-rackdc.properties" = {
363 source = cassandraRackFile;
364 };
365 environment.etc."cassandra/cassandra.yaml" = {
366 source = cassandraConfFile;
367 };
368 environment.etc."cassandra/log4j-server.properties" = {
369 source = cassandraLogFile;
370 };
371 environment.etc."cassandra/cassandra-env.sh" = {
372 text = ''
373 ${builtins.readFile cfg.envFile}
374 ${concatStringsSep "\n" cfg.extraParams}
375 '';
376 };
377 systemd.services.cassandra = {
378 description = "Cassandra Daemon";
379 wantedBy = [ "multi-user.target" ];
380 after = [ "network-interfaces.target" ];
381 environment = cassandraEnvironment;
382 restartTriggers = [ cassandraConfFile cassandraLogFile cassandraRackFile ];
383 serviceConfig = {
384
385 User = cfg.user;
386 PermissionsStartOnly = true;
387 LimitAS = "infinity";
388 LimitNOFILE = "100000";
389 LimitNPROC = "32768";
390 LimitMEMLOCK = "infinity";
391
392 };
393 script = ''
394 ${cassandraPackage}/bin/cassandra -f
395 '';
396 path = [
397 cfg.jre
398 cassandraPackage
399 pkgs.coreutils
400 ];
401 preStart = ''
402 mkdir -m 0700 -p /etc/cassandra/triggers
403 mkdir -m 0700 -p /var/lib/cassandra /var/log/cassandra
404 chown ${cfg.user} /var/lib/cassandra /var/log/cassandra /etc/cassandra/triggers
405 '';
406 postStart = ''
407 sleep 2
408 while ! nodetool status >/dev/null 2>&1; do
409 sleep 2
410 done
411 nodetool status
412 '';
413 };
414
415 environment.systemPackages = [ cassandraPackage ];
416
417 networking.firewall.allowedTCPPorts = [
418 7000
419 7001
420 9042
421 9160
422 ];
423
424 users.extraUsers.cassandra =
425 if config.ids.uids ? "cassandra"
426 then { uid = config.ids.uids.cassandra; } // cassandraUser
427 else cassandraUser ;
428
429 boot.kernel.sysctl."vm.swappiness" = pkgs.lib.mkOptionDefault 0;
430
431 systemd.timers."cassandra-repair" = {
432 timerConfig = {
433 OnCalendar = "${toString cfg.repairStartAt}";
434 RandomizedDelaySec = cfg.repairRandomizedDelayInSec;
435 };
436 };
437
438 systemd.services."cassandra-repair" = {
439 description = "Cassandra repair daemon";
440 environment = cassandraEnvironment;
441 script = "${cassandraPackage}/bin/nodetool repair -pr";
442 postStop = mkIf (cfg.repairPostStop != null) cfg.repairPostStop;
443 postStart = mkIf (cfg.repairPostStart != null) cfg.repairPostStart;
444 serviceConfig = {
445 User = cfg.user;
446 };
447 };
448 };
449}