nixos/saunafs: add module + test
This commit is contained in:
parent
b44c8b4c5f
commit
d22d60f3ac
@ -966,6 +966,7 @@
|
||||
./services/network-filesystems/rsyncd.nix
|
||||
./services/network-filesystems/samba-wsdd.nix
|
||||
./services/network-filesystems/samba.nix
|
||||
./services/network-filesystems/saunafs.nix
|
||||
./services/network-filesystems/tahoe.nix
|
||||
./services/network-filesystems/u9fs.nix
|
||||
./services/network-filesystems/webdav-server-rs.nix
|
||||
|
287
nixos/modules/services/network-filesystems/saunafs.nix
Normal file
287
nixos/modules/services/network-filesystems/saunafs.nix
Normal file
@ -0,0 +1,287 @@
|
||||
{
|
||||
config,
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
cfg = config.services.saunafs;
|
||||
|
||||
settingsFormat =
|
||||
let
|
||||
listSep = " ";
|
||||
allowedTypes = with lib.types; [
|
||||
bool
|
||||
int
|
||||
float
|
||||
str
|
||||
];
|
||||
valueToString =
|
||||
val:
|
||||
if lib.isList val then
|
||||
lib.concatStringsSep listSep (map (x: valueToString x) val)
|
||||
else if lib.isBool val then
|
||||
(if val then "1" else "0")
|
||||
else
|
||||
toString val;
|
||||
|
||||
in
|
||||
{
|
||||
type =
|
||||
let
|
||||
valueType =
|
||||
lib.types.oneOf (
|
||||
[
|
||||
(lib.types.listOf valueType)
|
||||
]
|
||||
++ allowedTypes
|
||||
)
|
||||
// {
|
||||
description = "Flat key-value file";
|
||||
};
|
||||
in
|
||||
lib.types.attrsOf valueType;
|
||||
|
||||
generate =
|
||||
name: value:
|
||||
pkgs.writeText name (
|
||||
lib.concatStringsSep "\n" (lib.mapAttrsToList (key: val: "${key} = ${valueToString val}") value)
|
||||
);
|
||||
};
|
||||
|
||||
initTool = pkgs.writeShellScriptBin "sfsmaster-init" ''
|
||||
if [ ! -e ${cfg.master.settings.DATA_PATH}/metadata.sfs ]; then
|
||||
cp --update=none ${pkgs.saunafs}/var/lib/saunafs/metadata.sfs.empty ${cfg.master.settings.DATA_PATH}/metadata.sfs
|
||||
chmod +w ${cfg.master.settings.DATA_PATH}/metadata.sfs
|
||||
fi
|
||||
'';
|
||||
|
||||
# master config file
|
||||
masterCfg = settingsFormat.generate "sfsmaster.cfg" cfg.master.settings;
|
||||
|
||||
# metalogger config file
|
||||
metaloggerCfg = settingsFormat.generate "sfsmetalogger.cfg" cfg.metalogger.settings;
|
||||
|
||||
# chunkserver config file
|
||||
chunkserverCfg = settingsFormat.generate "sfschunkserver.cfg" cfg.chunkserver.settings;
|
||||
|
||||
# generic template for all daemons
|
||||
systemdService = name: extraConfig: configFile: {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
wants = [ "network-online.target" ];
|
||||
after = [
|
||||
"network.target"
|
||||
"network-online.target"
|
||||
];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "forking";
|
||||
ExecStart = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} start";
|
||||
ExecStop = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} stop";
|
||||
ExecReload = "${pkgs.saunafs}/bin/sfs${name} -c ${configFile} reload";
|
||||
} // extraConfig;
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
###### interface
|
||||
|
||||
options = {
|
||||
services.saunafs = {
|
||||
masterHost = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = null;
|
||||
description = "IP or hostname name of master host.";
|
||||
};
|
||||
|
||||
sfsUser = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "saunafs";
|
||||
description = "Run daemons as user.";
|
||||
};
|
||||
|
||||
client.enable = lib.mkEnableOption "Saunafs client";
|
||||
|
||||
master = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable Saunafs master daemon.
|
||||
|
||||
You need to run `sfsmaster-init` on a freshly installed master server to
|
||||
initialize the `DATA_PATH` directory.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
|
||||
exports = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = null;
|
||||
description = "Paths to exports file (see {manpage}`sfsexports.cfg(5)`).";
|
||||
example = lib.literalExpression ''
|
||||
[ "* / rw,alldirs,admin,maproot=0:0" ];
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Whether to automatically open the necessary ports in the firewall.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.DATA_PATH = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/saunafs/master";
|
||||
description = "Data storage directory.";
|
||||
};
|
||||
};
|
||||
|
||||
description = "Contents of config file ({manpage}`sfsmaster.cfg(5)`).";
|
||||
};
|
||||
};
|
||||
|
||||
metalogger = {
|
||||
enable = lib.mkEnableOption "Saunafs metalogger daemon";
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.DATA_PATH = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/saunafs/metalogger";
|
||||
description = "Data storage directory";
|
||||
};
|
||||
};
|
||||
|
||||
description = "Contents of metalogger config file (see {manpage}`sfsmetalogger.cfg(5)`).";
|
||||
};
|
||||
};
|
||||
|
||||
chunkserver = {
|
||||
enable = lib.mkEnableOption "Saunafs chunkserver daemon";
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = "Whether to automatically open the necessary ports in the firewall.";
|
||||
default = false;
|
||||
};
|
||||
|
||||
hdds = lib.mkOption {
|
||||
type = with lib.types; listOf str;
|
||||
default = null;
|
||||
|
||||
example = lib.literalExpression ''
|
||||
[ "/mnt/hdd1" ];
|
||||
'';
|
||||
|
||||
description = ''
|
||||
Mount points to be used by chunkserver for storage (see {manpage}`sfshdd.cfg(5)`).
|
||||
|
||||
Note, that these mount points must writeable by the user defined by the saunafs user.
|
||||
'';
|
||||
};
|
||||
|
||||
settings = lib.mkOption {
|
||||
type = lib.types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
|
||||
options.DATA_PATH = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
default = "/var/lib/saunafs/chunkserver";
|
||||
description = "Directory for chunck meta data";
|
||||
};
|
||||
};
|
||||
|
||||
description = "Contents of chunkserver config file (see {manpage}`sfschunkserver.cfg(5)`).";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config =
|
||||
lib.mkIf (cfg.client.enable || cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable)
|
||||
{
|
||||
|
||||
warnings = [
|
||||
(lib.mkIf (cfg.sfsUser == "root") "Running saunafs services as root is not recommended.")
|
||||
];
|
||||
|
||||
# Service settings
|
||||
services.saunafs = {
|
||||
master.settings = lib.mkIf cfg.master.enable {
|
||||
WORKING_USER = cfg.sfsUser;
|
||||
EXPORTS_FILENAME = toString (
|
||||
pkgs.writeText "sfsexports.cfg" (lib.concatStringsSep "\n" cfg.master.exports)
|
||||
);
|
||||
};
|
||||
|
||||
metalogger.settings = lib.mkIf cfg.metalogger.enable {
|
||||
WORKING_USER = cfg.sfsUser;
|
||||
MASTER_HOST = cfg.masterHost;
|
||||
};
|
||||
|
||||
chunkserver.settings = lib.mkIf cfg.chunkserver.enable {
|
||||
WORKING_USER = cfg.sfsUser;
|
||||
MASTER_HOST = cfg.masterHost;
|
||||
HDD_CONF_FILENAME = toString (
|
||||
pkgs.writeText "sfshdd.cfg" (lib.concatStringsSep "\n" cfg.chunkserver.hdds)
|
||||
);
|
||||
};
|
||||
};
|
||||
|
||||
# Create system user account for daemons
|
||||
users =
|
||||
lib.mkIf
|
||||
(cfg.sfsUser != "root" && (cfg.master.enable || cfg.metalogger.enable || cfg.chunkserver.enable))
|
||||
{
|
||||
users."${cfg.sfsUser}" = {
|
||||
isSystemUser = true;
|
||||
description = "saunafs daemon user";
|
||||
group = "saunafs";
|
||||
};
|
||||
groups."${cfg.sfsUser}" = { };
|
||||
};
|
||||
|
||||
environment.systemPackages =
|
||||
(lib.optional cfg.client.enable pkgs.saunafs) ++ (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 =
|
||||
lib.optional cfg.master.enable "d ${cfg.master.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -"
|
||||
++ lib.optional cfg.metalogger.enable "d ${cfg.metalogger.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -"
|
||||
++ lib.optional cfg.chunkserver.enable "d ${cfg.chunkserver.settings.DATA_PATH} 0700 ${cfg.sfsUser} ${cfg.sfsUser} -";
|
||||
|
||||
# Service definitions
|
||||
systemd.services.sfs-master = lib.mkIf cfg.master.enable (
|
||||
systemdService "master" {
|
||||
TimeoutStartSec = 1800;
|
||||
TimeoutStopSec = 1800;
|
||||
Restart = "no";
|
||||
} masterCfg
|
||||
);
|
||||
|
||||
systemd.services.sfs-metalogger = lib.mkIf cfg.metalogger.enable (
|
||||
systemdService "metalogger" { Restart = "on-abort"; } metaloggerCfg
|
||||
);
|
||||
|
||||
systemd.services.sfs-chunkserver = lib.mkIf cfg.chunkserver.enable (
|
||||
systemdService "chunkserver" { Restart = "on-abort"; } chunkserverCfg
|
||||
);
|
||||
};
|
||||
}
|
@ -889,6 +889,7 @@ in {
|
||||
samba-wsdd = handleTest ./samba-wsdd.nix {};
|
||||
sane = handleTest ./sane.nix {};
|
||||
sanoid = handleTest ./sanoid.nix {};
|
||||
saunafs = handleTest ./saunafs.nix {};
|
||||
scaphandre = handleTest ./scaphandre.nix {};
|
||||
schleuder = handleTest ./schleuder.nix {};
|
||||
scion-freestanding-deployment = handleTest ./scion/freestanding-deployment {};
|
||||
|
122
nixos/tests/saunafs.nix
Normal file
122
nixos/tests/saunafs.nix
Normal file
@ -0,0 +1,122 @@
|
||||
import ./make-test-python.nix (
|
||||
{ pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
master =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
# data base is stored in memory
|
||||
# server may crash with default memory size
|
||||
virtualisation.memorySize = 1024;
|
||||
|
||||
services.saunafs.master = {
|
||||
enable = true;
|
||||
openFirewall = true;
|
||||
exports = [
|
||||
"* / rw,alldirs,maproot=0:0"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
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.saunafs = {
|
||||
masterHost = "master";
|
||||
chunkserver = {
|
||||
openFirewall = true;
|
||||
enable = true;
|
||||
hdds = [ "/data" ];
|
||||
|
||||
# The test image is too small and gets set to "full"
|
||||
settings.HDD_LEAVE_SPACE_DEFAULT = "100M";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
metalogger =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.saunafs = {
|
||||
masterHost = "master";
|
||||
metalogger.enable = true;
|
||||
};
|
||||
};
|
||||
|
||||
client =
|
||||
{ pkgs, lib, ... }:
|
||||
{
|
||||
services.saunafs.client.enable = true;
|
||||
# systemd.tmpfiles.rules = [ "d /sfs 755 root root -" ];
|
||||
systemd.network.enable = true;
|
||||
|
||||
# Use networkd to have properly functioning
|
||||
# network-online.target
|
||||
networking = {
|
||||
useDHCP = false;
|
||||
useNetworkd = true;
|
||||
};
|
||||
|
||||
systemd.mounts = [
|
||||
{
|
||||
requires = [ "network-online.target" ];
|
||||
after = [ "network-online.target" ];
|
||||
wantedBy = [ "remote-fs.target" ];
|
||||
type = "saunafs";
|
||||
what = "master:/";
|
||||
where = "/sfs";
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
in
|
||||
{
|
||||
name = "saunafs";
|
||||
|
||||
meta.maintainers = [ lib.maintainers.markuskowa ];
|
||||
|
||||
nodes = {
|
||||
inherit master metalogger;
|
||||
chunkserver1 = chunkserver;
|
||||
chunkserver2 = chunkserver;
|
||||
client1 = client;
|
||||
client2 = client;
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
# prepare master server
|
||||
master.start()
|
||||
master.wait_for_unit("multi-user.target")
|
||||
master.succeed("sfsmaster-init")
|
||||
master.succeed("systemctl restart sfs-master")
|
||||
master.wait_for_unit("sfs-master.service")
|
||||
|
||||
metalogger.wait_for_unit("sfs-metalogger.service")
|
||||
|
||||
# Setup chunkservers
|
||||
for chunkserver in [chunkserver1, chunkserver2]:
|
||||
chunkserver.wait_for_unit("multi-user.target")
|
||||
chunkserver.succeed("chown saunafs:saunafs /data")
|
||||
chunkserver.succeed("systemctl restart sfs-chunkserver")
|
||||
chunkserver.wait_for_unit("sfs-chunkserver.service")
|
||||
|
||||
for client in [client1, client2]:
|
||||
client.wait_for_unit("multi-user.target")
|
||||
|
||||
client1.succeed("echo test > /sfs/file")
|
||||
client2.succeed("grep test /sfs/file")
|
||||
'';
|
||||
}
|
||||
)
|
Loading…
Reference in New Issue
Block a user