cassandra: format

This commit is contained in:
Robert Hensing 2021-04-29 08:22:39 +02:00
parent 1c65a509fb
commit 19ba3d97d2
2 changed files with 237 additions and 169 deletions

View File

@ -4,9 +4,12 @@ with lib;
let let
cfg = config.services.cassandra; cfg = config.services.cassandra;
defaultUser = "cassandra"; defaultUser = "cassandra";
cassandraConfig = flip recursiveUpdate cfg.extraConfig
({ commitlog_sync = "batch"; cassandraConfig = flip recursiveUpdate cfg.extraConfig (
{
commitlog_sync = "batch";
commitlog_sync_batch_window_in_ms = 2; commitlog_sync_batch_window_in_ms = 2;
start_native_transport = cfg.allowClients; start_native_transport = cfg.allowClients;
cluster_name = cfg.clusterName; cluster_name = cfg.clusterName;
@ -15,17 +18,20 @@ let
data_file_directories = [ "${cfg.homeDir}/data" ]; data_file_directories = [ "${cfg.homeDir}/data" ];
commitlog_directory = "${cfg.homeDir}/commitlog"; commitlog_directory = "${cfg.homeDir}/commitlog";
saved_caches_directory = "${cfg.homeDir}/saved_caches"; saved_caches_directory = "${cfg.homeDir}/saved_caches";
} // (lib.optionalAttrs (cfg.seedAddresses != []) { } // lib.optionalAttrs (cfg.seedAddresses != [ ]) {
seed_provider = [{ seed_provider = [
{
class_name = "org.apache.cassandra.locator.SimpleSeedProvider"; class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }]; parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }];
}]; }
}) // (lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") { ];
} // lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") {
hints_directory = "${cfg.homeDir}/hints"; hints_directory = "${cfg.homeDir}/hints";
}) }
); );
cassandraConfigWithAddresses = cassandraConfig //
( if cfg.listenAddress == null cassandraConfigWithAddresses = cassandraConfig // (
if cfg.listenAddress == null
then { listen_interface = cfg.listenInterface; } then { listen_interface = cfg.listenInterface; }
else { listen_address = cfg.listenAddress; } else { listen_address = cfg.listenAddress; }
) // ( ) // (
@ -33,13 +39,17 @@ let
then { rpc_interface = cfg.rpcInterface; } then { rpc_interface = cfg.rpcInterface; }
else { rpc_address = cfg.rpcAddress; } else { rpc_address = cfg.rpcAddress; }
); );
cassandraEtc = pkgs.stdenv.mkDerivation
{ name = "cassandra-etc"; cassandraEtc = pkgs.stdenv.mkDerivation {
name = "cassandra-etc";
cassandraYaml = builtins.toJSON cassandraConfigWithAddresses; cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh"; cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig; cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
passAsFile = [ "extraEnvSh" ]; passAsFile = [ "extraEnvSh" ];
inherit (cfg) extraEnvSh; inherit (cfg) extraEnvSh;
buildCommand = '' buildCommand = ''
mkdir -p "$out" mkdir -p "$out"
@ -58,22 +68,29 @@ let
sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh" sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
''; '';
}; };
defaultJmxRolesFile = builtins.foldl'
defaultJmxRolesFile =
builtins.foldl'
(left: right: left + right) "" (left: right: left + right) ""
(map (role: "${role.username} ${role.password}") cfg.jmxRoles); (map (role: "${role.username} ${role.password}") cfg.jmxRoles);
fullJvmOptions = cfg.jvmOpts
fullJvmOptions =
cfg.jvmOpts
++ lib.optionals (cfg.jmxRoles != [ ]) [ ++ lib.optionals (cfg.jmxRoles != [ ]) [
"-Dcom.sun.management.jmxremote.authenticate=true" "-Dcom.sun.management.jmxremote.authenticate=true"
"-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}" "-Dcom.sun.management.jmxremote.password.file=${cfg.jmxRolesFile}"
] ] ++ lib.optionals cfg.remoteJmx [
++ lib.optionals cfg.remoteJmx [
"-Djava.rmi.server.hostname=${cfg.rpcAddress}" "-Djava.rmi.server.hostname=${cfg.rpcAddress}"
]; ];
in {
in
{
options.services.cassandra = { options.services.cassandra = {
enable = mkEnableOption '' enable = mkEnableOption ''
Apache Cassandra Scalable and highly available database. Apache Cassandra Scalable and highly available database.
''; '';
clusterName = mkOption { clusterName = mkOption {
type = types.str; type = types.str;
default = "Test Cluster"; default = "Test Cluster";
@ -83,16 +100,19 @@ in {
another. All nodes in a cluster must have the same value. another. All nodes in a cluster must have the same value.
''; '';
}; };
user = mkOption { user = mkOption {
type = types.str; type = types.str;
default = defaultUser; default = defaultUser;
description = "Run Apache Cassandra under this user."; description = "Run Apache Cassandra under this user.";
}; };
group = mkOption { group = mkOption {
type = types.str; type = types.str;
default = defaultUser; default = defaultUser;
description = "Run Apache Cassandra under this group."; description = "Run Apache Cassandra under this group.";
}; };
homeDir = mkOption { homeDir = mkOption {
type = types.path; type = types.path;
default = "/var/lib/cassandra"; default = "/var/lib/cassandra";
@ -100,6 +120,7 @@ in {
Home directory for Apache Cassandra. Home directory for Apache Cassandra.
''; '';
}; };
package = mkOption { package = mkOption {
type = types.package; type = types.package;
default = pkgs.cassandra; default = pkgs.cassandra;
@ -109,6 +130,7 @@ in {
The Apache Cassandra package to use. The Apache Cassandra package to use.
''; '';
}; };
jvmOpts = mkOption { jvmOpts = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [ ];
@ -116,6 +138,7 @@ in {
Populate the JVM_OPT environment variable. Populate the JVM_OPT environment variable.
''; '';
}; };
listenAddress = mkOption { listenAddress = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = "127.0.0.1"; default = "127.0.0.1";
@ -136,6 +159,7 @@ in {
Setting listen_address to 0.0.0.0 is always wrong. Setting listen_address to 0.0.0.0 is always wrong.
''; '';
}; };
listenInterface = mkOption { listenInterface = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -146,6 +170,7 @@ in {
supported. supported.
''; '';
}; };
rpcAddress = mkOption { rpcAddress = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = "127.0.0.1"; default = "127.0.0.1";
@ -167,6 +192,7 @@ in {
internet. Firewall it if needed. internet. Firewall it if needed.
''; '';
}; };
rpcInterface = mkOption { rpcInterface = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -176,6 +202,7 @@ in {
correspond to a single address, IP aliasing is not supported. correspond to a single address, IP aliasing is not supported.
''; '';
}; };
logbackConfig = mkOption { logbackConfig = mkOption {
type = types.lines; type = types.lines;
default = '' default = ''
@ -197,6 +224,7 @@ in {
XML logback configuration for cassandra XML logback configuration for cassandra
''; '';
}; };
seedAddresses = mkOption { seedAddresses = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ "127.0.0.1" ]; default = [ "127.0.0.1" ];
@ -207,6 +235,7 @@ in {
Set to 127.0.0.1 for a single node cluster. Set to 127.0.0.1 for a single node cluster.
''; '';
}; };
allowClients = mkOption { allowClients = mkOption {
type = types.bool; type = types.bool;
default = true; default = true;
@ -219,16 +248,19 @@ in {
<literal>extraConfig</literal>. <literal>extraConfig</literal>.
''; '';
}; };
extraConfig = mkOption { extraConfig = mkOption {
type = types.attrs; type = types.attrs;
default = { }; default = { };
example = example =
{ commitlog_sync_batch_window_in_ms = 3; {
commitlog_sync_batch_window_in_ms = 3;
}; };
description = '' description = ''
Extra options to be merged into cassandra.yaml as nix attribute set. Extra options to be merged into cassandra.yaml as nix attribute set.
''; '';
}; };
extraEnvSh = mkOption { extraEnvSh = mkOption {
type = types.lines; type = types.lines;
default = ""; default = "";
@ -237,6 +269,7 @@ in {
Extra shell lines to be appended onto cassandra-env.sh. Extra shell lines to be appended onto cassandra-env.sh.
''; '';
}; };
fullRepairInterval = mkOption { fullRepairInterval = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = "3w"; default = "3w";
@ -250,6 +283,7 @@ in {
Set to <literal>null</literal> to disable full repairs. Set to <literal>null</literal> to disable full repairs.
''; '';
}; };
fullRepairOptions = mkOption { fullRepairOptions = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [ ];
@ -258,6 +292,7 @@ in {
Options passed through to the full repair command. Options passed through to the full repair command.
''; '';
}; };
incrementalRepairInterval = mkOption { incrementalRepairInterval = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = "3d"; default = "3d";
@ -271,6 +306,7 @@ in {
Set to <literal>null</literal> to disable incremental repairs. Set to <literal>null</literal> to disable incremental repairs.
''; '';
}; };
incrementalRepairOptions = mkOption { incrementalRepairOptions = mkOption {
type = types.listOf types.str; type = types.listOf types.str;
default = [ ]; default = [ ];
@ -279,6 +315,7 @@ in {
Options passed through to the incremental repair command. Options passed through to the incremental repair command.
''; '';
}; };
maxHeapSize = mkOption { maxHeapSize = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -299,6 +336,7 @@ in {
expensive GC will be (usually). expensive GC will be (usually).
''; '';
}; };
heapNewSize = mkOption { heapNewSize = mkOption {
type = types.nullOr types.str; type = types.nullOr types.str;
default = null; default = null;
@ -322,6 +360,7 @@ in {
100 MB per physical CPU core. 100 MB per physical CPU core.
''; '';
}; };
mallocArenaMax = mkOption { mallocArenaMax = mkOption {
type = types.nullOr types.int; type = types.nullOr types.int;
default = null; default = null;
@ -330,6 +369,7 @@ in {
Set this to control the amount of arenas per-thread in glibc. Set this to control the amount of arenas per-thread in glibc.
''; '';
}; };
remoteJmx = mkOption { remoteJmx = mkOption {
type = types.bool; type = types.bool;
default = false; default = false;
@ -341,6 +381,7 @@ in {
See: https://wiki.apache.org/cassandra/JmxSecurity See: https://wiki.apache.org/cassandra/JmxSecurity
''; '';
}; };
jmxPort = mkOption { jmxPort = mkOption {
type = types.int; type = types.int;
default = 7199; default = 7199;
@ -351,6 +392,7 @@ in {
Firewall it if needed. Firewall it if needed.
''; '';
}; };
jmxRoles = mkOption { jmxRoles = mkOption {
default = [ ]; default = [ ];
description = '' description = ''
@ -375,9 +417,11 @@ in {
}; };
}); });
}; };
jmxRolesFile = mkOption { jmxRolesFile = mkOption {
type = types.nullOr types.path; type = types.nullOr types.path;
default = if (lib.versionAtLeast cfg.package.version "3.11") default =
if lib.versionAtLeast cfg.package.version "3.11"
then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
else null; else null;
example = "/var/lib/cassandra/jmx.password"; example = "/var/lib/cassandra/jmx.password";
@ -391,17 +435,21 @@ in {
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = assertions = [
[ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null); {
assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
message = "You have to set either listenAddress or listenInterface"; message = "You have to set either listenAddress or listenInterface";
} }
{ assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null); {
assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null);
message = "You have to set either rpcAddress or rpcInterface"; message = "You have to set either rpcAddress or rpcInterface";
} }
{ assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null); {
assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null);
message = "If you set either of maxHeapSize or heapNewSize you have to set both"; message = "If you set either of maxHeapSize or heapNewSize you have to set both";
} }
{ assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null; {
assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null;
message = '' message = ''
If you want JMX available remotely you need to set a password using If you want JMX available remotely you need to set a password using
<literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if
@ -410,8 +458,8 @@ in {
} }
]; ];
users = mkIf (cfg.user == defaultUser) { users = mkIf (cfg.user == defaultUser) {
extraUsers.${defaultUser} = extraUsers.${defaultUser} = {
{ group = cfg.group; group = cfg.group;
home = cfg.homeDir; home = cfg.homeDir;
createHome = true; createHome = true;
uid = config.ids.uids.cassandra; uid = config.ids.uids.cassandra;
@ -420,11 +468,11 @@ in {
extraGroups.${defaultUser}.gid = config.ids.gids.cassandra; extraGroups.${defaultUser}.gid = config.ids.gids.cassandra;
}; };
systemd.services.cassandra = systemd.services.cassandra = {
{ description = "Apache Cassandra service"; description = "Apache Cassandra service";
after = [ "network.target" ]; after = [ "network.target" ];
environment = environment = {
{ CASSANDRA_CONF = "${cassandraEtc}"; CASSANDRA_CONF = "${cassandraEtc}";
JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions; JVM_OPTS = builtins.concatStringsSep " " fullJvmOptions;
MAX_HEAP_SIZE = toString cfg.maxHeapSize; MAX_HEAP_SIZE = toString cfg.maxHeapSize;
HEAP_NEWSIZE = toString cfg.heapNewSize; HEAP_NEWSIZE = toString cfg.heapNewSize;
@ -433,57 +481,64 @@ in {
JMX_PORT = toString cfg.jmxPort; JMX_PORT = toString cfg.jmxPort;
}; };
wantedBy = [ "multi-user.target" ]; wantedBy = [ "multi-user.target" ];
serviceConfig = serviceConfig = {
{ User = cfg.user; User = cfg.user;
Group = cfg.group; Group = cfg.group;
ExecStart = "${cfg.package}/bin/cassandra -f"; ExecStart = "${cfg.package}/bin/cassandra -f";
SuccessExitStatus = 143; SuccessExitStatus = 143;
}; };
}; };
systemd.services.cassandra-full-repair = systemd.services.cassandra-full-repair = {
{ description = "Perform a full repair on this Cassandra node"; description = "Perform a full repair on this Cassandra node";
after = [ "cassandra.service" ]; after = [ "cassandra.service" ];
requires = [ "cassandra.service" ]; requires = [ "cassandra.service" ];
serviceConfig = serviceConfig = {
{ User = cfg.user; User = cfg.user;
Group = cfg.group; Group = cfg.group;
ExecStart = ExecStart =
lib.concatStringsSep " " lib.concatStringsSep " "
([ "${cfg.package}/bin/nodetool" "repair" "--full" ([
"${cfg.package}/bin/nodetool"
"repair"
"--full"
] ++ cfg.fullRepairOptions); ] ++ cfg.fullRepairOptions);
}; };
}; };
systemd.timers.cassandra-full-repair = systemd.timers.cassandra-full-repair =
mkIf (cfg.fullRepairInterval != null) { mkIf (cfg.fullRepairInterval != null) {
description = "Schedule full repairs on Cassandra"; description = "Schedule full repairs on Cassandra";
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
timerConfig = timerConfig = {
{ OnBootSec = cfg.fullRepairInterval; OnBootSec = cfg.fullRepairInterval;
OnUnitActiveSec = cfg.fullRepairInterval; OnUnitActiveSec = cfg.fullRepairInterval;
Persistent = true; Persistent = true;
}; };
}; };
systemd.services.cassandra-incremental-repair = systemd.services.cassandra-incremental-repair = {
{ description = "Perform an incremental repair on this cassandra node."; description = "Perform an incremental repair on this cassandra node.";
after = [ "cassandra.service" ]; after = [ "cassandra.service" ];
requires = [ "cassandra.service" ]; requires = [ "cassandra.service" ];
serviceConfig = serviceConfig = {
{ User = cfg.user; User = cfg.user;
Group = cfg.group; Group = cfg.group;
ExecStart = ExecStart =
lib.concatStringsSep " " lib.concatStringsSep " "
([ "${cfg.package}/bin/nodetool" "repair" ([
"${cfg.package}/bin/nodetool"
"repair"
] ++ cfg.incrementalRepairOptions); ] ++ cfg.incrementalRepairOptions);
}; };
}; };
systemd.timers.cassandra-incremental-repair = systemd.timers.cassandra-incremental-repair =
mkIf (cfg.incrementalRepairInterval != null) { mkIf (cfg.incrementalRepairInterval != null) {
description = "Schedule incremental repairs on Cassandra"; description = "Schedule incremental repairs on Cassandra";
wantedBy = [ "timers.target" ]; wantedBy = [ "timers.target" ];
timerConfig = timerConfig = {
{ OnBootSec = cfg.incrementalRepairInterval; OnBootSec = cfg.incrementalRepairInterval;
OnUnitActiveSec = cfg.incrementalRepairInterval; OnUnitActiveSec = cfg.incrementalRepairInterval;
Persistent = true; Persistent = true;
}; };

View File

@ -1,22 +1,34 @@
{ lib, stdenv, fetchurl, python, makeWrapper, gawk, bash, getopt, procps { lib
, which, jre, coreutils, nixosTests , stdenv
, fetchurl
, python
, makeWrapper
, gawk
, bash
, getopt
, procps
, which
, jre
, coreutils
, nixosTests
# generation is the attribute version suffix such as 3_11 in pkgs.cassandra_3_11 # generation is the attribute version suffix such as 3_11 in pkgs.cassandra_3_11
, generation , generation
, version, sha256 , version
, sha256
, extraMeta ? { } , extraMeta ? { }
, ... , ...
}: }:
let let
libPath = lib.makeLibraryPath [ stdenv.cc.cc ]; libPath = lib.makeLibraryPath [ stdenv.cc.cc ];
binPath = with lib; makeBinPath ([ binPath = with lib; makeBinPath [
bash bash
getopt getopt
gawk gawk
which which
jre jre
procps procps
]); ];
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -96,7 +108,8 @@ stdenv.mkDerivation rec {
tests = tests =
let let
test = nixosTests."cassandra_${generation}"; test = nixosTests."cassandra_${generation}";
in { in
{
nixos = nixos =
assert test.testPackage.version == version; assert test.testPackage.version == version;
test; test;