diff --git a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml index 0afeefa3b86d..29aa70fd6165 100644 --- a/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml +++ b/nixos/doc/manual/from_md/release-notes/rl-2205.section.xml @@ -168,6 +168,13 @@ services.baget. + + + moosefs, fault + tolerant petabyte distributed file system. Available as + moosefs. + + prosody-filer, diff --git a/nixos/doc/manual/release-notes/rl-2205.section.md b/nixos/doc/manual/release-notes/rl-2205.section.md index 7813d4abd3ee..c4ace1366f2f 100644 --- a/nixos/doc/manual/release-notes/rl-2205.section.md +++ b/nixos/doc/manual/release-notes/rl-2205.section.md @@ -51,6 +51,9 @@ In addition to numerous new and upgraded packages, this release has the followin - [BaGet](https://loic-sharma.github.io/BaGet/), a lightweight NuGet and symbol server. Available at [services.baget](#opt-services.baget.enable). +- [moosefs](https://moosefs.com), fault tolerant petabyte distributed file system. + Available as [moosefs](#opt-services.moosefs). + - [prosody-filer](https://github.com/ThomasLeister/prosody-filer), a server for handling XMPP HTTP Upload requests. Available at [services.prosody-filer](#opt-services.prosody-filer.enable). - [ethercalc](https://github.com/audreyt/ethercalc), an online collaborative diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 9b95a97e1c7b..28724d1e85d3 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -682,6 +682,7 @@ ./services/network-filesystems/litestream/default.nix ./services/network-filesystems/netatalk.nix ./services/network-filesystems/nfsd.nix + ./services/network-filesystems/moosefs.nix ./services/network-filesystems/openafs/client.nix ./services/network-filesystems/openafs/server.nix ./services/network-filesystems/orangefs/server.nix diff --git a/nixos/modules/services/network-filesystems/moosefs.nix b/nixos/modules/services/network-filesystems/moosefs.nix new file mode 100644 index 000000000000..88b2ada37e75 --- /dev/null +++ b/nixos/modules/services/network-filesystems/moosefs.nix @@ -0,0 +1,249 @@ +{ config, lib, pkgs, ... }: + +with lib; + +let + cfg = config.services.moosefs; + + mfsUser = if cfg.runAsUser then "moosefs" else "root"; + + settingsFormat = let + listSep = " "; + allowedTypes = with types; [ bool int float str ]; + valueToString = val: + if isList val then concatStringsSep listSep (map (x: valueToString x) val) + else if isBool val then (if val then "1" else "0") + else toString val; + + in { + type = with types; let + valueType = oneOf ([ + (listOf valueType) + ] ++ allowedTypes) // { + description = "Flat key-value file"; + }; + in attrsOf valueType; + + generate = name: value: + pkgs.writeText name ( lib.concatStringsSep "\n" ( + lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value )); + }; + + + initTool = pkgs.writeShellScriptBin "mfsmaster-init" '' + if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.mfs ]; then + cp ${pkgs.moosefs}/var/mfs/metadata.mfs.empty ${cfg.master.settings.DATA_PATH} + chmod +w ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty + ${pkgs.moosefs}/bin/mfsmaster -a -c ${masterCfg} start + ${pkgs.moosefs}/bin/mfsmaster -c ${masterCfg} stop + rm ${cfg.master.settings.DATA_PATH}/metadata.mfs.empty + fi + ''; + + # master config file + masterCfg = settingsFormat.generate + "mfsmaster.cfg" cfg.master.settings; + + # metalogger config file + metaloggerCfg = settingsFormat.generate + "mfsmetalogger.cfg" cfg.metalogger.settings; + + # chunkserver config file + chunkserverCfg = settingsFormat.generate + "mfschunkserver.cfg" cfg.chunkserver.settings; + + # generic template for all deamons + systemdService = name: extraConfig: configFile: { + wantedBy = [ "multi-user.target" ]; + wants = [ "network-online.target" ]; + after = [ "network.target" "network-online.target" ]; + + serviceConfig = { + Type = "forking"; + ExecStart = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} start"; + ExecStop = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} stop"; + ExecReload = "${pkgs.moosefs}/bin/mfs${name} -c ${configFile} reload"; + PIDFile = "${cfg."${name}".settings.DATA_PATH}/.mfs${name}.lock"; + } // extraConfig; + }; + +in { + ###### interface + + options = { + services.moosefs = { + masterHost = mkOption { + type = types.str; + default = null; + description = "IP or DNS name of master host."; + }; + + runAsUser = mkOption { + type = types.bool; + default = true; + example = true; + description = "Run daemons as user moosefs instead of root."; + }; + + client.enable = mkEnableOption "Moosefs client."; + + master = { + enable = mkOption { + type = types.bool; + description = '' + Enable Moosefs master daemon. + + You need to run mfsmaster-init on a freshly installed master server to + initialize the DATA_PATH direcory. + ''; + default = false; + }; + + exports = mkOption { + type = with types; listOf str; + default = null; + description = "Paths to export (see mfsexports.cfg)."; + example = [ + "* / rw,alldirs,admin,maproot=0:0" + "* . rw" + ]; + }; + + openFirewall = mkOption { + type = types.bool; + description = "Whether to automatically open the necessary ports in the firewall."; + default = false; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = mkOption { + type = types.str; + default = "/var/lib/mfs"; + description = "Data storage directory."; + }; + }; + + description = "Contents of config file (mfsmaster.cfg)."; + }; + }; + + metalogger = { + enable = mkEnableOption "Moosefs metalogger daemon."; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = mkOption { + type = types.str; + default = "/var/lib/mfs"; + description = "Data storage directory"; + }; + }; + + description = "Contents of metalogger config file (mfsmetalogger.cfg)."; + }; + }; + + chunkserver = { + enable = mkEnableOption "Moosefs chunkserver daemon."; + + openFirewall = mkOption { + type = types.bool; + description = "Whether to automatically open the necessary ports in the firewall."; + default = false; + }; + + hdds = mkOption { + type = with types; listOf str; + default = null; + description = "Mount points to be used by chunkserver for storage (see mfshdd.cfg)."; + example = [ "/mnt/hdd1" ]; + }; + + settings = mkOption { + type = types.submodule { + freeformType = settingsFormat.type; + + options.DATA_PATH = mkOption { + type = types.str; + default = "/var/lib/mfs"; + description = "Directory for lock file."; + }; + }; + + description = "Contents of chunkserver config file (mfschunkserver.cfg)."; + }; + }; + }; + }; + + ###### implementation + + config = mkIf ( cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) { + + warnings = [ ( mkIf (!cfg.runAsUser) "Running moosefs services as root is not recommended.") ]; + + # Service settings + services.moosefs = { + master.settings = mkIf cfg.master.enable { + WORKING_USER = mfsUser; + EXPORTS_FILENAME = toString ( pkgs.writeText "mfsexports.cfg" + (concatStringsSep "\n" cfg.master.exports)); + }; + + metalogger.settings = mkIf cfg.metalogger.enable { + WORKING_USER = mfsUser; + MASTER_HOST = cfg.masterHost; + }; + + chunkserver.settings = mkIf cfg.chunkserver.enable { + WORKING_USER = mfsUser; + MASTER_HOST = cfg.masterHost; + HDD_CONF_FILENAME = toString ( pkgs.writeText "mfshdd.cfg" + (concatStringsSep "\n" cfg.chunkserver.hdds)); + }; + }; + + # Create system user account for daemons + users = mkIf ( cfg.runAsUser && ( cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable ) ) { + users.moosefs = { + isSystemUser = true; + description = "moosefs daemon user"; + group = "moosefs"; + }; + groups.moosefs = {}; + }; + + environment.systemPackages = + (lib.optional cfg.client.enable pkgs.moosefs) ++ + (lib.optional cfg.master.enable initTool); + + networking.firewall.allowedTCPPorts = + (lib.optionals cfg.master.openFirewall [ 9419 9420 9421 ]) ++ + (lib.optional cfg.chunkserver.openFirewall 9422); + + # Ensure storage directories exist + systemd.tmpfiles.rules = + optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}" + ++ optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}" + ++ optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${mfsUser} ${mfsUser}"; + + # Service definitions + systemd.services.mfs-master = mkIf cfg.master.enable + ( systemdService "master" { + TimeoutStartSec = 1800; + TimeoutStopSec = 1800; + Restart = "no"; + } masterCfg ); + + systemd.services.mfs-metalogger = mkIf cfg.metalogger.enable + ( systemdService "metalogger" { Restart = "on-abnormal"; } metaloggerCfg ); + + systemd.services.mfs-chunkserver = mkIf cfg.chunkserver.enable + ( systemdService "chunkserver" { Restart = "on-abnormal"; } chunkserverCfg ); + }; +} diff --git a/nixos/tests/all-tests.nix b/nixos/tests/all-tests.nix index 1077dfab27c8..c01a28f04f89 100644 --- a/nixos/tests/all-tests.nix +++ b/nixos/tests/all-tests.nix @@ -305,6 +305,7 @@ in moodle = handleTest ./moodle.nix {}; morty = handleTest ./morty.nix {}; mosquitto = handleTest ./mosquitto.nix {}; + moosefs = handleTest ./moosefs.nix {}; mpd = handleTest ./mpd.nix {}; mpv = handleTest ./mpv.nix {}; mumble = handleTest ./mumble.nix {}; diff --git a/nixos/tests/moosefs.nix b/nixos/tests/moosefs.nix new file mode 100644 index 000000000000..0dc08748b828 --- /dev/null +++ b/nixos/tests/moosefs.nix @@ -0,0 +1,89 @@ +import ./make-test-python.nix ({ pkgs, ... } : + +let + master = { pkgs, ... } : { + # data base is stored in memory + # server crashes with default memory size + virtualisation.memorySize = 1024; + + services.moosefs.master = { + enable = true; + openFirewall = true; + exports = [ + "* / rw,alldirs,admin,maproot=0:0" + "* . rw" + ]; + }; + }; + + chunkserver = { pkgs, ... } : { + virtualisation.emptyDiskImages = [ 4096 ]; + boot.initrd.postDeviceCommands = '' + ${pkgs.e2fsprogs}/bin/mkfs.ext4 -L data /dev/vdb + ''; + + fileSystems = pkgs.lib.mkVMOverride { + "/data" = { + device = "/dev/disk/by-label/data"; + fsType = "ext4"; + }; + }; + + services.moosefs = { + masterHost = "master"; + chunkserver = { + openFirewall = true; + enable = true; + hdds = [ "~/data" ]; + }; + }; + }; + + metalogger = { pkgs, ... } : { + services.moosefs = { + masterHost = "master"; + metalogger.enable = true; + }; + }; + + client = { pkgs, ... } : { + services.moosefs.client.enable = true; + }; + +in { + name = "moosefs"; + + nodes= { + inherit master; + inherit metalogger; + chunkserver1 = chunkserver; + chunkserver2 = chunkserver; + client1 = client; + client2 = client; + }; + + testScript = '' + # prepare master server + master.start() + master.wait_for_unit("multi-user.target") + master.succeed("mfsmaster-init") + master.succeed("systemctl restart mfs-master") + master.wait_for_unit("mfs-master.service") + + metalogger.wait_for_unit("mfs-metalogger.service") + + for chunkserver in [chunkserver1, chunkserver2]: + chunkserver.wait_for_unit("multi-user.target") + chunkserver.succeed("chown moosefs:moosefs /data") + chunkserver.succeed("systemctl restart mfs-chunkserver") + chunkserver.wait_for_unit("mfs-chunkserver.service") + + for client in [client1, client2]: + client.wait_for_unit("multi-user.target") + client.succeed("mkdir /moosefs") + client.succeed("mount -t moosefs master:/ /moosefs") + + client1.succeed("echo test > /moosefs/file") + client2.succeed("grep test /moosefs/file") + ''; +})