at 24.11-pre 20 kB view raw
1{ config, lib, pkgs, ... }: with lib; 2 3# See http://christophe.varoqui.free.fr/usage.html and 4# https://github.com/opensvc/multipath-tools/blob/master/multipath/multipath.conf.5 5 6let 7 cfg = config.services.multipath; 8 9 indentLines = n: str: concatStringsSep "\n" ( 10 map (line: "${fixedWidthString n " " " "}${line}") ( 11 filter ( x: x != "" ) ( splitString "\n" str ) 12 ) 13 ); 14 15 addCheckDesc = desc: elemType: check: types.addCheck elemType check 16 // { description = "${elemType.description} (with check: ${desc})"; }; 17 hexChars = stringToCharacters "0123456789abcdef"; 18 isHexString = s: all (c: elem c hexChars) (stringToCharacters (toLower s)); 19 hexStr = addCheckDesc "hexadecimal string" types.str isHexString; 20 21in { 22 23 options.services.multipath = with types; { 24 25 enable = mkEnableOption "the device mapper multipath (DM-MP) daemon"; 26 27 package = mkPackageOption pkgs "multipath-tools" { }; 28 29 devices = mkOption { 30 default = [ ]; 31 example = literalExpression '' 32 [ 33 { 34 vendor = "\"COMPELNT\""; 35 product = "\"Compellent Vol\""; 36 path_checker = "tur"; 37 no_path_retry = "queue"; 38 max_sectors_kb = 256; 39 }, ... 40 ] 41 ''; 42 description = '' 43 This option allows you to define arrays for use in multipath 44 groups. 45 ''; 46 type = listOf (submodule { 47 options = { 48 49 vendor = mkOption { 50 type = str; 51 example = "COMPELNT"; 52 description = "Regular expression to match the vendor name"; 53 }; 54 55 product = mkOption { 56 type = str; 57 example = "Compellent Vol"; 58 description = "Regular expression to match the product name"; 59 }; 60 61 revision = mkOption { 62 type = nullOr str; 63 default = null; 64 description = "Regular expression to match the product revision"; 65 }; 66 67 product_blacklist = mkOption { 68 type = nullOr str; 69 default = null; 70 description = "Products with the given vendor matching this string are blacklisted"; 71 }; 72 73 alias_prefix = mkOption { 74 type = nullOr str; 75 default = null; 76 description = "The user_friendly_names prefix to use for this device type, instead of the default mpath"; 77 }; 78 79 vpd_vendor = mkOption { 80 type = nullOr str; 81 default = null; 82 description = "The vendor specific vpd page information, using the vpd page abbreviation"; 83 }; 84 85 hardware_handler = mkOption { 86 type = nullOr (enum [ "emc" "rdac" "hp_sw" "alua" "ana" ]); 87 default = null; 88 description = "The hardware handler to use for this device type"; 89 }; 90 91 # Optional arguments 92 path_grouping_policy = mkOption { 93 type = nullOr (enum [ "failover" "multibus" "group_by_serial" "group_by_prio" "group_by_node_name" ]); 94 default = null; # real default: "failover" 95 description = "The default path grouping policy to apply to unspecified multipaths"; 96 }; 97 98 uid_attribute = mkOption { 99 type = nullOr str; 100 default = null; 101 description = "The udev attribute providing a unique path identifier (WWID)"; 102 }; 103 104 getuid_callout = mkOption { 105 type = nullOr str; 106 default = null; 107 description = '' 108 (Superseded by uid_attribute) The default program and args to callout 109 to obtain a unique path identifier. Should be specified with an absolute path. 110 ''; 111 }; 112 113 path_selector = mkOption { 114 type = nullOr (enum [ 115 ''"round-robin 0"'' 116 ''"queue-length 0"'' 117 ''"service-time 0"'' 118 ''"historical-service-time 0"'' 119 ]); 120 default = null; # real default: "service-time 0" 121 description = "The default path selector algorithm to use; they are offered by the kernel multipath target"; 122 }; 123 124 path_checker = mkOption { 125 type = enum [ "readsector0" "tur" "emc_clariion" "hp_sw" "rdac" "directio" "cciss_tur" "none" ]; 126 default = "tur"; 127 description = "The default method used to determine the paths state"; 128 }; 129 130 prio = mkOption { 131 type = nullOr (enum [ 132 "none" "const" "sysfs" "emc" "alua" "ontap" "rdac" "hp_sw" "hds" 133 "random" "weightedpath" "path_latency" "ana" "datacore" "iet" 134 ]); 135 default = null; # real default: "const" 136 description = "The name of the path priority routine"; 137 }; 138 139 prio_args = mkOption { 140 type = nullOr str; 141 default = null; 142 description = "Arguments to pass to to the prio function"; 143 }; 144 145 features = mkOption { 146 type = nullOr str; 147 default = null; 148 description = "Specify any device-mapper features to be used"; 149 }; 150 151 failback = mkOption { 152 type = nullOr str; 153 default = null; # real default: "manual" 154 description = "Tell multipathd how to manage path group failback. Quote integers as strings"; 155 }; 156 157 rr_weight = mkOption { 158 type = nullOr (enum [ "priorities" "uniform" ]); 159 default = null; # real default: "uniform" 160 description = '' 161 If set to priorities the multipath configurator will assign path weights 162 as "path prio * rr_min_io". 163 ''; 164 }; 165 166 no_path_retry = mkOption { 167 type = nullOr str; 168 default = null; # real default: "fail" 169 description = "Specify what to do when all paths are down. Quote integers as strings"; 170 }; 171 172 rr_min_io = mkOption { 173 type = nullOr int; 174 default = null; # real default: 1000 175 description = '' 176 Number of I/O requests to route to a path before switching to the next in the 177 same path group. This is only for Block I/O (BIO) based multipath and 178 only apply to round-robin path_selector. 179 ''; 180 }; 181 182 rr_min_io_rq = mkOption { 183 type = nullOr int; 184 default = null; # real default: 1 185 description = '' 186 Number of I/O requests to route to a path before switching to the next in the 187 same path group. This is only for Request based multipath and 188 only apply to round-robin path_selector. 189 ''; 190 }; 191 192 fast_io_fail_tmo = mkOption { 193 type = nullOr str; 194 default = null; # real default: 5 195 description = '' 196 Specify the number of seconds the SCSI layer will wait after a problem has been 197 detected on a FC remote port before failing I/O to devices on that remote port. 198 This should be smaller than dev_loss_tmo. Setting this to "off" will disable 199 the timeout. Quote integers as strings. 200 ''; 201 }; 202 203 dev_loss_tmo = mkOption { 204 type = nullOr str; 205 default = null; # real default: 600 206 description = '' 207 Specify the number of seconds the SCSI layer will wait after a problem has 208 been detected on a FC remote port before removing it from the system. This 209 can be set to "infinity" which sets it to the max value of 2147483647 210 seconds, or 68 years. It will be automatically adjusted to the overall 211 retry interval no_path_retry * polling_interval 212 if a number of retries is given with no_path_retry and the 213 overall retry interval is longer than the specified dev_loss_tmo value. 214 The Linux kernel will cap this value to 600 if fast_io_fail_tmo 215 is not set. 216 ''; 217 }; 218 219 flush_on_last_del = mkOption { 220 type = nullOr (enum [ "yes" "no" ]); 221 default = null; # real default: "no" 222 description = '' 223 If set to "yes" multipathd will disable queueing when the last path to a 224 device has been deleted. 225 ''; 226 }; 227 228 user_friendly_names = mkOption { 229 type = nullOr (enum [ "yes" "no" ]); 230 default = null; # real default: "no" 231 description = '' 232 If set to "yes", using the bindings file /etc/multipath/bindings 233 to assign a persistent and unique alias to the multipath, in the 234 form of mpath. If set to "no" use the WWID as the alias. In either 235 case this be will be overridden by any specific aliases in the 236 multipaths section. 237 ''; 238 }; 239 240 detect_prio = mkOption { 241 type = nullOr (enum [ "yes" "no" ]); 242 default = null; # real default: "yes" 243 description = '' 244 If set to "yes", multipath will try to detect if the device supports 245 SCSI-3 ALUA. If so, the device will automatically use the sysfs 246 prioritizer if the required sysf attributes access_state and 247 preferred_path are supported, or the alua prioritizer if not. If set 248 to "no", the prioritizer will be selected as usual. 249 ''; 250 }; 251 252 detect_checker = mkOption { 253 type = nullOr (enum [ "yes" "no" ]); 254 default = null; # real default: "yes" 255 description = '' 256 If set to "yes", multipath will try to detect if the device supports 257 SCSI-3 ALUA. If so, the device will automatically use the tur checker. 258 If set to "no", the checker will be selected as usual. 259 ''; 260 }; 261 262 deferred_remove = mkOption { 263 type = nullOr (enum [ "yes" "no" ]); 264 default = null; # real default: "no" 265 description = '' 266 If set to "yes", multipathd will do a deferred remove instead of a 267 regular remove when the last path device has been deleted. This means 268 that if the multipath device is still in use, it will be freed when 269 the last user closes it. If path is added to the multipath device 270 before the last user closes it, the deferred remove will be canceled. 271 ''; 272 }; 273 274 san_path_err_threshold = mkOption { 275 type = nullOr str; 276 default = null; 277 description = '' 278 If set to a value greater than 0, multipathd will watch paths and check 279 how many times a path has been failed due to errors.If the number of 280 failures on a particular path is greater then the san_path_err_threshold, 281 then the path will not reinstate till san_path_err_recovery_time. These 282 path failures should occur within a san_path_err_forget_rate checks, if 283 not we will consider the path is good enough to reinstantate. 284 ''; 285 }; 286 287 san_path_err_forget_rate = mkOption { 288 type = nullOr str; 289 default = null; 290 description = '' 291 If set to a value greater than 0, multipathd will check whether the path 292 failures has exceeded the san_path_err_threshold within this many checks 293 i.e san_path_err_forget_rate. If so we will not reinstante the path till 294 san_path_err_recovery_time. 295 ''; 296 }; 297 298 san_path_err_recovery_time = mkOption { 299 type = nullOr str; 300 default = null; 301 description = '' 302 If set to a value greater than 0, multipathd will make sure that when 303 path failures has exceeded the san_path_err_threshold within 304 san_path_err_forget_rate then the path will be placed in failed state 305 for san_path_err_recovery_time duration. Once san_path_err_recovery_time 306 has timeout we will reinstante the failed path. san_path_err_recovery_time 307 value should be in secs. 308 ''; 309 }; 310 311 marginal_path_err_sample_time = mkOption { 312 type = nullOr int; 313 default = null; 314 description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error"; 315 }; 316 317 marginal_path_err_rate_threshold = mkOption { 318 type = nullOr int; 319 default = null; 320 description = "The error rate threshold as a permillage (1/1000)"; 321 }; 322 323 marginal_path_err_recheck_gap_time = mkOption { 324 type = nullOr str; 325 default = null; 326 description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error"; 327 }; 328 329 marginal_path_double_failed_time = mkOption { 330 type = nullOr str; 331 default = null; 332 description = "One of the four parameters of supporting path check based on accounting IO error such as intermittent error"; 333 }; 334 335 delay_watch_checks = mkOption { 336 type = nullOr str; 337 default = null; 338 description = "This option is deprecated, and mapped to san_path_err_forget_rate"; 339 }; 340 341 delay_wait_checks = mkOption { 342 type = nullOr str; 343 default = null; 344 description = "This option is deprecated, and mapped to san_path_err_recovery_time"; 345 }; 346 347 skip_kpartx = mkOption { 348 type = nullOr (enum [ "yes" "no" ]); 349 default = null; # real default: "no" 350 description = "If set to yes, kpartx will not automatically create partitions on the device"; 351 }; 352 353 max_sectors_kb = mkOption { 354 type = nullOr int; 355 default = null; 356 description = "Sets the max_sectors_kb device parameter on all path devices and the multipath device to the specified value"; 357 }; 358 359 ghost_delay = mkOption { 360 type = nullOr int; 361 default = null; 362 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"; 363 }; 364 365 all_tg_pt = mkOption { 366 type = nullOr str; 367 default = null; 368 description = "Set the 'all targets ports' flag when registering keys with mpathpersist"; 369 }; 370 371 }; 372 }); 373 }; 374 375 defaults = mkOption { 376 type = nullOr str; 377 default = null; 378 description = '' 379 This section defines default values for attributes which are used 380 whenever no values are given in the appropriate device or multipath 381 sections. 382 ''; 383 }; 384 385 blacklist = mkOption { 386 type = nullOr str; 387 default = null; 388 description = '' 389 This section defines which devices should be excluded from the 390 multipath topology discovery. 391 ''; 392 }; 393 394 blacklist_exceptions = mkOption { 395 type = nullOr str; 396 default = null; 397 description = '' 398 This section defines which devices should be included in the 399 multipath topology discovery, despite being listed in the 400 blacklist section. 401 ''; 402 }; 403 404 overrides = mkOption { 405 type = nullOr str; 406 default = null; 407 description = '' 408 This section defines values for attributes that should override the 409 device-specific settings for all devices. 410 ''; 411 }; 412 413 extraConfig = mkOption { 414 type = nullOr str; 415 default = null; 416 description = "Lines to append to default multipath.conf"; 417 }; 418 419 extraConfigFile = mkOption { 420 type = nullOr str; 421 default = null; 422 description = "Append an additional file's contents to /etc/multipath.conf"; 423 }; 424 425 pathGroups = mkOption { 426 example = literalExpression '' 427 [ 428 { 429 wwid = "360080e500043b35c0123456789abcdef"; 430 alias = 10001234; 431 array = "bigarray.example.com"; 432 fsType = "zfs"; # optional 433 options = "ro"; # optional 434 }, ... 435 ] 436 ''; 437 description = '' 438 This option allows you to define multipath groups as described 439 in http://christophe.varoqui.free.fr/usage.html. 440 ''; 441 type = listOf (submodule { 442 options = { 443 444 alias = mkOption { 445 type = int; 446 example = 1001234; 447 description = "The name of the multipath device"; 448 }; 449 450 wwid = mkOption { 451 type = hexStr; 452 example = "360080e500043b35c0123456789abcdef"; 453 description = "The identifier for the multipath device"; 454 }; 455 456 array = mkOption { 457 type = str; 458 default = null; 459 example = "bigarray.example.com"; 460 description = "The DNS name of the storage array"; 461 }; 462 463 fsType = mkOption { 464 type = nullOr str; 465 default = null; 466 example = "zfs"; 467 description = "Type of the filesystem"; 468 }; 469 470 options = mkOption { 471 type = nullOr str; 472 default = null; 473 example = "ro"; 474 description = "Options used to mount the file system"; 475 }; 476 477 }; 478 }); 479 }; 480 481 }; 482 483 config = mkIf cfg.enable { 484 environment.etc."multipath.conf".text = 485 let 486 inherit (cfg) defaults blacklist blacklist_exceptions overrides; 487 488 mkDeviceBlock = cfg: let 489 nonNullCfg = lib.filterAttrs (k: v: v != null) cfg; 490 attrs = lib.mapAttrsToList (name: value: " ${name} ${toString value}") nonNullCfg; 491 in '' 492 device { 493 ${lib.concatStringsSep "\n" attrs} 494 } 495 ''; 496 devices = lib.concatMapStringsSep "\n" mkDeviceBlock cfg.devices; 497 498 mkMultipathBlock = m: '' 499 multipath { 500 wwid ${m.wwid} 501 alias ${toString m.alias} 502 } 503 ''; 504 multipaths = lib.concatMapStringsSep "\n" mkMultipathBlock cfg.pathGroups; 505 506 in '' 507 devices { 508 ${indentLines 2 devices} 509 } 510 511 ${optionalString (defaults != null) '' 512 defaults { 513 ${indentLines 2 defaults} 514 } 515 ''} 516 ${optionalString (blacklist != null) '' 517 blacklist { 518 ${indentLines 2 blacklist} 519 } 520 ''} 521 ${optionalString (blacklist_exceptions != null) '' 522 blacklist_exceptions { 523 ${indentLines 2 blacklist_exceptions} 524 } 525 ''} 526 ${optionalString (overrides != null) '' 527 overrides { 528 ${indentLines 2 overrides} 529 } 530 ''} 531 multipaths { 532 ${indentLines 2 multipaths} 533 } 534 ''; 535 536 systemd.packages = [ cfg.package ]; 537 538 environment.systemPackages = [ cfg.package ]; 539 boot.kernelModules = [ "dm-multipath" "dm-service-time" ]; 540 541 # We do not have systemd in stage-1 boot so must invoke `multipathd` 542 # with the `-1` argument which disables systemd calls. Invoke `multipath` 543 # to display the multipath mappings in the output of `journalctl -b`. 544 # TODO: Implement for systemd stage 1 545 boot.initrd.kernelModules = [ "dm-multipath" "dm-service-time" ]; 546 boot.initrd.postDeviceCommands = mkIf (!config.boot.initrd.systemd.enable) '' 547 modprobe -a dm-multipath dm-service-time 548 multipathd -s 549 (set -x && sleep 1 && multipath -ll) 550 ''; 551 }; 552}