Merge pull request #121124 from hercules-ci/cassandra-tidy

cassandra: tidy
This commit is contained in:
Robert Hensing 2021-05-03 13:41:41 +02:00 committed by GitHub
commit 0cf3550c91
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 258 additions and 176 deletions

View File

@ -1,79 +1,108 @@
{ config, lib, pkgs, ... }: { config, lib, pkgs, ... }:
with lib;
let let
inherit (lib)
concatStringsSep
flip
literalExample
optionalAttrs
optionals
recursiveUpdate
mkEnableOption
mkIf
mkOption
types
versionAtLeast
;
cfg = config.services.cassandra; cfg = config.services.cassandra;
defaultUser = "cassandra"; defaultUser = "cassandra";
cassandraConfig = flip recursiveUpdate cfg.extraConfig
({ commitlog_sync = "batch";
commitlog_sync_batch_window_in_ms = 2;
start_native_transport = cfg.allowClients;
cluster_name = cfg.clusterName;
partitioner = "org.apache.cassandra.dht.Murmur3Partitioner";
endpoint_snitch = "SimpleSnitch";
data_file_directories = [ "${cfg.homeDir}/data" ];
commitlog_directory = "${cfg.homeDir}/commitlog";
saved_caches_directory = "${cfg.homeDir}/saved_caches";
} // (lib.optionalAttrs (cfg.seedAddresses != []) {
seed_provider = [{
class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
parameters = [ { seeds = concatStringsSep "," cfg.seedAddresses; } ];
}];
}) // (lib.optionalAttrs (lib.versionAtLeast cfg.package.version "3") {
hints_directory = "${cfg.homeDir}/hints";
})
);
cassandraConfigWithAddresses = cassandraConfig //
( if cfg.listenAddress == null
then { listen_interface = cfg.listenInterface; }
else { listen_address = cfg.listenAddress; }
) // (
if cfg.rpcAddress == null
then { rpc_interface = cfg.rpcInterface; }
else { rpc_address = cfg.rpcAddress; }
);
cassandraEtc = pkgs.stdenv.mkDerivation
{ name = "cassandra-etc";
cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
passAsFile = [ "extraEnvSh" ];
inherit (cfg) extraEnvSh;
buildCommand = ''
mkdir -p "$out"
echo "$cassandraYaml" > "$out/cassandra.yaml" cassandraConfig = flip recursiveUpdate cfg.extraConfig (
ln -s "$cassandraLogbackConfig" "$out/logback.xml" {
commitlog_sync = "batch";
commitlog_sync_batch_window_in_ms = 2;
start_native_transport = cfg.allowClients;
cluster_name = cfg.clusterName;
partitioner = "org.apache.cassandra.dht.Murmur3Partitioner";
endpoint_snitch = "SimpleSnitch";
data_file_directories = [ "${cfg.homeDir}/data" ];
commitlog_directory = "${cfg.homeDir}/commitlog";
saved_caches_directory = "${cfg.homeDir}/saved_caches";
} // optionalAttrs (cfg.seedAddresses != [ ]) {
seed_provider = [
{
class_name = "org.apache.cassandra.locator.SimpleSeedProvider";
parameters = [{ seeds = concatStringsSep "," cfg.seedAddresses; }];
}
];
} // optionalAttrs (versionAtLeast cfg.package.version "3") {
hints_directory = "${cfg.homeDir}/hints";
}
);
( cat "$cassandraEnvPkg" cassandraConfigWithAddresses = cassandraConfig // (
echo "# lines from services.cassandra.extraEnvSh: " if cfg.listenAddress == null
cat "$extraEnvShPath" then { listen_interface = cfg.listenInterface; }
) > "$out/cassandra-env.sh" else { listen_address = cfg.listenAddress; }
) // (
if cfg.rpcAddress == null
then { rpc_interface = cfg.rpcInterface; }
else { rpc_address = cfg.rpcAddress; }
);
# Delete default JMX Port, otherwise we can't set it using env variable cassandraEtc = pkgs.stdenv.mkDerivation {
sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh" name = "cassandra-etc";
# Delete default password file cassandraYaml = builtins.toJSON cassandraConfigWithAddresses;
sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh" cassandraEnvPkg = "${cfg.package}/conf/cassandra-env.sh";
''; cassandraLogbackConfig = pkgs.writeText "logback.xml" cfg.logbackConfig;
};
defaultJmxRolesFile = builtins.foldl' passAsFile = [ "extraEnvSh" ];
(left: right: left + right) "" inherit (cfg) extraEnvSh;
(map (role: "${role.username} ${role.password}") cfg.jmxRoles);
fullJvmOptions = cfg.jvmOpts buildCommand = ''
++ lib.optionals (cfg.jmxRoles != []) [ mkdir -p "$out"
echo "$cassandraYaml" > "$out/cassandra.yaml"
ln -s "$cassandraLogbackConfig" "$out/logback.xml"
( cat "$cassandraEnvPkg"
echo "# lines from services.cassandra.extraEnvSh: "
cat "$extraEnvShPath"
) > "$out/cassandra-env.sh"
# Delete default JMX Port, otherwise we can't set it using env variable
sed -i '/JMX_PORT="7199"/d' "$out/cassandra-env.sh"
# Delete default password file
sed -i '/-Dcom.sun.management.jmxremote.password.file=\/etc\/cassandra\/jmxremote.password/d' "$out/cassandra-env.sh"
'';
};
defaultJmxRolesFile =
builtins.foldl'
(left: right: left + right) ""
(map (role: "${role.username} ${role.password}") cfg.jmxRoles);
fullJvmOptions =
cfg.jvmOpts
++ 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}"
] ] ++ 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 +112,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 +132,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,17 +142,19 @@ 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 = [ ];
description = '' description = ''
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";
example = literalExample "null"; example = null;
description = '' description = ''
Address or interface to bind to and tell other Cassandra nodes Address or interface to bind to and tell other Cassandra nodes
to connect to. You _must_ change this if you want multiple to connect to. You _must_ change this if you want multiple
@ -136,6 +171,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,10 +182,11 @@ 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";
example = literalExample "null"; example = null;
description = '' description = ''
The address or interface to bind the native transport server to. The address or interface to bind the native transport server to.
@ -167,6 +204,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 +214,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 +236,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 +247,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 +260,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,48 +281,53 @@ 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";
example = literalExample "null"; example = null;
description = '' description = ''
Set the interval how often full repairs are run, i.e. Set the interval how often full repairs are run, i.e.
<literal>nodetool repair --full</literal> is executed. See <literal>nodetool repair --full</literal> is executed. See
https://cassandra.apache.org/doc/latest/operating/repair.html https://cassandra.apache.org/doc/latest/operating/repair.html
for more information. for more information.
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 = [ ];
example = [ "--partitioner-range" ]; example = [ "--partitioner-range" ];
description = '' description = ''
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";
example = literalExample "null"; example = null;
description = '' description = ''
Set the interval how often incremental repairs are run, i.e. Set the interval how often incremental repairs are run, i.e.
<literal>nodetool repair</literal> is executed. See <literal>nodetool repair</literal> is executed. See
https://cassandra.apache.org/doc/latest/operating/repair.html https://cassandra.apache.org/doc/latest/operating/repair.html
for more information. for more information.
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 = [ ];
example = [ "--partitioner-range" ]; example = [ "--partitioner-range" ];
description = '' description = ''
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 +348,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 +372,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 +381,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 +393,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,8 +404,9 @@ in {
Firewall it if needed. Firewall it if needed.
''; '';
}; };
jmxRoles = mkOption { jmxRoles = mkOption {
default = []; default = [ ];
description = '' description = ''
Roles that are allowed to access the JMX (e.g. nodetool) Roles that are allowed to access the JMX (e.g. nodetool)
BEWARE: The passwords will be stored world readable in the nix-store. BEWARE: The passwords will be stored world readable in the nix-store.
@ -375,11 +429,13 @@ 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 =
then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile if versionAtLeast cfg.package.version "3.11"
else null; then pkgs.writeText "jmx-roles-file" defaultJmxRolesFile
else null;
example = "/var/lib/cassandra/jmx.password"; example = "/var/lib/cassandra/jmx.password";
description = '' description = ''
Specify your own jmx roles file. Specify your own jmx roles file.
@ -391,102 +447,115 @@ in {
}; };
config = mkIf cfg.enable { config = mkIf cfg.enable {
assertions = assertions = [
[ { assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null); {
message = "You have to set either listenAddress or listenInterface"; assertion = (cfg.listenAddress == null) != (cfg.listenInterface == null);
} message = "You have to set either listenAddress or listenInterface";
{ assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null); }
message = "You have to set either rpcAddress or rpcInterface"; {
} assertion = (cfg.rpcAddress == null) != (cfg.rpcInterface == null);
{ assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null); message = "You have to set either rpcAddress or rpcInterface";
message = "If you set either of maxHeapSize or heapNewSize you have to set both"; }
} {
{ assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null; assertion = (cfg.maxHeapSize == null) == (cfg.heapNewSize == null);
message = '' message = "If you set either of maxHeapSize or heapNewSize you have to set both";
If you want JMX available remotely you need to set a password using }
<literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if {
using Cassandra older than v3.11. assertion = cfg.remoteJmx -> cfg.jmxRolesFile != null;
''; message = ''
} If you want JMX available remotely you need to set a password using
]; <literal>jmxRoles</literal> or <literal>jmxRolesFile</literal> if
using Cassandra older than v3.11.
'';
}
];
users = mkIf (cfg.user == defaultUser) { users = mkIf (cfg.user == defaultUser) {
extraUsers.${defaultUser} = users.${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;
description = "Cassandra service user"; description = "Cassandra service user";
}; };
extraGroups.${defaultUser}.gid = config.ids.gids.cassandra; groups.${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;
MALLOC_ARENA_MAX = toString cfg.mallocArenaMax; MALLOC_ARENA_MAX = toString cfg.mallocArenaMax;
LOCAL_JMX = if cfg.remoteJmx then "no" else "yes"; LOCAL_JMX = if cfg.remoteJmx then "no" else "yes";
JMX_PORT = toString cfg.jmxPort; JMX_PORT = toString cfg.jmxPort;
};
wantedBy = [ "multi-user.target" ];
serviceConfig =
{ User = cfg.user;
Group = cfg.group;
ExecStart = "${cfg.package}/bin/cassandra -f";
SuccessExitStatus = 143;
};
}; };
wantedBy = [ "multi-user.target" ];
serviceConfig = {
User = cfg.user;
Group = cfg.group;
ExecStart = "${cfg.package}/bin/cassandra -f";
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 " " concatStringsSep " "
([ "${cfg.package}/bin/nodetool" "repair" "--full" ([
] ++ cfg.fullRepairOptions); "${cfg.package}/bin/nodetool"
}; "repair"
"--full"
] ++ 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 " " concatStringsSep " "
([ "${cfg.package}/bin/nodetool" "repair" ([
] ++ cfg.incrementalRepairOptions); "${cfg.package}/bin/nodetool"
}; "repair"
] ++ 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;
}; };
}; };
}; };
meta.maintainers = with lib.maintainers; [ roberth ];
} }

View File

@ -1,22 +1,34 @@
{ lib, stdenv, fetchurl, python, makeWrapper, gawk, bash, getopt, procps { lib
, which, jre, coreutils, nixosTests , stdenv
# generation is the attribute version suffix such as 3_11 in pkgs.cassandra_3_11 , 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 , generation
, version, sha256 , version
, extraMeta ? {} , sha256
, extraMeta ? { }
, ... , ...
}: }:
let let
libPath = lib.makeLibraryPath [ stdenv.cc.cc ]; libPath = lib.makeLibraryPath [ stdenv.cc.cc ];
binPath = with lib; makeBinPath ([ binPath = lib.makeBinPath [
bash bash
getopt getopt
gawk gawk
which which
jre jre
procps procps
]); ];
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
@ -90,13 +102,14 @@ stdenv.mkDerivation rec {
wrapProgram $out/bin/cqlsh --prefix PATH : ${python}/bin wrapProgram $out/bin/cqlsh --prefix PATH : ${python}/bin
runHook postInstall runHook postInstall
''; '';
passthru = { passthru = {
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;