at 22.05-pre 18 kB view raw
1{ config, lib, pkgs, ... }: 2 3let 4 inherit (lib) 5 concatStringsSep 6 flip 7 literalDocBook 8 literalExpression 9 optionalAttrs 10 optionals 11 recursiveUpdate 12 mkEnableOption 13 mkIf 14 mkOption 15 types 16 versionAtLeast 17 ; 18 19 cfg = config.services.cassandra; 20 21 defaultUser = "cassandra"; 22 23 cassandraConfig = flip recursiveUpdate cfg.extraConfig ( 24 { 25 commitlog_sync = "batch"; 26 commitlog_sync_batch_window_in_ms = 2; 27 start_native_transport = cfg.allowClients; 28 cluster_name = cfg.clusterName; 29 partitioner = "org.apache.cassandra.dht.Murmur3Partitioner"; 30 endpoint_snitch = "SimpleSnitch"; 31 data_file_directories = [ "${cfg.homeDir}/data" ]; 32 commitlog_directory = "${cfg.homeDir}/commitlog"; 33 saved_caches_directory = "${cfg.homeDir}/saved_caches"; 34 } // optionalAttrs (cfg.seedAddresses != [ ]) { 35 seed_provider = [ 36 { 37 class_name = "org.apache.cassandra.locator.SimpleSeedProvider"; 38 parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }]; 39 } 40 ]; 41 } // optionalAttrs (versionAtLeast cfg.package.version "3") { 42 hints_directory = "${cfg.homeDir}/hints"; 43 } 44 ); 45 46 cassandraConfigWithAddresses = cassandraConfig // ( 47 if cfg.listenAddress == null 48 then { listen_interface = cfg.listenInterface; } 49 else { listen_address = cfg.listenAddress; } 50 ) // ( 51 if cfg.rpcAddress == null 52 then { rpc_interface = cfg.rpcInterface; } 53 else { rpc_address = cfg.rpcAddress; } 54 ); 55 56 cassandraEtc = pkgs.stdenv.mkDerivation { 57 name = "cassandra-etc"; 58 59 cassandraYaml = builtins.toJSON cassandraConfigWithAddresses; 60 cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh"; 61 cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig; 62 63 passAsFile = [ "extraEnvSh" ]; 64 inherit (cfg) extraEnvSh; 65 66 buildCommand = '' 67 mkdir -p "$out" 68 69 echo "$cassandraYaml" > "$out/cassandra.yaml" 70 ln -s "$cassandraLogbackConfig" "$out/logback.xml" 71 72 ( cat "$cassandraEnvPkg" 73 echo "# lines from services.cassandra.extraEnvSh: " 74 cat "$extraEnvShPath" 75 ) > "$out/cassandra-env.sh" 76 77 # Delete default JMX Port, otherwise we can't set it using env variable 78 sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh" 79 80 # Delete default password file 81 sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh" 82 ''; 83 }; 84 85 defaultJmxRolesFile = 86 builtins.foldl' 87 (left: right: left + right) "" 88 (map (role: "${role.username} ${role.password}") cfg.jmxRoles); 89 90 fullJvmOptions = 91 cfg.jvmOpts 92 ++ optionals (cfg.jmxRoles != [ ]) [ 93 "-Dcom.sun.management.jmxremote.authenticate=true" 94 "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}" 95 ] ++ optionals cfg.remoteJmx [ 96 "-Djava.rmi.server.hostname=${cfg.rpcAddress}" 97 ]; 98 99in 100{ 101 options.services.cassandra = { 102 103 enable = mkEnableOption '' 104 Apache Cassandra Scalable and highly available database. 105 ''; 106 107 clusterName = mkOption { 108 type = types.str; 109 default = "Test Cluster"; 110 description = '' 111 The name of the cluster. 112 This setting prevents nodes in one logical cluster from joining 113 another. All nodes in a cluster must have the same value. 114 ''; 115 }; 116 117 user = mkOption { 118 type = types.str; 119 default = defaultUser; 120 description = "Run Apache Cassandra under this user."; 121 }; 122 123 group = mkOption { 124 type = types.str; 125 default = defaultUser; 126 description = "Run Apache Cassandra under this group."; 127 }; 128 129 homeDir = mkOption { 130 type = types.path; 131 default = "/var/lib/cassandra"; 132 description = '' 133 Home directory for Apache Cassandra. 134 ''; 135 }; 136 137 package = mkOption { 138 type = types.package; 139 default = pkgs.cassandra; 140 defaultText = literalExpression "pkgs.cassandra"; 141 example = literalExpression "pkgs.cassandra_3_11"; 142 description = '' 143 The Apache Cassandra package to use. 144 ''; 145 }; 146 147 jvmOpts = mkOption { 148 type = types.listOf types.str; 149 default = [ ]; 150 description = '' 151 Populate the JVM_OPT environment variable. 152 ''; 153 }; 154 155 listenAddress = mkOption { 156 type = types.nullOr types.str; 157 default = "127.0.0.1"; 158 example = null; 159 description = '' 160 Address or interface to bind to and tell other Cassandra nodes 161 to connect to. You _must_ change this if you want multiple 162 nodes to be able to communicate! 163 164 Set listenAddress OR listenInterface, not both. 165 166 Leaving it blank leaves it up to 167 InetAddress.getLocalHost(). This will always do the Right 168 Thing _if_ the node is properly configured (hostname, name 169 resolution, etc), and the Right Thing is to use the address 170 associated with the hostname (it might not be). 171 172 Setting listen_address to 0.0.0.0 is always wrong. 173 ''; 174 }; 175 176 listenInterface = mkOption { 177 type = types.nullOr types.str; 178 default = null; 179 example = "eth1"; 180 description = '' 181 Set listenAddress OR listenInterface, not both. Interfaces 182 must correspond to a single address, IP aliasing is not 183 supported. 184 ''; 185 }; 186 187 rpcAddress = mkOption { 188 type = types.nullOr types.str; 189 default = "127.0.0.1"; 190 example = null; 191 description = '' 192 The address or interface to bind the native transport server to. 193 194 Set rpcAddress OR rpcInterface, not both. 195 196 Leaving rpcAddress blank has the same effect as on 197 listenAddress (i.e. it will be based on the configured hostname 198 of the node). 199 200 Note that unlike listenAddress, you can specify 0.0.0.0, but you 201 must also set extraConfig.broadcast_rpc_address to a value other 202 than 0.0.0.0. 203 204 For security reasons, you should not expose this port to the 205 internet. Firewall it if needed. 206 ''; 207 }; 208 209 rpcInterface = mkOption { 210 type = types.nullOr types.str; 211 default = null; 212 example = "eth1"; 213 description = '' 214 Set rpcAddress OR rpcInterface, not both. Interfaces must 215 correspond to a single address, IP aliasing is not supported. 216 ''; 217 }; 218 219 logbackConfig = mkOption { 220 type = types.lines; 221 default = '' 222 <configuration scan="false"> 223 <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> 224 <encoder> 225 <pattern>%-5level %date{HH:mm:ss,SSS} %msg%n</pattern> 226 </encoder> 227 </appender> 228 229 <root level="INFO"> 230 <appender-ref ref="STDOUT" /> 231 </root> 232 233 <logger name="com.thinkaurelius.thrift" level="ERROR"/> 234 </configuration> 235 ''; 236 description = '' 237 XML logback configuration for cassandra 238 ''; 239 }; 240 241 seedAddresses = mkOption { 242 type = types.listOf types.str; 243 default = [ "127.0.0.1" ]; 244 description = '' 245 The addresses of hosts designated as contact points in the cluster. A 246 joining node contacts one of the nodes in the seeds list to learn the 247 topology of the ring. 248 Set to 127.0.0.1 for a single node cluster. 249 ''; 250 }; 251 252 allowClients = mkOption { 253 type = types.bool; 254 default = true; 255 description = '' 256 Enables or disables the native transport server (CQL binary protocol). 257 This server uses the same address as the <literal>rpcAddress</literal>, 258 but the port it uses is not <literal>rpc_port</literal> but 259 <literal>native_transport_port</literal>. See the official Cassandra 260 docs for more information on these variables and set them using 261 <literal>extraConfig</literal>. 262 ''; 263 }; 264 265 extraConfig = mkOption { 266 type = types.attrs; 267 default = { }; 268 example = 269 { 270 commitlog_sync_batch_window_in_ms = 3; 271 }; 272 description = '' 273 Extra options to be merged into cassandra.yaml as nix attribute set. 274 ''; 275 }; 276 277 extraEnvSh = mkOption { 278 type = types.lines; 279 default = ""; 280 example = literalExpression ''"CLASSPATH=$CLASSPATH:''${extraJar}"''; 281 description = '' 282 Extra shell lines to be appended onto cassandra-env.sh. 283 ''; 284 }; 285 286 fullRepairInterval = mkOption { 287 type = types.nullOr types.str; 288 default = "3w"; 289 example = null; 290 description = '' 291 Set the interval how often full repairs are run, i.e. 292 <literal>nodetool repair --full</literal> is executed. See 293 https://cassandra.apache.org/doc/latest/operating/repair.html 294 for more information. 295 296 Set to <literal>null</literal> to disable full repairs. 297 ''; 298 }; 299 300 fullRepairOptions = mkOption { 301 type = types.listOf types.str; 302 default = [ ]; 303 example = [ "--partitioner-range" ]; 304 description = '' 305 Options passed through to the full repair command. 306 ''; 307 }; 308 309 incrementalRepairInterval = mkOption { 310 type = types.nullOr types.str; 311 default = "3d"; 312 example = null; 313 description = '' 314 Set the interval how often incremental repairs are run, i.e. 315 <literal>nodetool repair</literal> is executed. See 316 https://cassandra.apache.org/doc/latest/operating/repair.html 317 for more information. 318 319 Set to <literal>null</literal> to disable incremental repairs. 320 ''; 321 }; 322 323 incrementalRepairOptions = mkOption { 324 type = types.listOf types.str; 325 default = [ ]; 326 example = [ "--partitioner-range" ]; 327 description = '' 328 Options passed through to the incremental repair command. 329 ''; 330 }; 331 332 maxHeapSize = mkOption { 333 type = types.nullOr types.str; 334 default = null; 335 example = "4G"; 336 description = '' 337 Must be left blank or set together with heapNewSize. 338 If left blank a sensible value for the available amount of RAM and CPU 339 cores is calculated. 340 341 Override to set the amount of memory to allocate to the JVM at 342 start-up. For production use you may wish to adjust this for your 343 environment. MAX_HEAP_SIZE is the total amount of memory dedicated 344 to the Java heap. HEAP_NEWSIZE refers to the size of the young 345 generation. 346 347 The main trade-off for the young generation is that the larger it 348 is, the longer GC pause times will be. The shorter it is, the more 349 expensive GC will be (usually). 350 ''; 351 }; 352 353 heapNewSize = mkOption { 354 type = types.nullOr types.str; 355 default = null; 356 example = "800M"; 357 description = '' 358 Must be left blank or set together with heapNewSize. 359 If left blank a sensible value for the available amount of RAM and CPU 360 cores is calculated. 361 362 Override to set the amount of memory to allocate to the JVM at 363 start-up. For production use you may wish to adjust this for your 364 environment. HEAP_NEWSIZE refers to the size of the young 365 generation. 366 367 The main trade-off for the young generation is that the larger it 368 is, the longer GC pause times will be. The shorter it is, the more 369 expensive GC will be (usually). 370 371 The example HEAP_NEWSIZE assumes a modern 8-core+ machine for decent pause 372 times. If in doubt, and if you do not particularly want to tweak, go with 373 100 MB per physical CPU core. 374 ''; 375 }; 376 377 mallocArenaMax = mkOption { 378 type = types.nullOr types.int; 379 default = null; 380 example = 4; 381 description = '' 382 Set this to control the amount of arenas per-thread in glibc. 383 ''; 384 }; 385 386 remoteJmx = mkOption { 387 type = types.bool; 388 default = false; 389 description = '' 390 Cassandra ships with JMX accessible *only* from localhost. 391 To enable remote JMX connections set to true. 392 393 Be sure to also enable authentication and/or TLS. 394 See: https://wiki.apache.org/cassandra/JmxSecurity 395 ''; 396 }; 397 398 jmxPort = mkOption { 399 type = types.int; 400 default = 7199; 401 description = '' 402 Specifies the default port over which Cassandra will be available for 403 JMX connections. 404 For security reasons, you should not expose this port to the internet. 405 Firewall it if needed. 406 ''; 407 }; 408 409 jmxRoles = mkOption { 410 default = [ ]; 411 description = '' 412 Roles that are allowed to access the JMX (e.g. nodetool) 413 BEWARE: The passwords will be stored world readable in the nix-store. 414 It's recommended to use your own protected file using 415 <literal>jmxRolesFile</literal> 416 417 Doesn't work in versions older than 3.11 because they don't like that 418 it's world readable. 419 ''; 420 type = types.listOf (types.submodule { 421 options = { 422 username = mkOption { 423 type = types.str; 424 description = "Username for JMX"; 425 }; 426 password = mkOption { 427 type = types.str; 428 description = "Password for JMX"; 429 }; 430 }; 431 }); 432 }; 433 434 jmxRolesFile = mkOption { 435 type = types.nullOr types.path; 436 default = 437 if versionAtLeast cfg.package.version "3.11" 438 then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile 439 else null; 440 defaultText = literalDocBook ''generated configuration file if version is at least 3.11, otherwise <literal>null</literal>''; 441 example = "/var/lib/cassandra/jmx.password"; 442 description = '' 443 Specify your own jmx roles file. 444 445 Make sure the permissions forbid "others" from reading the file if 446 you're using Cassandra below version 3.11. 447 ''; 448 }; 449 }; 450 451 config = mkIf cfg.enable { 452 assertions = [ 453 { 454 assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null); 455 message = "You have to set either listenAddress or listenInterface"; 456 } 457 { 458 assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null); 459 message = "You have to set either rpcAddress or rpcInterface"; 460 } 461 { 462 assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null); 463 message = "If you set either of maxHeapSize or heapNewSize you have to set both"; 464 } 465 { 466 assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null; 467 message = '' 468 If you want JMX available remotely you need to set a password using 469 <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if 470 using Cassandra older than v3.11. 471 ''; 472 } 473 ]; 474 users = mkIf (cfg.user == defaultUser) { 475 users.${defaultUser} = { 476 group = cfg.group; 477 home = cfg.homeDir; 478 createHome = true; 479 uid = config.ids.uids.cassandra; 480 description = "Cassandra service user"; 481 }; 482 groups.${defaultUser}.gid = config.ids.gids.cassandra; 483 }; 484 485 systemd.services.cassandra = { 486 description = "Apache Cassandra service"; 487 after = [ "network.target" ]; 488 environment = { 489 CASSANDRA_CONF = "${cassandraEtc}"; 490 JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions; 491 MAX_HEAP_SIZE = toString cfg.maxHeapSize; 492 HEAP_NEWSIZE = toString cfg.heapNewSize; 493 MALLOC_ARENA_MAX = toString cfg.mallocArenaMax; 494 LOCAL_JMX = if cfg.remoteJmx then "no" else "yes"; 495 JMX_PORT = toString cfg.jmxPort; 496 }; 497 wantedBy = [ "multi-user.target" ]; 498 serviceConfig = { 499 User = cfg.user; 500 Group = cfg.group; 501 ExecStart = "${cfg.package}/bin/cassandra -f"; 502 SuccessExitStatus = 143; 503 }; 504 }; 505 506 systemd.services.cassandra-full-repair = { 507 description = "Perform a full repair on this Cassandra node"; 508 after = [ "cassandra.service" ]; 509 requires = [ "cassandra.service" ]; 510 serviceConfig = { 511 User = cfg.user; 512 Group = cfg.group; 513 ExecStart = 514 concatStringsSep " " 515 ([ 516 "${cfg.package}/bin/nodetool" 517 "repair" 518 "--full" 519 ] ++ cfg.fullRepairOptions); 520 }; 521 }; 522 523 systemd.timers.cassandra-full-repair = 524 mkIf (cfg.fullRepairInterval != null) { 525 description = "Schedule full repairs on Cassandra"; 526 wantedBy = [ "timers.target" ]; 527 timerConfig = { 528 OnBootSec = cfg.fullRepairInterval; 529 OnUnitActiveSec = cfg.fullRepairInterval; 530 Persistent = true; 531 }; 532 }; 533 534 systemd.services.cassandra-incremental-repair = { 535 description = "Perform an incremental repair on this cassandra node."; 536 after = [ "cassandra.service" ]; 537 requires = [ "cassandra.service" ]; 538 serviceConfig = { 539 User = cfg.user; 540 Group = cfg.group; 541 ExecStart = 542 concatStringsSep " " 543 ([ 544 "${cfg.package}/bin/nodetool" 545 "repair" 546 ] ++ cfg.incrementalRepairOptions); 547 }; 548 }; 549 550 systemd.timers.cassandra-incremental-repair = 551 mkIf (cfg.incrementalRepairInterval != null) { 552 description = "Schedule incremental repairs on Cassandra"; 553 wantedBy = [ "timers.target" ]; 554 timerConfig = { 555 OnBootSec = cfg.incrementalRepairInterval; 556 OnUnitActiveSec = cfg.incrementalRepairInterval; 557 Persistent = true; 558 }; 559 }; 560 }; 561 562 meta.maintainers = with lib.maintainers; [ roberth ]; 563}