python3Packages.orjson: Disable failing tests on 32 bit
[NixPkgs.git] / nixos / modules / services / misc / exhibitor.nix
blob91a87b55af595ecdf19f2d0fb8c1b165a039a7a4
1 { config, lib, options, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.exhibitor;
7   opt = options.services.exhibitor;
8   exhibitorConfig = ''
9     zookeeper-install-directory=${cfg.baseDir}/zookeeper
10     zookeeper-data-directory=${cfg.zkDataDir}
11     zookeeper-log-directory=${cfg.zkLogDir}
12     zoo-cfg-extra=${cfg.zkExtraCfg}
13     client-port=${toString cfg.zkClientPort}
14     connect-port=${toString cfg.zkConnectPort}
15     election-port=${toString cfg.zkElectionPort}
16     cleanup-period-ms=${toString cfg.zkCleanupPeriod}
17     servers-spec=${concatStringsSep "," cfg.zkServersSpec}
18     auto-manage-instances=${toString cfg.autoManageInstances}
19     ${cfg.extraConf}
20   '';
21   # NB: toString rather than lib.boolToString on cfg.autoManageInstances is intended.
22   # Exhibitor tests if it's an integer not equal to 0, so the empty string (toString false)
23   # will operate in the same fashion as a 0.
24   configDir = pkgs.writeTextDir "exhibitor.properties" exhibitorConfig;
25   cliOptionsCommon = {
26     configtype = cfg.configType;
27     defaultconfig = "${configDir}/exhibitor.properties";
28     port = toString cfg.port;
29     hostname = cfg.hostname;
30     headingtext = if (cfg.headingText != null) then (lib.escapeShellArg cfg.headingText) else null;
31     nodemodification = lib.boolToString cfg.nodeModification;
32     configcheckms = toString cfg.configCheckMs;
33     jquerystyle = cfg.jqueryStyle;
34     loglines = toString cfg.logLines;
35     servo = lib.boolToString cfg.servo;
36     timeout = toString cfg.timeout;
37   };
38   s3CommonOptions = { s3region = cfg.s3Region; s3credentials = cfg.s3Credentials; };
39   cliOptionsPerConfig = {
40     s3 = {
41       s3config = "${cfg.s3Config.bucketName}:${cfg.s3Config.objectKey}";
42       s3configprefix = cfg.s3Config.configPrefix;
43     };
44     zookeeper = {
45       zkconfigconnect = concatStringsSep "," cfg.zkConfigConnect;
46       zkconfigexhibitorpath = cfg.zkConfigExhibitorPath;
47       zkconfigpollms = toString cfg.zkConfigPollMs;
48       zkconfigretry = "${toString cfg.zkConfigRetry.sleepMs}:${toString cfg.zkConfigRetry.retryQuantity}";
49       zkconfigzpath = cfg.zkConfigZPath;
50       zkconfigexhibitorport = toString cfg.zkConfigExhibitorPort; # NB: This might be null
51     };
52     file = {
53       fsconfigdir = cfg.fsConfigDir;
54       fsconfiglockprefix = cfg.fsConfigLockPrefix;
55       fsConfigName = fsConfigName;
56     };
57     none = {
58       noneconfigdir = configDir;
59     };
60   };
61   cliOptions = concatStringsSep " " (mapAttrsToList (k: v: "--${k} ${v}") (filterAttrs (k: v: v != null && v != "") (cliOptionsCommon //
62                cliOptionsPerConfig.${cfg.configType} //
63                s3CommonOptions //
64                optionalAttrs cfg.s3Backup { s3backup = "true"; } //
65                optionalAttrs cfg.fileSystemBackup { filesystembackup = "true"; }
66                )));
69   options = {
70     services.exhibitor = {
71       enable = mkEnableOption (lib.mdDoc "exhibitor server");
73       # See https://github.com/soabase/exhibitor/wiki/Running-Exhibitor for what these mean
74       # General options for any type of config
75       port = mkOption {
76         type = types.port;
77         default = 8080;
78         description = lib.mdDoc ''
79           The port for exhibitor to listen on and communicate with other exhibitors.
80         '';
81       };
82       baseDir = mkOption {
83         type = types.str;
84         default = "/var/exhibitor";
85         description = lib.mdDoc ''
86           Baseline directory for exhibitor runtime config.
87         '';
88       };
89       configType = mkOption {
90         type = types.enum [ "file" "s3" "zookeeper" "none" ];
91         description = lib.mdDoc ''
92           Which configuration type you want to use. Additional config will be
93           required depending on which type you are using.
94         '';
95       };
96       hostname = mkOption {
97         type = types.nullOr types.str;
98         description = lib.mdDoc ''
99           Hostname to use and advertise
100         '';
101         default = null;
102       };
103       nodeModification = mkOption {
104         type = types.bool;
105         description = lib.mdDoc ''
106           Whether the Explorer UI will allow nodes to be modified (use with caution).
107         '';
108         default = true;
109       };
110       configCheckMs = mkOption {
111         type = types.int;
112         description = lib.mdDoc ''
113           Period (ms) to check for shared config updates.
114         '';
115         default = 30000;
116       };
117       headingText = mkOption {
118         type = types.nullOr types.str;
119         description = lib.mdDoc ''
120           Extra text to display in UI header
121         '';
122         default = null;
123       };
124       jqueryStyle = mkOption {
125         type = types.enum [ "red" "black" "custom" ];
126         description = lib.mdDoc ''
127           Styling used for the JQuery-based UI.
128         '';
129         default = "red";
130       };
131       logLines = mkOption {
132         type = types.int;
133         description = lib.mdDoc ''
134         Max lines of logging to keep in memory for display.
135         '';
136         default = 1000;
137       };
138       servo = mkOption {
139         type = types.bool;
140         description = lib.mdDoc ''
141           ZooKeeper will be queried once a minute for its state via the 'mntr' four
142           letter word (this requires ZooKeeper 3.4.x+). Servo will be used to publish
143           this data via JMX.
144         '';
145         default = false;
146       };
147       timeout = mkOption {
148         type = types.int;
149         description = lib.mdDoc ''
150           Connection timeout (ms) for ZK connections.
151         '';
152         default = 30000;
153       };
154       autoManageInstances = mkOption {
155         type = types.bool;
156         description = lib.mdDoc ''
157           Automatically manage ZooKeeper instances in the ensemble
158         '';
159         default = false;
160       };
161       zkDataDir = mkOption {
162         type = types.str;
163         default = "${cfg.baseDir}/zkData";
164         defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkData"'';
165         description = lib.mdDoc ''
166           The Zookeeper data directory
167         '';
168       };
169       zkLogDir = mkOption {
170         type = types.path;
171         default = "${cfg.baseDir}/zkLogs";
172         defaultText = literalExpression ''"''${config.${opt.baseDir}}/zkLogs"'';
173         description = lib.mdDoc ''
174           The Zookeeper logs directory
175         '';
176       };
177       extraConf = mkOption {
178         type = types.str;
179         default = "";
180         description = lib.mdDoc ''
181           Extra Exhibitor configuration to put in the ZooKeeper config file.
182         '';
183       };
184       zkExtraCfg = mkOption {
185         type = types.str;
186         default = "initLimit=5&syncLimit=2&tickTime=2000";
187         description = lib.mdDoc ''
188           Extra options to pass into Zookeeper
189         '';
190       };
191       zkClientPort = mkOption {
192         type = types.int;
193         default = 2181;
194         description = lib.mdDoc ''
195           Zookeeper client port
196         '';
197       };
198       zkConnectPort = mkOption {
199         type = types.int;
200         default = 2888;
201         description = lib.mdDoc ''
202           The port to use for followers to talk to each other.
203         '';
204       };
205       zkElectionPort = mkOption {
206         type = types.int;
207         default = 3888;
208         description = lib.mdDoc ''
209           The port for Zookeepers to use for leader election.
210         '';
211       };
212       zkCleanupPeriod = mkOption {
213         type = types.int;
214         default = 0;
215         description = lib.mdDoc ''
216           How often (in milliseconds) to run the Zookeeper log cleanup task.
217         '';
218       };
219       zkServersSpec = mkOption {
220         type = types.listOf types.str;
221         default = [];
222         description = lib.mdDoc ''
223           Zookeeper server spec for all servers in the ensemble.
224         '';
225         example = [ "S:1:zk1.example.com" "S:2:zk2.example.com" "S:3:zk3.example.com" "O:4:zk-observer.example.com" ];
226       };
228       # Backup options
229       s3Backup = mkOption {
230         type = types.bool;
231         default = false;
232         description = lib.mdDoc ''
233           Whether to enable backups to S3
234         '';
235       };
236       fileSystemBackup = mkOption {
237         type = types.bool;
238         default = false;
239         description = lib.mdDoc ''
240           Enables file system backup of ZooKeeper log files
241         '';
242       };
244       # Options for using zookeeper configType
245       zkConfigConnect = mkOption {
246         type = types.listOf types.str;
247         description = lib.mdDoc ''
248           The initial connection string for ZooKeeper shared config storage
249         '';
250         example = ["host1:2181" "host2:2181"];
251       };
252       zkConfigExhibitorPath = mkOption {
253         type = types.str;
254         description = lib.mdDoc ''
255           If the ZooKeeper shared config is also running Exhibitor, the URI path for the REST call
256         '';
257         default = "/";
258       };
259       zkConfigExhibitorPort = mkOption {
260         type = types.nullOr types.int;
261         description = lib.mdDoc ''
262           If the ZooKeeper shared config is also running Exhibitor, the port that
263           Exhibitor is listening on. IMPORTANT: if this value is not set it implies
264           that Exhibitor is not being used on the ZooKeeper shared config.
265         '';
266       };
267       zkConfigPollMs = mkOption {
268         type = types.int;
269         description = lib.mdDoc ''
270           The period in ms to check for changes in the config ensemble
271         '';
272         default = 10000;
273       };
274       zkConfigRetry = {
275         sleepMs = mkOption {
276           type = types.int;
277           default = 1000;
278           description = lib.mdDoc ''
279             Retry sleep time connecting to the ZooKeeper config
280           '';
281         };
282         retryQuantity = mkOption {
283           type = types.int;
284           default = 3;
285           description = lib.mdDoc ''
286             Retries connecting to the ZooKeeper config
287           '';
288         };
289       };
290       zkConfigZPath = mkOption {
291         type = types.str;
292         description = lib.mdDoc ''
293           The base ZPath that Exhibitor should use
294         '';
295         example = "/exhibitor/config";
296       };
298       # Config options for s3 configType
299       s3Config = {
300         bucketName = mkOption {
301           type = types.str;
302           description = lib.mdDoc ''
303             Bucket name to store config
304           '';
305         };
306         objectKey = mkOption {
307           type = types.str;
308           description = lib.mdDoc ''
309             S3 key name to store the config
310           '';
311         };
312         configPrefix = mkOption {
313           type = types.str;
314           description = lib.mdDoc ''
315             When using AWS S3 shared config files, the prefix to use for values such as locks
316           '';
317           default = "exhibitor-";
318         };
319       };
321       # The next two are used for either s3backup or s3 configType
322       s3Credentials = mkOption {
323         type = types.nullOr types.path;
324         description = lib.mdDoc ''
325           Optional credentials to use for s3backup or s3config. Argument is the path
326           to an AWS credential properties file with two properties:
327           com.netflix.exhibitor.s3.access-key-id and com.netflix.exhibitor.s3.access-secret-key
328         '';
329         default = null;
330       };
331       s3Region = mkOption {
332         type = types.nullOr types.str;
333         description = lib.mdDoc ''
334           Optional region for S3 calls
335         '';
336         default = null;
337       };
339       # Config options for file config type
340       fsConfigDir = mkOption {
341         type = types.path;
342         description = lib.mdDoc ''
343           Directory to store Exhibitor properties (cannot be used with s3config).
344           Exhibitor uses file system locks so you can specify a shared location
345           so as to enable complete ensemble management.
346         '';
347       };
348       fsConfigLockPrefix = mkOption {
349         type = types.str;
350         description = lib.mdDoc ''
351           A prefix for a locking mechanism used in conjunction with fsconfigdir
352         '';
353         default = "exhibitor-lock-";
354       };
355       fsConfigName = mkOption {
356         type = types.str;
357         description = lib.mdDoc ''
358           The name of the file to store config in
359         '';
360         default = "exhibitor.properties";
361       };
362     };
363   };
365   config = mkIf cfg.enable {
366     systemd.services.exhibitor = {
367       description = "Exhibitor Daemon";
368       wantedBy = [ "multi-user.target" ];
369       after = [ "network.target" ];
370       environment = {
371         ZOO_LOG_DIR = cfg.baseDir;
372       };
373       serviceConfig = {
374         /***
375           Exhibitor is a bit un-nixy. It wants to present to you a user interface in order to
376           mutate the configuration of both itself and ZooKeeper, and to coordinate changes
377           among the members of the Zookeeper ensemble. I'm going for a different approach here,
378           which is to manage all the configuration via nix and have it write out the configuration
379           files that exhibitor will use, and to reduce the amount of inter-exhibitor orchestration.
380         ***/
381         ExecStart = ''
382           ${pkgs.exhibitor}/bin/startExhibitor.sh ${cliOptions}
383         '';
384         User = "zookeeper";
385         PermissionsStartOnly = true;
386       };
387       # This is a bit wonky, but the reason for this is that Exhibitor tries to write to
388       # ${cfg.baseDir}/zookeeper/bin/../conf/zoo.cfg
389       # I want everything but the conf directory to be in the immutable nix store, and I want defaults
390       # from the nix store
391       # If I symlink the bin directory in, then bin/../ will resolve to the parent of the symlink in the
392       # immutable nix store. Bind mounting a writable conf over the existing conf might work, but it gets very
393       # messy with trying to copy the existing out into a mutable store.
394       # Another option is to try to patch upstream exhibitor, but the current package just pulls down the
395       # prebuild JARs off of Maven, rather than building them ourselves, as Maven support in Nix isn't
396       # very mature. So, it seems like a reasonable compromise is to just copy out of the immutable store
397       # just before starting the service, so we're running binaries from the immutable store, but we work around
398       # Exhibitor's desire to mutate its current installation.
399       preStart = ''
400         mkdir -m 0700 -p ${cfg.baseDir}/zookeeper
401         # Not doing a chown -R to keep the base ZK files owned by root
402         chown zookeeper ${cfg.baseDir} ${cfg.baseDir}/zookeeper
403         cp -Rf ${pkgs.zookeeper}/* ${cfg.baseDir}/zookeeper
404         chown -R zookeeper ${cfg.baseDir}/zookeeper/conf
405         chmod -R u+w ${cfg.baseDir}/zookeeper/conf
406         replace_what=$(echo ${pkgs.zookeeper} | sed 's/[\/&]/\\&/g')
407         replace_with=$(echo ${cfg.baseDir}/zookeeper | sed 's/[\/&]/\\&/g')
408         sed -i 's/'"$replace_what"'/'"$replace_with"'/g' ${cfg.baseDir}/zookeeper/bin/zk*.sh
409       '';
410     };
411     users.users.zookeeper = {
412       uid = config.ids.uids.zookeeper;
413       description = "Zookeeper daemon user";
414       home = cfg.baseDir;
415     };
416   };