Merge ~raharper/curtin:mwhudson-vmtest-reuse-half-a-raid into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: fbc9696d4b4b0ff0aa9846e8bff8061e879cac9d
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/curtin:mwhudson-vmtest-reuse-half-a-raid
Merge into: curtin:master
Diff against target: 345 lines (+250/-19)
6 files modified
curtin/block/clear_holders.py (+20/-5)
curtin/commands/block_meta.py (+16/-12)
examples/tests/reuse-raid-member-partition.yaml (+73/-0)
examples/tests/reuse-raid-member-wipe.yaml (+71/-0)
tests/unittests/test_clear_holders.py (+8/-2)
tests/vmtests/test_reuse_raid_member.py (+62/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Watkins (community) Approve
Review via email: mp+370843@code.launchpad.net

Commit message

Handle partial raid on partitions

Consider disks and partitions when checking for holders that
need removal before they can be used. Additionally when shutting
down a raid, if it is inactive or broken, writes to the /dev/mdX
device will fail which is expected, don't exit instead, log a
non-fatal error and continue with shutting down the raid.

With these fixes TestReuseRAIDMemberPartition vmtests now pass.

LP: #1835091

To post a comment you must log in.
Revision history for this message
Ryan Harper (raharper) wrote :

This needs a full vmtest run to ensure we're not regressing things with the changes.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Michael Hudson-Doyle (mwhudson) wrote :

LGTM assuming the vmtests are good.

20033d3... by Ryan Harper

Discover how to spell Discovering

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
05c2aaf... by Ryan Harper

Refactor to handle not present devices

In some scenarios, the partitions in the config _may_ be present
but we need to handle if they are not. Additionally, if they were
present when we initially start clearing devices, they wont be present
after the clear so assert_clear needs to adapt to allow that.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
22d9742... by Ryan Harper

Don't forget to return the paths

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

This looks really good, thanks! I have one inline comment about exception handling that I'd like resolved, but after that I'll be +1.

review: Needs Information
Revision history for this message
Ryan Harper (raharper) :
fbc9696... by Ryan Harper

Update comment around exception handling for partial raid shutdown

Revision history for this message
Dan Watkins (oddbloke) wrote :

LGTM, thanks for the comment!

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

This needed a rebase after the change to btrfs-tools package names. After rebase, I'll resubmit vmtest run.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/curtin/block/clear_holders.py b/curtin/block/clear_holders.py
index 4a099cd..bcc16e6 100644
--- a/curtin/block/clear_holders.py
+++ b/curtin/block/clear_holders.py
@@ -166,14 +166,27 @@ def shutdown_mdadm(device):
166166
167 blockdev = block.sysfs_to_devpath(device)167 blockdev = block.sysfs_to_devpath(device)
168168
169 LOG.info('Wiping superblock on raid device: %s', device)169 LOG.info('Discovering raid devices and spares for %s', device)
170 _wipe_superblock(blockdev, exclusive=False)
171
172 md_devs = (170 md_devs = (
173 mdadm.md_get_devices_list(blockdev) +171 mdadm.md_get_devices_list(blockdev) +
174 mdadm.md_get_spares_list(blockdev))172 mdadm.md_get_spares_list(blockdev))
175 mdadm.set_sync_action(blockdev, action="idle")173 mdadm.set_sync_action(blockdev, action="idle")
176 mdadm.set_sync_action(blockdev, action="frozen")174 mdadm.set_sync_action(blockdev, action="frozen")
175
176 LOG.info('Wiping superblock on raid device: %s', device)
177 try:
178 _wipe_superblock(blockdev, exclusive=False)
179 except ValueError as e:
180 # if the array is not functional, writes to the device may fail
181 # and _wipe_superblock will raise ValueError for short writes
182 # which happens on inactive raid volumes. In that case we
183 # shouldn't give up yet as we still want to disassemble
184 # array and wipe members. Other errors such as IOError or OSError
185 # are unwelcome and will stop deployment.
186 LOG.debug('Non-fatal error writing to array device %s, '
187 'proceeding with shutdown: %s', blockdev, e)
188
189 LOG.info('Removing raid array members: %s', md_devs)
177 for mddev in md_devs:190 for mddev in md_devs:
178 try:191 try:
179 mdadm.fail_device(blockdev, mddev)192 mdadm.fail_device(blockdev, mddev)
@@ -534,8 +547,10 @@ def assert_clear(base_paths):
534 valid = ('disk', 'partition')547 valid = ('disk', 'partition')
535 if not isinstance(base_paths, (list, tuple)):548 if not isinstance(base_paths, (list, tuple)):
536 base_paths = [base_paths]549 base_paths = [base_paths]
537 base_paths = [block.sys_block_path(path) for path in base_paths]550 base_paths = [block.sys_block_path(path, strict=False)
538 for holders_tree in [gen_holders_tree(p) for p in base_paths]:551 for path in base_paths]
552 for holders_tree in [gen_holders_tree(p)
553 for p in base_paths if os.path.exists(p)]:
539 if any(holder_type not in valid and path not in base_paths554 if any(holder_type not in valid and path not in base_paths
540 for (holder_type, path) in get_holder_types(holders_tree)):555 for (holder_type, path) in get_holder_types(holders_tree)):
541 raise OSError('Storage not clear, remaining:\n{}'556 raise OSError('Storage not clear, remaining:\n{}'
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index a9110a9..37b93dd 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -67,7 +67,7 @@ def block_meta(args):
67 if devices is None:67 if devices is None:
68 devices = []68 devices = []
69 if 'storage' in cfg:69 if 'storage' in cfg:
70 devices = get_disk_paths_from_storage_config(70 devices = get_device_paths_from_storage_config(
71 extract_storage_ordered_dict(cfg))71 extract_storage_ordered_dict(cfg))
72 if len(devices) == 0:72 if len(devices) == 0:
73 devices = cfg.get('block-meta', {}).get('devices', [])73 devices = cfg.get('block-meta', {}).get('devices', [])
@@ -1535,20 +1535,24 @@ def zfs_handler(info, storage_config):
1535 util.write_file(state['fstab'], fstab_entry, omode='a')1535 util.write_file(state['fstab'], fstab_entry, omode='a')
15361536
15371537
1538def get_disk_paths_from_storage_config(storage_config):1538def get_device_paths_from_storage_config(storage_config):
1539 """Returns a list of disk paths in a storage config filtering out1539 """Returns a list of device paths in a storage config filtering out
1540 preserved or disks which do not have wipe configuration.1540 preserved or devices which do not have wipe configuration.
15411541
1542 :param: storage_config: Ordered dict of storage configation1542 :param: storage_config: Ordered dict of storage configation
1543 """1543 """
1544 def get_path_if_present(disk, config):1544 dpaths = []
1545 return get_path_to_storage_volume(disk, config)1545 for (k, v) in storage_config.items():
15461546 if v.get('type') in ['disk', 'partition']:
1547 return [get_path_if_present(k, storage_config)1547 if config.value_as_boolean(v.get('wipe')):
1548 for (k, v) in storage_config.items()1548 if config.value_as_boolean(v.get('preserve')):
1549 if v.get('type') == 'disk' and1549 continue
1550 config.value_as_boolean(v.get('wipe')) and1550 try:
1551 not config.value_as_boolean(v.get('preserve'))]1551 dpaths.append(
1552 get_path_to_storage_volume(k, storage_config))
1553 except Exception:
1554 pass
1555 return dpaths
15521556
15531557
1554def zfsroot_update_storage_config(storage_config):1558def zfsroot_update_storage_config(storage_config):
diff --git a/examples/tests/reuse-raid-member-partition.yaml b/examples/tests/reuse-raid-member-partition.yaml
1555new file mode 1006441559new file mode 100644
index 0000000..3fe2d83
--- /dev/null
+++ b/examples/tests/reuse-raid-member-partition.yaml
@@ -0,0 +1,73 @@
1showtrace: true
2
3# The point of this test is to test installing to a partition that used to
4# be a RAID member where the other disks that used to be part of the
5# RAID are not present (the scenario that the disk was just grabbed
6# out of a pile of previously used disks and shoved into a server).
7
8# So what it does is to create a RAID0 out of two partition from two
9# disks, stop the RAID, wipe one of the disks and then install to the
10# other, reusing the partition that was part of the RAID.
11
12bucket:
13 - &setup |
14 parted /dev/disk/by-id/virtio-disk-a --script -- \
15 mklabel gpt \
16 mkpart primary 1GiB 2GiB \
17 mkpart primary 2GiB 9GiB
18 parted /dev/disk/by-id/virtio-disk-b --script -- \
19 mklabel gpt \
20 mkpart primary 2GiB 9GiB
21 udevadm settle
22 mdadm --create --metadata 1.2 --level 0 -n 2 /dev/md1 --assume-clean \
23 /dev/disk/by-id/virtio-disk-a-part2 /dev/disk/by-id/virtio-disk-b-part1
24 udevadm settle
25 mdadm --stop /dev/md1
26 udevadm settle
27 mdadm --zero-superblock /dev/disk/by-id/virtio-disk-b-part1
28 wipefs -a /dev/disk/by-id/virtio-disk-b
29 udevadm settle
30
31early_commands:
32 00-setup-raid: [sh, -exuc, *setup]
33
34storage:
35 config:
36 - type: disk
37 id: id_disk0
38 serial: disk-a
39 ptable: gpt
40 preserve: true
41 - type: disk
42 id: id_disk1
43 serial: disk-b
44 - type: partition
45 id: id_disk0_part1
46 preserve: true
47 device: id_disk0
48 flag: boot
49 number: 1
50 size: 1G
51 - type: partition
52 id: id_disk0_part2
53 preserve: true
54 device: id_disk0
55 number: 2
56 size: 7G
57 - type: format
58 id: id_efi_format
59 volume: id_disk0_part1
60 fstype: fat32
61 - type: format
62 id: id_root_format
63 volume: id_disk0_part2
64 fstype: ext4
65 - type: mount
66 device: id_root_format
67 id: id_root_mount
68 path: /
69 - type: mount
70 id: id_efi_mount
71 device: id_efi_format
72 path: /boot/efi
73 version: 1
diff --git a/examples/tests/reuse-raid-member-wipe.yaml b/examples/tests/reuse-raid-member-wipe.yaml
0new file mode 10064474new file mode 100644
index 0000000..84a2686
--- /dev/null
+++ b/examples/tests/reuse-raid-member-wipe.yaml
@@ -0,0 +1,71 @@
1showtrace: true
2
3# The point of this test is to test installing to a disk that contains
4# a partition that used to be a RAID member where the other parts of
5# the RAID are not present (the scenario is that the disk was just
6# grabbed out of a pile of previously used disks and shoved into a
7# server).
8
9# So what it does is to create a RAID0 out of partitions on two disks,
10# stop the RAID, wipe the superblock on one of them and then install
11# to the other using a standard partitioning scheme.
12
13bucket:
14 - &setup |
15 parted /dev/disk/by-id/virtio-disk-a --script -- \
16 mklabel gpt \
17 mkpart primary 1GiB 9GiB
18 parted /dev/disk/by-id/virtio-disk-b --script -- \
19 mklabel gpt \
20 mkpart primary 1GiB 9GiB
21 udevadm settle
22 mdadm --create --metadata 1.2 --level 0 -n 2 /dev/md1 --assume-clean \
23 /dev/disk/by-id/virtio-disk-a-part1 /dev/disk/by-id/virtio-disk-b-part1
24 udevadm settle
25 mdadm --stop /dev/md1
26 udevadm settle
27 mdadm --zero-superblock /dev/disk/by-id/virtio-disk-b-part1
28 wipefs -a /dev/disk/by-id/virtio-disk-b
29 udevadm settle
30
31early_commands:
32 00-setup-raid: [sh, -exuc, *setup]
33
34storage:
35 config:
36 - type: disk
37 id: id_disk0
38 serial: disk-a
39 ptable: gpt
40 wipe: superblock-recursive
41 - type: disk
42 id: id_disk1
43 serial: disk-b
44 - type: partition
45 id: id_disk0_part1
46 device: id_disk0
47 flag: boot
48 number: 1
49 size: 512M
50 - type: partition
51 id: id_disk0_part2
52 device: id_disk0
53 number: 2
54 size: 8G
55 - type: format
56 id: id_efi_format
57 volume: id_disk0_part1
58 fstype: fat32
59 - type: format
60 id: id_root_format
61 volume: id_disk0_part2
62 fstype: ext4
63 - type: mount
64 device: id_root_format
65 id: id_root_mount
66 path: /
67 - type: mount
68 id: id_efi_mount
69 device: id_efi_format
70 path: /boot/efi
71 version: 1
diff --git a/tests/unittests/test_clear_holders.py b/tests/unittests/test_clear_holders.py
index e4d8f8d..906ae06 100644
--- a/tests/unittests/test_clear_holders.py
+++ b/tests/unittests/test_clear_holders.py
@@ -566,11 +566,17 @@ class TestClearHolders(CiTestCase):
566 for tree, result in test_trees_and_results:566 for tree, result in test_trees_and_results:
567 self.assertEqual(clear_holders.get_holder_types(tree), result)567 self.assertEqual(clear_holders.get_holder_types(tree), result)
568568
569 @mock.patch('curtin.block.clear_holders.os.path.exists')
569 @mock.patch('curtin.block.clear_holders.block.sys_block_path')570 @mock.patch('curtin.block.clear_holders.block.sys_block_path')
570 @mock.patch('curtin.block.clear_holders.gen_holders_tree')571 @mock.patch('curtin.block.clear_holders.gen_holders_tree')
571 def test_assert_clear(self, mock_gen_holders_tree, mock_syspath):572 def test_assert_clear(self, mock_gen_holders_tree, mock_syspath, m_ospe):
573 def my_sysblock(p, strict=False):
574 return '/sys/class/block/%s' % os.path.basename(p)
575
572 mock_gen_holders_tree.return_value = self.example_holders_trees[0][0]576 mock_gen_holders_tree.return_value = self.example_holders_trees[0][0]
573 mock_syspath.side_effect = lambda x: x577 mock_syspath.side_effect = my_sysblock
578 # os.path.exists set to True to include all devices in the call list
579 m_ospe.return_value = True
574 device = self.test_blockdev580 device = self.test_blockdev
575 with self.assertRaises(OSError):581 with self.assertRaises(OSError):
576 clear_holders.assert_clear(device)582 clear_holders.assert_clear(device)
diff --git a/tests/vmtests/test_reuse_raid_member.py b/tests/vmtests/test_reuse_raid_member.py
577new file mode 100644583new file mode 100644
index 0000000..3052c9c
--- /dev/null
+++ b/tests/vmtests/test_reuse_raid_member.py
@@ -0,0 +1,62 @@
1# This file is part of curtin. See LICENSE file for copyright and license info.
2
3from . import VMBaseClass
4from .releases import base_vm_classes as relbase
5
6
7class TestReuseRAIDMember(VMBaseClass):
8 """ Curtin can install to a RAID member if other members are missing. """
9 conf_file = "examples/tests/reuse-raid-member-wipe.yaml"
10 extra_disks = ['10G', '10G']
11 uefi = True
12
13 def test_simple(self):
14 pass
15
16
17class TestReuseRAIDMemberPartition(VMBaseClass):
18 """ Curtin can install to a RAID member if other members are missing. """
19 conf_file = "examples/tests/reuse-raid-member-wipe.yaml"
20 extra_disks = ['10G', '10G']
21 uefi = True
22
23 def test_simple(self):
24 pass
25
26
27class BionicTestReuseRAIDMember(relbase.bionic, TestReuseRAIDMember):
28 __test__ = True
29
30
31class CosmicTestReuseRAIDMember(relbase.cosmic, TestReuseRAIDMember):
32 __test__ = True
33
34
35class DiscoTestReuseRAIDMember(relbase.disco, TestReuseRAIDMember):
36 __test__ = True
37
38
39class EoanTestReuseRAIDMember(relbase.eoan, TestReuseRAIDMember):
40 __test__ = True
41
42
43class BionicTestReuseRAIDMemberPartition(
44 relbase.bionic, TestReuseRAIDMemberPartition):
45 __test__ = True
46
47
48class CosmicTestReuseRAIDMemberPartition(
49 relbase.cosmic, TestReuseRAIDMemberPartition):
50 __test__ = True
51
52
53class DiscoTestReuseRAIDMemberPartition(
54 relbase.disco, TestReuseRAIDMemberPartition):
55 __test__ = True
56
57
58class EoanTestReuseRAIDMemberPartition(
59 relbase.eoan, TestReuseRAIDMemberPartition):
60 __test__ = True
61
62# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches