1{ system ? builtins.currentSystem
2, config ? { }
3, pkgs ? import ../.. { inherit system config; }
4}:
5
6with import ../lib/testing-python.nix { inherit system pkgs; };
7with pkgs.lib;
8
9let
10 # A testScript fragment that prepares a disk with some empty, unpartitioned
11 # space. and uses it to boot the test with. Takes a single argument `machine`
12 # from which the diskImage is extracted.
13 useDiskImage = machine: ''
14 import os
15 import shutil
16 import subprocess
17 import tempfile
18
19 tmp_disk_image = tempfile.NamedTemporaryFile()
20
21 shutil.copyfile("${machine.system.build.diskImage}/nixos.img", tmp_disk_image.name)
22
23 subprocess.run([
24 "${machine.config.virtualisation.qemu.package}/bin/qemu-img",
25 "resize",
26 "-f",
27 "raw",
28 tmp_disk_image.name,
29 "+32M",
30 ])
31
32 # Fix the GPT table by moving the backup table to the end of the enlarged
33 # disk image. This is necessary because we increased the size of the disk
34 # before. The disk needs to be a raw disk because sgdisk can only run on
35 # raw images.
36 subprocess.run([
37 "${pkgs.gptfdisk}/bin/sgdisk",
38 "--move-second-header",
39 tmp_disk_image.name,
40 ])
41
42 # Set NIX_DISK_IMAGE so that the qemu script finds the right disk image.
43 os.environ['NIX_DISK_IMAGE'] = tmp_disk_image.name
44 '';
45
46 common = { config, pkgs, lib, ... }: {
47 virtualisation.useDefaultFilesystems = false;
48 virtualisation.fileSystems = {
49 "/" = {
50 device = "/dev/vda2";
51 fsType = "ext4";
52 };
53 };
54
55 # systemd-repart operates on disks with a partition table. The qemu module,
56 # however, creates separate filesystem images without a partition table, so
57 # we have to create a disk image manually.
58 #
59 # This creates two partitions, an ESP available as /dev/vda1 and the root
60 # partition available as /dev/vda2.
61 system.build.diskImage = import ../lib/make-disk-image.nix {
62 inherit config pkgs lib;
63 # Use a raw format disk so that it can be resized before starting the
64 # test VM.
65 format = "raw";
66 # Keep the image as small as possible but leave some room for changes.
67 bootSize = "32M";
68 additionalSpace = "0M";
69 # GPT with an EFI System Partition is the typical use case for
70 # systemd-repart because it does not support MBR.
71 partitionTableType = "efi";
72 # We do not actually care much about the content of the partitions, so we
73 # do not need a bootloader installed.
74 installBootLoader = false;
75 # Improve determinism by not copying a channel.
76 copyChannel = false;
77 };
78 };
79in
80{
81 basic = makeTest {
82 name = "systemd-repart";
83 meta.maintainers = with maintainers; [ nikstur ];
84
85 nodes.machine = { config, pkgs, ... }: {
86 imports = [ common ];
87
88 boot.initrd.systemd.enable = true;
89
90 boot.initrd.systemd.repart.enable = true;
91 systemd.repart.partitions = {
92 "10-root" = {
93 Type = "linux-generic";
94 };
95 };
96 };
97
98 testScript = { nodes, ... }: ''
99 ${useDiskImage nodes.machine}
100
101 machine.start()
102 machine.wait_for_unit("multi-user.target")
103
104 systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
105 assert "Growing existing partition 1." in systemd_repart_logs
106 '';
107 };
108
109 after-initrd = makeTest {
110 name = "systemd-repart-after-initrd";
111 meta.maintainers = with maintainers; [ nikstur ];
112
113 nodes.machine = { config, pkgs, ... }: {
114 imports = [ common ];
115
116 systemd.repart.enable = true;
117 systemd.repart.partitions = {
118 "10-root" = {
119 Type = "linux-generic";
120 };
121 };
122 };
123
124 testScript = { nodes, ... }: ''
125 ${useDiskImage nodes.machine}
126
127 machine.start()
128 machine.wait_for_unit("multi-user.target")
129
130 systemd_repart_logs = machine.succeed("journalctl --unit systemd-repart.service")
131 assert "Growing existing partition 1." in systemd_repart_logs
132 '';
133 };
134
135 create-root = makeTest {
136 name = "systemd-repart-create-root";
137 meta.maintainers = with maintainers; [ nikstur ];
138
139 nodes.machine = { config, lib, pkgs, ... }: {
140 virtualisation.useDefaultFilesystems = false;
141 virtualisation.fileSystems = {
142 "/" = {
143 device = "/dev/disk/by-partlabel/created-root";
144 fsType = "ext4";
145 };
146 "/nix/store" = {
147 device = "/dev/vda2";
148 fsType = "ext4";
149 };
150 };
151
152 # Create an image containing only the Nix store. This enables creating
153 # the root partition with systemd-repart and then successfully booting
154 # into a working system.
155 #
156 # This creates two partitions, an ESP available as /dev/vda1 and the Nix
157 # store available as /dev/vda2.
158 system.build.diskImage = import ../lib/make-disk-image.nix {
159 inherit config pkgs lib;
160 onlyNixStore = true;
161 format = "raw";
162 bootSize = "32M";
163 additionalSpace = "0M";
164 partitionTableType = "efi";
165 installBootLoader = false;
166 copyChannel = false;
167 };
168
169 boot.initrd.systemd.enable = true;
170
171 boot.initrd.systemd.repart.enable = true;
172 boot.initrd.systemd.repart.device = "/dev/vda";
173 systemd.repart.partitions = {
174 "10-root" = {
175 Type = "root";
176 Label = "created-root";
177 Format = "ext4";
178 };
179 };
180 };
181
182 testScript = { nodes, ... }: ''
183 ${useDiskImage nodes.machine}
184
185 machine.start()
186 machine.wait_for_unit("multi-user.target")
187
188 systemd_repart_logs = machine.succeed("journalctl --boot --unit systemd-repart.service")
189 assert "Adding new partition 2 to partition table." in systemd_repart_logs
190 '';
191 };
192}