nixos/preload: init
[NixPkgs.git] / nixos / modules / services / network-filesystems / ceph.nix
blobaad03728b203d4fba454f30744800fb494e0483b
1 { config, lib, pkgs, ... }:
3 with lib;
5 let
6   cfg = config.services.ceph;
8   # function that translates "camelCaseOptions" to "camel case options", credits to tilpner in #nixos@freenode
9   expandCamelCase = replaceStrings upperChars (map (s: " ${s}") lowerChars);
10   expandCamelCaseAttrs = mapAttrs' (name: value: nameValuePair (expandCamelCase name) value);
12   makeServices = daemonType: daemonIds:
13     mkMerge (map (daemonId:
14       { "ceph-${daemonType}-${daemonId}" = makeService daemonType daemonId cfg.global.clusterName cfg.${daemonType}.package; })
15       daemonIds);
17   makeService = daemonType: daemonId: clusterName: ceph:
18     let
19       stateDirectory = "ceph/${if daemonType == "rgw" then "radosgw" else daemonType}/${clusterName}-${daemonId}"; in {
20     enable = true;
21     description = "Ceph ${builtins.replaceStrings lowerChars upperChars daemonType} daemon ${daemonId}";
22     after = [ "network-online.target" "time-sync.target" ] ++ optional (daemonType == "osd") "ceph-mon.target";
23     wants = [ "network-online.target" "time-sync.target" ];
24     partOf = [ "ceph-${daemonType}.target" ];
25     wantedBy = [ "ceph-${daemonType}.target" ];
27     path = [ pkgs.getopt ];
29     # Don't start services that are not yet initialized
30     unitConfig.ConditionPathExists = "/var/lib/${stateDirectory}/keyring";
31     startLimitBurst =
32       if daemonType == "osd" then 30 else if lib.elem daemonType ["mgr" "mds"] then 3 else 5;
33     startLimitIntervalSec = 60 * 30;  # 30 mins
35     serviceConfig = {
36       LimitNOFILE = 1048576;
37       LimitNPROC = 1048576;
38       Environment = "CLUSTER=${clusterName}";
39       ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
40       PrivateDevices = "yes";
41       PrivateTmp = "true";
42       ProtectHome = "true";
43       ProtectSystem = "full";
44       Restart = "on-failure";
45       StateDirectory = stateDirectory;
46       User = "ceph";
47       Group = if daemonType == "osd" then "disk" else "ceph";
48       ExecStart = ''${ceph.out}/bin/${if daemonType == "rgw" then "radosgw" else "ceph-${daemonType}"} \
49                     -f --cluster ${clusterName} --id ${daemonId}'';
50     } // optionalAttrs (daemonType == "osd") {
51       ExecStartPre = "${ceph.lib}/libexec/ceph/ceph-osd-prestart.sh --id ${daemonId} --cluster ${clusterName}";
52       RestartSec = "20s";
53       PrivateDevices = "no"; # osd needs disk access
54     } // optionalAttrs ( daemonType == "mon") {
55       RestartSec = "10";
56     };
57   };
59   makeTarget = daemonType:
60     {
61       "ceph-${daemonType}" = {
62         description = "Ceph target allowing to start/stop all ceph-${daemonType} services at once";
63         partOf = [ "ceph.target" ];
64         wantedBy = [ "ceph.target" ];
65         before = [ "ceph.target" ];
66         unitConfig.StopWhenUnneeded = true;
67       };
68     };
71   options.services.ceph = {
72     # Ceph has a monolithic configuration file but different sections for
73     # each daemon, a separate client section and a global section
74     enable = mkEnableOption (lib.mdDoc "Ceph global configuration");
76     global = {
77       fsid = mkOption {
78         type = types.str;
79         example = ''
80           433a2193-4f8a-47a0-95d2-209d7ca2cca5
81         '';
82         description = lib.mdDoc ''
83           Filesystem ID, a generated uuid, its must be generated and set before
84           attempting to start a cluster
85         '';
86       };
88       clusterName = mkOption {
89         type = types.str;
90         default = "ceph";
91         description = lib.mdDoc ''
92           Name of cluster
93         '';
94       };
96       mgrModulePath = mkOption {
97         type = types.path;
98         default = "${pkgs.ceph.lib}/lib/ceph/mgr";
99         defaultText = literalExpression ''"''${pkgs.ceph.lib}/lib/ceph/mgr"'';
100         description = lib.mdDoc ''
101           Path at which to find ceph-mgr modules.
102         '';
103       };
105       monInitialMembers = mkOption {
106         type = with types; nullOr commas;
107         default = null;
108         example = ''
109           node0, node1, node2
110         '';
111         description = lib.mdDoc ''
112           List of hosts that will be used as monitors at startup.
113         '';
114       };
116       monHost = mkOption {
117         type = with types; nullOr commas;
118         default = null;
119         example = ''
120           10.10.0.1, 10.10.0.2, 10.10.0.3
121         '';
122         description = lib.mdDoc ''
123           List of hostname shortnames/IP addresses of the initial monitors.
124         '';
125       };
127       maxOpenFiles = mkOption {
128         type = types.int;
129         default = 131072;
130         description = lib.mdDoc ''
131           Max open files for each OSD daemon.
132         '';
133       };
135       authClusterRequired = mkOption {
136         type = types.enum [ "cephx" "none" ];
137         default = "cephx";
138         description = lib.mdDoc ''
139           Enables requiring daemons to authenticate with eachother in the cluster.
140         '';
141       };
143       authServiceRequired = mkOption {
144         type = types.enum [ "cephx" "none" ];
145         default = "cephx";
146         description = lib.mdDoc ''
147           Enables requiring clients to authenticate with the cluster to access services in the cluster (e.g. radosgw, mds or osd).
148         '';
149       };
151       authClientRequired = mkOption {
152         type = types.enum [ "cephx" "none" ];
153         default = "cephx";
154         description = lib.mdDoc ''
155           Enables requiring the cluster to authenticate itself to the client.
156         '';
157       };
159       publicNetwork = mkOption {
160         type = with types; nullOr commas;
161         default = null;
162         example = ''
163           10.20.0.0/24, 192.168.1.0/24
164         '';
165         description = lib.mdDoc ''
166           A comma-separated list of subnets that will be used as public networks in the cluster.
167         '';
168       };
170       clusterNetwork = mkOption {
171         type = with types; nullOr commas;
172         default = null;
173         example = ''
174           10.10.0.0/24, 192.168.0.0/24
175         '';
176         description = lib.mdDoc ''
177           A comma-separated list of subnets that will be used as cluster networks in the cluster.
178         '';
179       };
181       rgwMimeTypesFile = mkOption {
182         type = with types; nullOr path;
183         default = "${pkgs.mailcap}/etc/mime.types";
184         defaultText = literalExpression ''"''${pkgs.mailcap}/etc/mime.types"'';
185         description = lib.mdDoc ''
186           Path to mime types used by radosgw.
187         '';
188       };
189     };
191     extraConfig = mkOption {
192       type = with types; attrsOf str;
193       default = {};
194       example = {
195         "ms bind ipv6" = "true";
196       };
197       description = lib.mdDoc ''
198         Extra configuration to add to the global section. Use for setting values that are common for all daemons in the cluster.
199       '';
200     };
202     mgr = {
203       enable = mkEnableOption (lib.mdDoc "Ceph MGR daemon");
204       daemons = mkOption {
205         type = with types; listOf str;
206         default = [];
207         example = [ "name1" "name2" ];
208         description = lib.mdDoc ''
209           A list of names for manager daemons that should have a service created. The names correspond
210           to the id part in ceph i.e. [ "name1" ] would result in mgr.name1
211         '';
212       };
213       package = mkPackageOptionMD pkgs "ceph" { };
214       extraConfig = mkOption {
215         type = with types; attrsOf str;
216         default = {};
217         description = lib.mdDoc ''
218           Extra configuration to add to the global section for manager daemons.
219         '';
220       };
221     };
223     mon = {
224       enable = mkEnableOption (lib.mdDoc "Ceph MON daemon");
225       daemons = mkOption {
226         type = with types; listOf str;
227         default = [];
228         example = [ "name1" "name2" ];
229         description = lib.mdDoc ''
230           A list of monitor daemons that should have a service created. The names correspond
231           to the id part in ceph i.e. [ "name1" ] would result in mon.name1
232         '';
233       };
234       package = mkPackageOptionMD pkgs "ceph" { };
235       extraConfig = mkOption {
236         type = with types; attrsOf str;
237         default = {};
238         description = lib.mdDoc ''
239           Extra configuration to add to the monitor section.
240         '';
241       };
242     };
244     osd = {
245       enable = mkEnableOption (lib.mdDoc "Ceph OSD daemon");
246       daemons = mkOption {
247         type = with types; listOf str;
248         default = [];
249         example = [ "name1" "name2" ];
250         description = lib.mdDoc ''
251           A list of OSD daemons that should have a service created. The names correspond
252           to the id part in ceph i.e. [ "name1" ] would result in osd.name1
253         '';
254       };
255       package = mkPackageOptionMD pkgs "ceph" { };
256       extraConfig = mkOption {
257         type = with types; attrsOf str;
258         default = {
259           "osd journal size" = "10000";
260           "osd pool default size" = "3";
261           "osd pool default min size" = "2";
262           "osd pool default pg num" = "200";
263           "osd pool default pgp num" = "200";
264           "osd crush chooseleaf type" = "1";
265         };
266         description = lib.mdDoc ''
267           Extra configuration to add to the OSD section.
268         '';
269       };
270     };
272     mds = {
273       enable = mkEnableOption (lib.mdDoc "Ceph MDS daemon");
274       daemons = mkOption {
275         type = with types; listOf str;
276         default = [];
277         example = [ "name1" "name2" ];
278         description = lib.mdDoc ''
279           A list of metadata service daemons that should have a service created. The names correspond
280           to the id part in ceph i.e. [ "name1" ] would result in mds.name1
281         '';
282       };
283       package = mkPackageOptionMD pkgs "ceph" { };
284       extraConfig = mkOption {
285         type = with types; attrsOf str;
286         default = {};
287         description = lib.mdDoc ''
288           Extra configuration to add to the MDS section.
289         '';
290       };
291     };
293     rgw = {
294       enable = mkEnableOption (lib.mdDoc "Ceph RadosGW daemon");
295       package = mkPackageOptionMD pkgs "ceph" { };
296       daemons = mkOption {
297         type = with types; listOf str;
298         default = [];
299         example = [ "name1" "name2" ];
300         description = lib.mdDoc ''
301           A list of rados gateway daemons that should have a service created. The names correspond
302           to the id part in ceph i.e. [ "name1" ] would result in client.name1, radosgw daemons
303           aren't daemons to cluster in the sense that OSD, MGR or MON daemons are. They are simply
304           daemons, from ceph, that uses the cluster as a backend.
305         '';
306       };
307     };
309     client = {
310       enable = mkEnableOption (lib.mdDoc "Ceph client configuration");
311       extraConfig = mkOption {
312         type = with types; attrsOf (attrsOf str);
313         default = {};
314         example = literalExpression ''
315           {
316             # This would create a section for a radosgw daemon named node0 and related
317             # configuration for it
318             "client.radosgw.node0" = { "some config option" = "true"; };
319           };
320         '';
321         description = lib.mdDoc ''
322           Extra configuration to add to the client section. Configuration for rados gateways
323           would be added here, with their own sections, see example.
324         '';
325       };
326     };
327   };
329   config = mkIf config.services.ceph.enable {
330     assertions = [
331       { assertion = cfg.global.fsid != "";
332         message = "fsid has to be set to a valid uuid for the cluster to function";
333       }
334       { assertion = cfg.mon.enable -> cfg.mon.daemons != [];
335         message = "have to set id of atleast one MON if you're going to enable Monitor";
336       }
337       { assertion = cfg.mds.enable -> cfg.mds.daemons != [];
338         message = "have to set id of atleast one MDS if you're going to enable Metadata Service";
339       }
340       { assertion = cfg.osd.enable -> cfg.osd.daemons != [];
341         message = "have to set id of atleast one OSD if you're going to enable OSD";
342       }
343       { assertion = cfg.mgr.enable -> cfg.mgr.daemons != [];
344         message = "have to set id of atleast one MGR if you're going to enable MGR";
345       }
346     ];
348     warnings = optional (cfg.global.monInitialMembers == null)
349       "Not setting up a list of members in monInitialMembers requires that you set the host variable for each mon daemon or else the cluster won't function";
351     environment.etc."ceph/ceph.conf".text = let
352       # Merge the extraConfig set for mgr daemons, as mgr don't have their own section
353       globalSection = expandCamelCaseAttrs (cfg.global // cfg.extraConfig // optionalAttrs cfg.mgr.enable cfg.mgr.extraConfig);
354       # Remove all name-value pairs with null values from the attribute set to avoid making empty sections in the ceph.conf
355       globalSection' = filterAttrs (name: value: value != null) globalSection;
356       totalConfig = {
357           global = globalSection';
358         } // optionalAttrs (cfg.mon.enable && cfg.mon.extraConfig != {}) { mon = cfg.mon.extraConfig; }
359           // optionalAttrs (cfg.mds.enable && cfg.mds.extraConfig != {}) { mds = cfg.mds.extraConfig; }
360           // optionalAttrs (cfg.osd.enable && cfg.osd.extraConfig != {}) { osd = cfg.osd.extraConfig; }
361           // optionalAttrs (cfg.client.enable && cfg.client.extraConfig != {})  cfg.client.extraConfig;
362       in
363         generators.toINI {} totalConfig;
365     users.users.ceph = {
366       uid = config.ids.uids.ceph;
367       description = "Ceph daemon user";
368       group = "ceph";
369       extraGroups = [ "disk" ];
370     };
372     users.groups.ceph = {
373       gid = config.ids.gids.ceph;
374     };
376     systemd.services = let
377       services = []
378         ++ optional cfg.mon.enable (makeServices "mon" cfg.mon.daemons)
379         ++ optional cfg.mds.enable (makeServices "mds" cfg.mds.daemons)
380         ++ optional cfg.osd.enable (makeServices "osd" cfg.osd.daemons)
381         ++ optional cfg.rgw.enable (makeServices "rgw" cfg.rgw.daemons)
382         ++ optional cfg.mgr.enable (makeServices "mgr" cfg.mgr.daemons);
383       in
384         mkMerge services;
386     systemd.targets = let
387       targets = [
388         { ceph = {
389           description = "Ceph target allowing to start/stop all ceph service instances at once";
390           wantedBy = [ "multi-user.target" ];
391           unitConfig.StopWhenUnneeded = true;
392         }; } ]
393         ++ optional cfg.mon.enable (makeTarget "mon")
394         ++ optional cfg.mds.enable (makeTarget "mds")
395         ++ optional cfg.osd.enable (makeTarget "osd")
396         ++ optional cfg.rgw.enable (makeTarget "rgw")
397         ++ optional cfg.mgr.enable (makeTarget "mgr");
398       in
399         mkMerge targets;
401     systemd.tmpfiles.rules = [
402       "d /etc/ceph - ceph ceph - -"
403       "d /run/ceph 0770 ceph ceph -"
404       "d /var/lib/ceph - ceph ceph - -"]
405     ++ optionals cfg.mgr.enable [ "d /var/lib/ceph/mgr - ceph ceph - -"]
406     ++ optionals cfg.mon.enable [ "d /var/lib/ceph/mon - ceph ceph - -"]
407     ++ optionals cfg.osd.enable [ "d /var/lib/ceph/osd - ceph ceph - -"];
408   };