Subject: [PATCH v2 2/2] Provide md-array-device-mapping to start MD arrays via UUID or name.

From: Felix Lechner <[email-masked]>
Date: Thu, 23 Nov 2023 06:57:00 -0800

This commit cures the most precipitous danger for users of MD arrays in GNU
Guix, namely that their equipment may not boot after a drive failure. That
behavior likely contradicts their primary expectation for having such a disk
arrangments.

In order to facilitate a smooth transition from raid-device-mapping to
md-array-device-mapping, this commit introduces a new mapping rather than
repurpose the old one. The new mapping here is also incompatible with
raid-device-mapping in the sense that a plain string is now interpreted as the
array name from the MD superblock.

For details, please consult the mdadm manual page.

Personally, the author prefers UUIDs over array names when identifying array
components, but either will work. The system test uses the name.

The name for the new device mapping was chosen instead of the traditional RAID
to account for the fact that some modern technologies (like SSDs) and some
array configurations, such as striping, are neither redundant nor inexpensive.

Adjusts the documentation by erasing any mention of the obsolete
raid-device-mapping. No one should use that any longer. Ideally, users would
be a deprecation warning, but I was unable to adapt 'define-deprecated' to
this use case. Please feel free to make further changes.

This commit includes an updated system test for the root file system on
an-md-array.

More details for the motivation of these changes may be available here:

  https://lists.gnu.org/archive/html/guix-devel/2023-04/msg00010.html

The author of this commit used to maintain mdadm in Debian.

Please feel free to insert better changelog messages. I had some difficulty
meeting the likely expectations of any reviewer. Please also feel free to make
any other adjustments as needed without checking with me. Thanks!

* gnu/system/mapped-devices.scm: New variable md-array-device-mapping.
* doc/guix.texi: Mention md-array-device-mapping in the documentation..
* gnu/tests/install.scm: Adjust test for root-on-md-array.
---
Hi Ludo'

With this updated patch series, I hope to address all your questions
and concerns. Thanks!

Kind regards
Felix

doc/guix.texi                 | 28 ++++++------
 gnu/system/mapped-devices.scm | 36 +++++++++++++++
 gnu/tests/install.scm         | 84 +++++++++++++++++++++++++++++++++++
 3 files changed, 135 insertions(+), 13 deletions(-)

diff --git a/doc/guix.texi b/doc/guix.texi
index 94903fb5e2..7676a58d99 100644
--- a/doc/guix.texi
+++ b/doc/guix.texi
@@ -17762,18 +17762,19 @@ the system boots up.
 
 @table @code
 @item source
-This is either a string specifying the name of the block device to be mapped,
-such as @code{"/dev/sda3"}, or a list of such strings when several devices
-need to be assembled for creating a new one.  In case of LVM this is a
-string specifying name of the volume group to be mapped.
+This is either a string specifying the name of the block device to be
+mapped, such as @code{"/dev/sda3"}.  For MD array devices it is either
+the UUID of the array or a string that is interpreted as the array name
+(see mdadm.conf(5) in the manual).  In case of LVM it is a string
+specifying name of the volume group to be mapped.
 
 @item target
 This string specifies the name of the resulting mapped device.  For
 kernel mappers such as encrypted devices of type @code{luks-device-mapping},
 specifying @code{"my-partition"} leads to the creation of
 the @code{"/dev/mapper/my-partition"} device.
-For RAID devices of type @code{raid-device-mapping}, the full device name
-such as @code{"/dev/md0"} needs to be given.
+For MD array devices of type @code{md-array-device-mapping}, the full device
+name such as @code{"/dev/md18"} needs to be given.
 LVM logical volumes of type @code{lvm-device-mapping} need to
 be specified as @code{"VGNAME-LVNAME"}.
 
@@ -17793,11 +17794,12 @@ command from the package with the same name.  It relies on the
 @code{dm-crypt} Linux kernel module.
 @end defvar
 
-@defvar raid-device-mapping
+@defvar md-array-device-mapping
 This defines a RAID device, which is assembled using the @code{mdadm}
-command from the package with the same name.  It requires a Linux kernel
-module for the appropriate RAID level to be loaded, such as @code{raid456}
-for RAID-4, RAID-5 or RAID-6, or @code{raid10} for RAID-10.
+command from the package with the same name.  It requires the Linux kernel
+module for the appropriate RAID level to be loaded, such as @code{raid1}
+for mirroring, @code{raid456} for the checksum-based RAID levels 4, 5 or 6,
+or @code{raid10} for RAID-10.
 @end defvar
 
 @cindex LVM, logical volume manager
@@ -17855,9 +17857,9 @@ may be declared as follows:
 
 @lisp
 (mapped-device
-  (source (list "/dev/sda1" "/dev/sdb1"))
-  (target "/dev/md0")
-  (type raid-device-mapping))
+  (source (uuid "33cf3e31:8e33d75b:517d64b9:0a8f7623" 'mdadm))
+  (target "/dev/md17")
+  (type md-array-device-mapping))
 @end lisp
 
 The @file{/dev/md0} device can then be used as the @code{device} of a
diff --git a/gnu/system/mapped-devices.scm b/gnu/system/mapped-devices.scm
index e6b8970c12..e6635b531d 100644
--- a/gnu/system/mapped-devices.scm
+++ b/gnu/system/mapped-devices.scm
@@ -64,6 +64,7 @@ (define-module (gnu system mapped-devices)
             check-device-initrd-modules           ;XXX: needs a better place
 
             luks-device-mapping
+            md-array-device-mapping
             raid-device-mapping
             lvm-device-mapping))
 
@@ -276,6 +277,39 @@ (define luks-device-mapping
    (close close-luks-device)
    (check check-luks-device)))
 
+(define (open-md-array-device source targets)
+  "Return a gexp that assembles SOURCE to the MD device
+TARGET (e.g., \"/dev/md0\"), using 'mdadm'."
+  (let ((array-selector
+         (match source
+           ((? uuid?)
+            (string-append "--uuid=" (uuid->string source)))
+           ((? string?)
+            (string-append "--name=" source))))
+        (md-device
+         (match targets
+           ((target)
+            target))))
+    ;; Use 'mdadm-static' rather than 'mdadm' to avoid pulling its whole
+    ;; closure (80 MiB) in the initrd when an MD device is needed for boot.
+    #~(zero? (system* #$(file-append mdadm-static "/sbin/mdadm")
+                      "--assemble" #$md-device
+                      "--run"
+                      #$array-selector))))
+
+(define (close-md-array-device source targets)
+  "Return a gexp that stops the MD device TARGET."
+  (match targets
+    ((target)
+     #~(zero? (system* #$(file-append mdadm-static "/sbin/mdadm")
+                       "--stop" #$target)))))
+
+(define md-array-device-mapping
+  ;; The type of MD mapped device.
+  (mapped-device-kind
+   (open open-md-array-device)
+   (close close-md-array-device)))
+
 (define (open-raid-device sources targets)
   "Return a gexp that assembles SOURCES (a list of devices) to the RAID device
 TARGET (e.g., \"/dev/md0\"), using 'mdadm'."
@@ -317,6 +351,8 @@ (define raid-device-mapping
    (open open-raid-device)
    (close close-raid-device)))
 
+(define-deprecated raid-device-mapping md-array-device-mapping)
+
 (define (open-lvm-device source targets)
   #~(and
      (zero? (system* #$(file-append lvm2-static "/sbin/lvm")
diff --git a/gnu/tests/install.scm b/gnu/tests/install.scm
index daa4647299..9e80b55f84 100644
--- a/gnu/tests/install.scm
+++ b/gnu/tests/install.scm
@@ -64,6 +64,7 @@ (define-module (gnu tests install)
             %test-iso-image-installer
             %test-separate-store-os
             %test-separate-home-os
+            %test-md-array-root-os
             %test-raid-root-os
             %test-encrypted-root-os
             %test-encrypted-home-os
@@ -610,6 +611,89 @@ (define %test-separate-store-os
                          (command (qemu-command* images)))
       (run-basic-test %separate-store-os command "separate-store-os")))))
 
+
+;;;
+;;; MD root device.
+;;;
+
+(define-os-with-source (%md-array-root-os %md-array-root-os-source)
+  ;; An OS whose root partition is a MD partition.
+  (use-modules (gnu) (gnu tests))
+
+  (operating-system
+    (host-name "raidified")
+    (timezone "Europe/Paris")
+    (locale "en_US.utf8")
+
+    (bootloader (bootloader-configuration
+                 (bootloader grub-bootloader)
+                 (targets (list "/dev/vdb"))))
+    (kernel-arguments '("console=ttyS0"))
+
+    ;; Add a kernel module for RAID-1 (aka. "mirror").
+    (initrd-modules (cons "raid1" %base-initrd-modules))
+
+    (mapped-devices (list (mapped-device
+                           (source "marionette:mirror0")
+                           (target "/dev/md0")
+                           (type md-array-device-mapping))))
+    (file-systems (cons (file-system
+                          (device (file-system-label "root-fs"))
+                          (mount-point "/")
+                          (type "ext4")
+                          (dependencies mapped-devices))
+                        %base-file-systems))
+    (users %base-user-accounts)
+    (services (cons (service marionette-service-type
+                             (marionette-configuration
+                              (imported-modules '((gnu services herd)
+                                                  (guix combinators)))))
+                    %base-services))))
+
+(define %md-array-root-installation-script
+  ;; Installation with a separate /gnu partition.  See
+  ;; <https://raid.wiki.kernel.org/index.php/RAID_setup> for more on RAID and
+  ;; mdadm.
+  "\
+. /etc/profile
+set -e -x
+guix --version
+
+export GUIX_BUILD_OPTIONS=--no-grafts
+parted --script /dev/vdb mklabel gpt \\
+  mkpart primary ext2 1M 3M \\
+  mkpart primary ext2 3M 1.6G \\
+  mkpart primary ext2 1.6G 3.2G \\
+  set 1 boot on \\
+  set 1 bios_grub on
+yes | mdadm --create /dev/md0 --verbose --homehost=marionette --name=mirror0 \\
+  --level=mirror --raid-devices=2 /dev/vdb2 /dev/vdb3
+mkfs.ext4 -L root-fs /dev/md0
+mount /dev/md0 /mnt
+df -h /mnt
+herd start cow-store /mnt
+mkdir /mnt/etc
+cp /etc/target-config.scm /mnt/etc/config.scm
+guix system init /mnt/etc/config.scm /mnt --no-substitutes
+sync
+reboot\n")
+
+(define %test-md-array-root-os
+  (system-test
+   (name "md-array-root-os")
+   (description
+    "Test functionality of an OS installed with a RAID root partition managed
+by 'mdadm'.")
+   (value
+    (mlet* %store-monad ((images (run-install %md-array-root-os
+                                              %md-array-root-os-source
+                                              #:script
+                                              %md-array-root-installation-script
+                                              #:target-size (* 3200 MiB)))
+                         (command (qemu-command* images)))
+      (run-basic-test %md-array-root-os
+                      `(,@command) "md-array-root-os")))))
+
 
 ;;;
 ;;; RAID root device.
-- 
2.41.0