diff --git a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
index 8104f293e8b0b..700ad741c0036 100644
--- a/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
+++ b/nixos/doc/manual/from_md/release-notes/rl-2111.section.xml
@@ -344,6 +344,13 @@
controller support.
+
+
+ multipath,
+ the device mapper multipath (DM-MP) daemon. Available as
+ services.multipath.
+
+
diff --git a/nixos/doc/manual/release-notes/rl-2111.section.md b/nixos/doc/manual/release-notes/rl-2111.section.md
index 73a43be8c7415..ab1e94d59f18a 100644
--- a/nixos/doc/manual/release-notes/rl-2111.section.md
+++ b/nixos/doc/manual/release-notes/rl-2111.section.md
@@ -105,6 +105,8 @@ In addition to numerous new and upgraded packages, this release has the followin
- [joycond](https://github.com/DanielOgorchock/joycond), a service that uses `hid-nintendo` to provide nintendo joycond pairing and better nintendo switch pro controller support.
+- [multipath](https://github.com/opensvc/multipath-tools), the device mapper multipath (DM-MP) daemon. Available as [services.multipath](#opt-services.multipath.enable).
+
## Backward Incompatibilities {#sec-release-21.11-incompatibilities}
- The `services.wakeonlan` option was removed, and replaced with `networking.interfaces..wakeOnLan`.
diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix
index c45309bd6b69d..df35a57d047f7 100644
--- a/nixos/modules/module-list.nix
+++ b/nixos/modules/module-list.nix
@@ -779,6 +779,7 @@
./services/networking/mstpd.nix
./services/networking/mtprotoproxy.nix
./services/networking/mullvad-vpn.nix
+ ./services/networking/multipath.nix
./services/networking/murmur.nix
./services/networking/mxisd.nix
./services/networking/namecoind.nix
diff --git a/nixos/modules/services/networking/iscsi/root-initiator.nix b/nixos/modules/services/networking/iscsi/root-initiator.nix
index 3274878c4fae4..c12aca1bc24d7 100644
--- a/nixos/modules/services/networking/iscsi/root-initiator.nix
+++ b/nixos/modules/services/networking/iscsi/root-initiator.nix
@@ -64,6 +64,12 @@ in
default = false;
};
+ extraIscsiCommands = mkOption {
+ description = "Extra iscsi commands to run in the initrd.";
+ default = "";
+ type = lines;
+ };
+
extraConfig = mkOption {
description = "Extra lines to append to /etc/iscsid.conf";
default = null;
@@ -162,6 +168,9 @@ in
'' else ''
iscsiadm --mode node --targetname ${escapeShellArg cfg.target} --login
''}
+
+ ${cfg.extraIscsiCommands}
+
pkill -9 iscsid
'';
};
diff --git a/nixos/modules/services/networking/multipath.nix b/nixos/modules/services/networking/multipath.nix
new file mode 100644
index 0000000000000..1cc2ad1fc849c
--- /dev/null
+++ b/nixos/modules/services/networking/multipath.nix
@@ -0,0 +1,572 @@
+{ config, lib, pkgs, ... }: with lib;
+
+# See http://christophe.varoqui.free.fr/usage.html and
+# https://github.com/opensvc/multipath-tools/blob/master/multipath/multipath.conf.5
+
+let
+ cfg = config.services.multipath;
+
+ indentLines = n: str: concatStringsSep "\n" (
+ map (line: "${fixedWidthString n " " " "}${line}") (
+ filter ( x: x != "" ) ( splitString "\n" str )
+ )
+ );
+
+ addCheckDesc = desc: elemType: check: types.addCheck elemType check
+ // { description = "${elemType.description} (with check: ${desc})"; };
+ hexChars = stringToCharacters "0123456789abcdef";
+ isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s));
+ hexStr = addCheckDesc "hexadecimal string" types.str isHexString;
+
+in {
+
+ options.services.multipath = with types; {
+
+ enable = mkEnableOption "the device mapper multipath (DM-MP) daemon";
+
+ package = mkOption {
+ type = package;
+ description = "multipath-tools package to use";
+ default = pkgs.multipath-tools;
+ defaultText = "pkgs.multipath-tools";
+ };
+
+ devices = mkOption {
+ default = [ ];
+ example = literalExpression ''
+ [
+ {
+ vendor = "\"COMPELNT\"";
+ product = "\"Compellent Vol\"";
+ path_checker = "tur";
+ no_path_retry = "queue";
+ max_sectors_kb = 256;
+ }, ...
+ ]
+ '';
+ description = ''
+ This option allows you to define arrays for use in multipath
+ groups.
+ '';
+ type = listOf (submodule {
+ options = {
+
+ vendor = mkOption {
+ type = str;
+ example = "COMPELNT";
+ description = "Regular expression to match the vendor name";
+ };
+
+ product = mkOption {
+ type = str;
+ example = "Compellent Vol";
+ description = "Regular expression to match the product name";
+ };
+
+ revision = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Regular expression to match the product revision";
+ };
+
+ product_blacklist = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Products with the given vendor matching this string are blacklisted";
+ };
+
+ alias_prefix = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "The user_friendly_names prefix to use for this device type, instead of the default mpath";
+ };
+
+ vpd_vendor = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "The vendor specific vpd page information, using the vpd page abbreviation";
+ };
+
+ hardware_handler = mkOption {
+ type = nullOr (enum [ "emc" "rdac" "hp_sw" "alua" "ana" ]);
+ default = null;
+ description = "The hardware handler to use for this device type";
+ };
+
+ # Optional arguments
+ path_grouping_policy = mkOption {
+ type = nullOr (enum [ "failover" "multibus" "group_by_serial" "group_by_prio" "group_by_node_name" ]);
+ default = null; # real default: "failover"
+ description = "The default path grouping policy to apply to unspecified multipaths";
+ };
+
+ uid_attribute = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "The udev attribute providing a unique path identifier (WWID)";
+ };
+
+ getuid_callout = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ (Superseded by uid_attribute) The default program and args to callout
+ to obtain a unique path identifier. Should be specified with an absolute path.
+ '';
+ };
+
+ path_selector = mkOption {
+ type = nullOr (enum [
+ ''"round-robin 0"''
+ ''"queue-length 0"''
+ ''"service-time 0"''
+ ''"historical-service-time 0"''
+ ]);
+ default = null; # real default: "service-time 0"
+ description = "The default path selector algorithm to use; they are offered by the kernel multipath target";
+ };
+
+ path_checker = mkOption {
+ type = enum [ "readsector0" "tur" "emc_clariion" "hp_sw" "rdac" "directio" "cciss_tur" "none" ];
+ default = "tur";
+ description = "The default method used to determine the paths state";
+ };
+
+ prio = mkOption {
+ type = nullOr (enum [
+ "none" "const" "sysfs" "emc" "alua" "ontap" "rdac" "hp_sw" "hds"
+ "random" "weightedpath" "path_latency" "ana" "datacore" "iet"
+ ]);
+ default = null; # real default: "const"
+ description = "The name of the path priority routine";
+ };
+
+ prio_args = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Arguments to pass to to the prio function";
+ };
+
+ features = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Specify any device-mapper features to be used";
+ };
+
+ failback = mkOption {
+ type = nullOr str;
+ default = null; # real default: "manual"
+ description = "Tell multipathd how to manage path group failback. Quote integers as strings";
+ };
+
+ rr_weight = mkOption {
+ type = nullOr (enum [ "priorities" "uniform" ]);
+ default = null; # real default: "uniform"
+ description = ''
+ If set to priorities the multipath configurator will assign path weights
+ as "path prio * rr_min_io".
+ '';
+ };
+
+ no_path_retry = mkOption {
+ type = nullOr str;
+ default = null; # real default: "fail"
+ description = "Specify what to do when all paths are down. Quote integers as strings";
+ };
+
+ rr_min_io = mkOption {
+ type = nullOr int;
+ default = null; # real default: 1000
+ description = ''
+ Number of I/O requests to route to a path before switching to the next in the
+ same path group. This is only for Block I/O (BIO) based multipath and
+ only apply to round-robin path_selector.
+ '';
+ };
+
+ rr_min_io_rq = mkOption {
+ type = nullOr int;
+ default = null; # real default: 1
+ description = ''
+ Number of I/O requests to route to a path before switching to the next in the
+ same path group. This is only for Request based multipath and
+ only apply to round-robin path_selector.
+ '';
+ };
+
+ fast_io_fail_tmo = mkOption {
+ type = nullOr str;
+ default = null; # real default: 5
+ description = ''
+ Specify the number of seconds the SCSI layer will wait after a problem has been
+ detected on a FC remote port before failing I/O to devices on that remote port.
+ This should be smaller than dev_loss_tmo. Setting this to "off" will disable
+ the timeout. Quote integers as strings.
+ '';
+ };
+
+ dev_loss_tmo = mkOption {
+ type = nullOr str;
+ default = null; # real default: 600
+ description = ''
+ Specify the number of seconds the SCSI layer will wait after a problem has
+ been detected on a FC remote port before removing it from the system. This
+ can be set to "infinity" which sets it to the max value of 2147483647
+ seconds, or 68 years. It will be automatically adjusted to the overall
+ retry interval no_path_retry * polling_interval
+ if a number of retries is given with no_path_retry and the
+ overall retry interval is longer than the specified dev_loss_tmo value.
+ The Linux kernel will cap this value to 600 if fast_io_fail_tmo
+ is not set.
+ '';
+ };
+
+ flush_on_last_del = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "no"
+ description = ''
+ If set to "yes" multipathd will disable queueing when the last path to a
+ device has been deleted.
+ '';
+ };
+
+ user_friendly_names = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "no"
+ description = ''
+ If set to "yes", using the bindings file /etc/multipath/bindings
+ to assign a persistent and unique alias to the multipath, in the
+ form of mpath. If set to "no" use the WWID as the alias. In either
+ case this be will be overridden by any specific aliases in the
+ multipaths section.
+ '';
+ };
+
+ retain_attached_hw_handler = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "yes"
+ description = ''
+ (Obsolete for kernels >= 4.3) If set to "yes" and the SCSI layer has
+ already attached a hardware_handler to the device, multipath will not
+ force the device to use the hardware_handler specified by mutipath.conf.
+ If the SCSI layer has not attached a hardware handler, multipath will
+ continue to use its configured hardware handler.
+
+ Important Note: Linux kernel 4.3 or newer always behaves as if
+ "retain_attached_hw_handler yes" was set.
+ '';
+ };
+
+ detect_prio = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "yes"
+ description = ''
+ If set to "yes", multipath will try to detect if the device supports
+ SCSI-3 ALUA. If so, the device will automatically use the sysfs
+ prioritizer if the required sysf attributes access_state and
+ preferred_path are supported, or the alua prioritizer if not. If set
+ to "no", the prioritizer will be selected as usual.
+ '';
+ };
+
+ detect_checker = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "yes"
+ description = ''
+ If set to "yes", multipath will try to detect if the device supports
+ SCSI-3 ALUA. If so, the device will automatically use the tur checker.
+ If set to "no", the checker will be selected as usual.
+ '';
+ };
+
+ deferred_remove = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "no"
+ description = ''
+ If set to "yes", multipathd will do a deferred remove instead of a
+ regular remove when the last path device has been deleted. This means
+ that if the multipath device is still in use, it will be freed when
+ the last user closes it. If path is added to the multipath device
+ before the last user closes it, the deferred remove will be canceled.
+ '';
+ };
+
+ san_path_err_threshold = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ If set to a value greater than 0, multipathd will watch paths and check
+ how many times a path has been failed due to errors.If the number of
+ failures on a particular path is greater then the san_path_err_threshold,
+ then the path will not reinstate till san_path_err_recovery_time. These
+ path failures should occur within a san_path_err_forget_rate checks, if
+ not we will consider the path is good enough to reinstantate.
+ '';
+ };
+
+ san_path_err_forget_rate = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ If set to a value greater than 0, multipathd will check whether the path
+ failures has exceeded the san_path_err_threshold within this many checks
+ i.e san_path_err_forget_rate. If so we will not reinstante the path till
+ san_path_err_recovery_time.
+ '';
+ };
+
+ san_path_err_recovery_time = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ If set to a value greater than 0, multipathd will make sure that when
+ path failures has exceeded the san_path_err_threshold within
+ san_path_err_forget_rate then the path will be placed in failed state
+ for san_path_err_recovery_time duration. Once san_path_err_recovery_time
+ has timeout we will reinstante the failed path. san_path_err_recovery_time
+ value should be in secs.
+ '';
+ };
+
+ marginal_path_err_sample_time = mkOption {
+ type = nullOr int;
+ default = null;
+ description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+ };
+
+ marginal_path_err_rate_threshold = mkOption {
+ type = nullOr int;
+ default = null;
+ description = "The error rate threshold as a permillage (1/1000)";
+ };
+
+ marginal_path_err_recheck_gap_time = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+ };
+
+ marginal_path_double_failed_time = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error";
+ };
+
+ delay_watch_checks = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "This option is deprecated, and mapped to san_path_err_forget_rate";
+ };
+
+ delay_wait_checks = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "This option is deprecated, and mapped to san_path_err_recovery_time";
+ };
+
+ skip_kpartx = mkOption {
+ type = nullOr (enum [ "yes" "no" ]);
+ default = null; # real default: "no"
+ description = "If set to yes, kpartx will not automatically create partitions on the device";
+ };
+
+ max_sectors_kb = mkOption {
+ type = nullOr int;
+ default = null;
+ description = "Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value";
+ };
+
+ ghost_delay = mkOption {
+ type = nullOr int;
+ default = null;
+ description = "Sets the number of seconds that multipath will wait after creating a device with only ghost paths before marking it ready for use in systemd";
+ };
+
+ all_tg_pt = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Set the 'all targets ports' flag when registering keys with mpathpersist";
+ };
+
+ };
+ });
+ };
+
+ defaults = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ This section defines default values for attributes which are used
+ whenever no values are given in the appropriate device or multipath
+ sections.
+ '';
+ };
+
+ blacklist = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ This section defines which devices should be excluded from the
+ multipath topology discovery.
+ '';
+ };
+
+ blacklist_exceptions = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ This section defines which devices should be included in the
+ multipath topology discovery, despite being listed in the
+ blacklist section.
+ '';
+ };
+
+ overrides = mkOption {
+ type = nullOr str;
+ default = null;
+ description = ''
+ This section defines values for attributes that should override the
+ device-specific settings for all devices.
+ '';
+ };
+
+ extraConfig = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Lines to append to default multipath.conf";
+ };
+
+ extraConfigFile = mkOption {
+ type = nullOr str;
+ default = null;
+ description = "Append an additional file's contents to /etc/multipath.conf";
+ };
+
+ pathGroups = mkOption {
+ example = literalExpression ''
+ [
+ {
+ wwid = "360080e500043b35c0123456789abcdef";
+ alias = 10001234;
+ array = "bigarray.example.com";
+ fsType = "zfs"; # optional
+ options = "ro"; # optional
+ }, ...
+ ]
+ '';
+ description = ''
+ This option allows you to define multipath groups as described
+ in http://christophe.varoqui.free.fr/usage.html.
+ '';
+ type = listOf (submodule {
+ options = {
+
+ alias = mkOption {
+ type = int;
+ example = 1001234;
+ description = "The name of the multipath device";
+ };
+
+ wwid = mkOption {
+ type = hexStr;
+ example = "360080e500043b35c0123456789abcdef";
+ description = "The identifier for the multipath device";
+ };
+
+ array = mkOption {
+ type = str;
+ default = null;
+ example = "bigarray.example.com";
+ description = "The DNS name of the storage array";
+ };
+
+ fsType = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "zfs";
+ description = "Type of the filesystem";
+ };
+
+ options = mkOption {
+ type = nullOr str;
+ default = null;
+ example = "ro";
+ description = "Options used to mount the file system";
+ };
+
+ };
+ });
+ };
+
+ };
+
+ config = mkIf cfg.enable {
+ environment.etc."multipath.conf".text =
+ let
+ inherit (cfg) defaults blacklist blacklist_exceptions overrides;
+
+ mkDeviceBlock = cfg: let
+ nonNullCfg = lib.filterAttrs (k: v: v != null) cfg;
+ attrs = lib.mapAttrsToList (name: value: " ${name} ${toString value}") nonNullCfg;
+ in ''
+ device {
+ ${lib.concatStringsSep "\n" attrs}
+ }
+ '';
+ devices = lib.concatMapStringsSep "\n" mkDeviceBlock cfg.devices;
+
+ mkMultipathBlock = m: ''
+ multipath {
+ wwid ${m.wwid}
+ alias ${toString m.alias}
+ }
+ '';
+ multipaths = lib.concatMapStringsSep "\n" mkMultipathBlock cfg.pathGroups;
+
+ in ''
+ devices {
+ ${indentLines 2 devices}
+ }
+
+ ${optionalString (!isNull defaults) ''
+ defaults {
+ ${indentLines 2 defaults}
+ multipath_dir ${cfg.package}/lib/multipath
+ }
+ ''}
+ ${optionalString (!isNull blacklist) ''
+ blacklist {
+ ${indentLines 2 blacklist}
+ }
+ ''}
+ ${optionalString (!isNull blacklist_exceptions) ''
+ blacklist_exceptions {
+ ${indentLines 2 blacklist_exceptions}
+ }
+ ''}
+ ${optionalString (!isNull overrides) ''
+ overrides {
+ ${indentLines 2 overrides}
+ }
+ ''}
+ multipaths {
+ ${indentLines 2 multipaths}
+ }
+ '';
+
+ systemd.packages = [ cfg.package ];
+
+ environment.systemPackages = [ cfg.package ];
+ boot.kernelModules = [ "dm-multipath" "dm-service-time" ];
+
+ # We do not have systemd in stage-1 boot so must invoke `multipathd`
+ # with the `-1` argument which disables systemd calls. Invoke `multipath`
+ # to display the multipath mappings in the output of `journalctl -b`.
+ boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ];
+ boot.initrd.postDeviceCommands = ''
+ modprobe -a dm-multipath dm-service-time
+ multipathd -s
+ (set -x && sleep 1 && multipath -ll)
+ '';
+ };
+}
diff --git a/nixos/modules/system/boot/stage-1.nix b/nixos/modules/system/boot/stage-1.nix
index bd7e955a6f425..adbed9d8d58e7 100644
--- a/nixos/modules/system/boot/stage-1.nix
+++ b/nixos/modules/system/boot/stage-1.nix
@@ -137,6 +137,14 @@ let
copy_bin_and_libs ${pkgs.e2fsprogs}/sbin/resize2fs
''}
+ # Copy multipath.
+ ${optionalString config.services.multipath.enable ''
+ copy_bin_and_libs ${config.services.multipath.package}/bin/multipath
+ copy_bin_and_libs ${config.services.multipath.package}/bin/multipathd
+ # Copy lib/multipath manually.
+ cp -rpv ${config.services.multipath.package}/lib/multipath $out/lib
+ ''}
+
# Copy secrets if needed.
#
# TODO: move out to a separate script; see #85000.
@@ -199,6 +207,10 @@ let
$out/bin/dmsetup --version 2>&1 | tee -a log | grep -q "version:"
LVM_SYSTEM_DIR=$out $out/bin/lvm version 2>&1 | tee -a log | grep -q "LVM"
$out/bin/mdadm --version
+ ${optionalString config.services.multipath.enable ''
+ ($out/bin/multipath || true) 2>&1 | grep -q 'need to be root'
+ ($out/bin/multipathd || true) 2>&1 | grep -q 'need to be root'
+ ''}
${config.boot.initrd.extraUtilsCommandsTest}
fi
@@ -338,7 +350,26 @@ let
{ object = pkgs.kmod-debian-aliases;
symlink = "/etc/modprobe.d/debian.conf";
}
- ];
+ ] ++ lib.optionals config.services.multipath.enable [
+ { object = pkgs.runCommand "multipath.conf" {
+ src = config.environment.etc."multipath.conf".text;
+ preferLocalBuild = true;
+ } ''
+ target=$out
+ printf "$src" > $out
+ substituteInPlace $out \
+ --replace ${config.services.multipath.package}/lib ${extraUtils}/lib
+ '';
+ symlink = "/etc/multipath.conf";
+ }
+ ] ++ (lib.mapAttrsToList
+ (symlink: options:
+ {
+ inherit symlink;
+ object = options.source;
+ }
+ )
+ config.boot.initrd.extraFiles);
};
# Script to add secret files to the initrd at bootloader update time
@@ -419,6 +450,22 @@ in
'';
};
+ boot.initrd.extraFiles = mkOption {
+ default = { };
+ type = types.attrsOf
+ (types.submodule {
+ options = {
+ source = mkOption {
+ type = types.package;
+ description = "The object to make available inside the initrd.";
+ };
+ };
+ });
+ description = ''
+ Extra files to link and copy in to the initrd.
+ '';
+ };
+
boot.initrd.prepend = mkOption {
default = [ ];
type = types.listOf types.str;
diff --git a/nixos/tests/iscsi-multipath-root.nix b/nixos/tests/iscsi-multipath-root.nix
new file mode 100644
index 0000000000000..a26fea503b62a
--- /dev/null
+++ b/nixos/tests/iscsi-multipath-root.nix
@@ -0,0 +1,267 @@
+import ./make-test-python.nix (
+ { pkgs, lib, ... }:
+ let
+ initiatorName = "iqn.2020-08.org.linux-iscsi.initiatorhost:example";
+ targetName = "iqn.2003-01.org.linux-iscsi.target.x8664:sn.acf8fd9c23af";
+ in
+ {
+ name = "iscsi";
+ meta = {
+ maintainers = pkgs.lib.teams.deshaw.members;
+ };
+
+ nodes = {
+ target = { config, pkgs, lib, ... }: {
+ virtualisation.vlans = [ 1 2 ];
+ services.target = {
+ enable = true;
+ config = {
+ fabric_modules = [ ];
+ storage_objects = [
+ {
+ dev = "/dev/vdb";
+ name = "test";
+ plugin = "block";
+ write_back = true;
+ wwn = "92b17c3f-6b40-4168-b082-ceeb7b495522";
+ }
+ ];
+ targets = [
+ {
+ fabric = "iscsi";
+ tpgs = [
+ {
+ enable = true;
+ attributes = {
+ authentication = 0;
+ generate_node_acls = 1;
+ };
+ luns = [
+ {
+ alias = "94dfe06967";
+ alua_tg_pt_gp_name = "default_tg_pt_gp";
+ index = 0;
+ storage_object = "/backstores/block/test";
+ }
+ ];
+ node_acls = [
+ {
+ mapped_luns = [
+ {
+ alias = "d42f5bdf8a";
+ index = 0;
+ tpg_lun = 0;
+ write_protect = false;
+ }
+ ];
+ node_wwn = initiatorName;
+ }
+ ];
+ portals = [
+ {
+ ip_address = "0.0.0.0";
+ iser = false;
+ offload = false;
+ port = 3260;
+ }
+ ];
+ tag = 1;
+ }
+ ];
+ wwn = targetName;
+ }
+ ];
+ };
+ };
+
+ networking.firewall.allowedTCPPorts = [ 3260 ];
+ networking.firewall.allowedUDPPorts = [ 3260 ];
+
+ virtualisation.memorySize = 2048;
+ virtualisation.emptyDiskImages = [ 2048 ];
+ };
+
+ initiatorAuto = { nodes, config, pkgs, ... }: {
+ virtualisation.vlans = [ 1 2 ];
+
+ services.multipath = {
+ enable = true;
+ defaults = ''
+ find_multipaths yes
+ user_friendly_names yes
+ '';
+ pathGroups = [
+ {
+ alias = 123456;
+ wwid = "3600140592b17c3f6b404168b082ceeb7";
+ }
+ ];
+ };
+
+ services.openiscsi = {
+ enable = true;
+ enableAutoLoginOut = true;
+ discoverPortal = "target";
+ name = initiatorName;
+ };
+
+ environment.systemPackages = with pkgs; [
+ xfsprogs
+ ];
+
+ environment.etc."initiator-root-disk-closure".source = nodes.initiatorRootDisk.config.system.build.toplevel;
+
+ nix.binaryCaches = lib.mkForce [ ];
+ nix.extraOptions = ''
+ hashed-mirrors =
+ connect-timeout = 1
+ '';
+ };
+
+ initiatorRootDisk = { config, pkgs, modulesPath, lib, ... }: {
+ boot.initrd.network.enable = true;
+ boot.loader.grub.enable = false;
+
+ boot.kernelParams = lib.mkOverride 5 (
+ [
+ "boot.shell_on_fail"
+ "console=tty1"
+ "ip=192.168.1.1:::255.255.255.0::ens9:none"
+ "ip=192.168.2.1:::255.255.255.0::ens10:none"
+ ]
+ );
+
+ # defaults to true, puts some code in the initrd that tries to mount an overlayfs on /nix/store
+ virtualisation.writableStore = false;
+ virtualisation.vlans = [ 1 2 ];
+
+ services.multipath = {
+ enable = true;
+ defaults = ''
+ find_multipaths yes
+ user_friendly_names yes
+ '';
+ pathGroups = [
+ {
+ alias = 123456;
+ wwid = "3600140592b17c3f6b404168b082ceeb7";
+ }
+ ];
+ };
+
+ fileSystems = lib.mkOverride 5 {
+ "/" = {
+ fsType = "xfs";
+ device = "/dev/mapper/123456";
+ options = [ "_netdev" ];
+ };
+ };
+
+ boot.initrd.extraFiles."etc/multipath/wwids".source = pkgs.writeText "wwids" "/3600140592b17c3f6b404168b082ceeb7/";
+
+ boot.iscsi-initiator = {
+ discoverPortal = "target";
+ name = initiatorName;
+ target = targetName;
+ extraIscsiCommands = ''
+ iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login
+ '';
+ };
+ };
+
+ };
+
+ testScript = { nodes, ... }: ''
+ target.start()
+ target.wait_for_unit("iscsi-target.service")
+
+ initiatorAuto.start()
+
+ initiatorAuto.wait_for_unit("iscsid.service")
+ initiatorAuto.wait_for_unit("iscsi.service")
+ initiatorAuto.get_unit_info("iscsi")
+
+ # Expecting this to fail since we should already know about 192.168.1.3
+ initiatorAuto.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+ # Expecting this to succeed since we don't yet know about 192.168.2.3
+ initiatorAuto.succeed("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+
+ # /dev/sda is provided by iscsi on target
+ initiatorAuto.succeed("set -x; while ! test -e /dev/sda; do sleep 1; done")
+
+ initiatorAuto.succeed("mkfs.xfs /dev/sda")
+ initiatorAuto.succeed("mkdir /mnt")
+
+ # Start by verifying /dev/sda and /dev/sdb are both the same disk
+ initiatorAuto.succeed("mount /dev/sda /mnt")
+ initiatorAuto.succeed("touch /mnt/hi")
+ initiatorAuto.succeed("umount /mnt")
+
+ initiatorAuto.succeed("mount /dev/sdb /mnt")
+ initiatorAuto.succeed("test -e /mnt/hi")
+ initiatorAuto.succeed("umount /mnt")
+
+ initiatorAuto.succeed("systemctl restart multipathd")
+ initiatorAuto.succeed("multipath -ll | systemd-cat")
+
+ # Install our RootDisk machine to 123456, the alias to the device that multipath is now managing
+ initiatorAuto.succeed("mount /dev/mapper/123456 /mnt")
+ initiatorAuto.succeed("mkdir -p /mnt/etc/{multipath,iscsi}")
+ initiatorAuto.succeed("cp -r /etc/multipath/wwids /mnt/etc/multipath/wwids")
+ initiatorAuto.succeed("cp -r /etc/iscsi/{nodes,send_targets} /mnt/etc/iscsi")
+ initiatorAuto.succeed(
+ "nixos-install --no-bootloader --no-root-passwd --system /etc/initiator-root-disk-closure"
+ )
+ initiatorAuto.succeed("umount /mnt")
+ initiatorAuto.shutdown()
+
+ initiatorRootDisk.start()
+ initiatorRootDisk.wait_for_unit("multi-user.target")
+ initiatorRootDisk.wait_for_unit("iscsid")
+
+ # Log in over both nodes
+ initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.1.3 --login")
+ initiatorRootDisk.fail("iscsiadm -m discovery -o update -t sendtargets -p 192.168.2.3 --login")
+ initiatorRootDisk.succeed("systemctl restart multipathd")
+ initiatorRootDisk.succeed("multipath -ll | systemd-cat")
+
+ # Verify we can write and sync the root disk
+ initiatorRootDisk.succeed("mkdir /scratch")
+ initiatorRootDisk.succeed("touch /scratch/both-up")
+ initiatorRootDisk.succeed("sync /scratch")
+
+ # Verify we can write to the root with ens9 (sda, 192.168.1.3) down
+ initiatorRootDisk.succeed("ip link set ens9 down")
+ initiatorRootDisk.succeed("touch /scratch/ens9-down")
+ initiatorRootDisk.succeed("sync /scratch")
+ initiatorRootDisk.succeed("ip link set ens9 up")
+
+ # todo: better way to wait until multipath notices the link is back
+ initiatorRootDisk.succeed("sleep 5")
+ initiatorRootDisk.succeed("touch /scratch/both-down")
+ initiatorRootDisk.succeed("sync /scratch")
+
+ # Verify we can write to the root with ens10 (sdb, 192.168.2.3) down
+ initiatorRootDisk.succeed("ip link set ens10 down")
+ initiatorRootDisk.succeed("touch /scratch/ens10-down")
+ initiatorRootDisk.succeed("sync /scratch")
+ initiatorRootDisk.succeed("ip link set ens10 up")
+ initiatorRootDisk.succeed("touch /scratch/ens10-down")
+ initiatorRootDisk.succeed("sync /scratch")
+
+ initiatorRootDisk.succeed("ip link set ens9 up")
+ initiatorRootDisk.succeed("ip link set ens10 up")
+ initiatorRootDisk.shutdown()
+
+ # Verify we can boot with the target's eth1 down, forcing
+ # it to multipath via the second link
+ target.succeed("ip link set eth1 down")
+ initiatorRootDisk.start()
+ initiatorRootDisk.wait_for_unit("multi-user.target")
+ initiatorRootDisk.wait_for_unit("iscsid")
+ initiatorRootDisk.succeed("test -e /scratch/both-up")
+ '';
+ }
+)
+
+
diff --git a/pkgs/os-specific/linux/multipath-tools/default.nix b/pkgs/os-specific/linux/multipath-tools/default.nix
index 241498057232f..13e9892436461 100644
--- a/pkgs/os-specific/linux/multipath-tools/default.nix
+++ b/pkgs/os-specific/linux/multipath-tools/default.nix
@@ -19,9 +19,11 @@ stdenv.mkDerivation rec {
substituteInPlace libmultipath/Makefile \
--replace /usr/include/libdevmapper.h ${lib.getDev lvm2}/include/libdevmapper.h
+ # systemd-udev-settle.service is deprecated.
substituteInPlace multipathd/multipathd.service \
--replace /sbin/modprobe ${lib.getBin kmod}/sbin/modprobe \
- --replace /sbin/multipathd "$out/bin/multipathd"
+ --replace /sbin/multipathd "$out/bin/multipathd" \
+ --replace " systemd-udev-settle.service" ""
sed -i -re '
s,^( *#define +DEFAULT_MULTIPATHDIR\>).*,\1 "'"$out/lib/multipath"'",