Merge ~raharper/curtin:fix/lvm-over-multipath-partition-wipe into curtin:master

Proposed by Ryan Harper
Status: Merged
Approved by: Ryan Harper
Approved revision: 2eca4183ebe4e1bdfecec20e3adc966bf2b08e16
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~raharper/curtin:fix/lvm-over-multipath-partition-wipe
Merge into: curtin:master
Diff against target: 284 lines (+156/-7)
7 files modified
curtin/block/clear_holders.py (+4/-0)
curtin/block/multipath.py (+2/-0)
curtin/commands/block_meta.py (+4/-0)
examples/tests/multipath-lvm-part-wipe.yaml (+125/-0)
tests/unittests/test_clear_holders.py (+5/-5)
tests/unittests/test_curthooks.py (+7/-2)
tests/vmtests/test_multipath_lvm.py (+9/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Dan Watkins (community) Approve
Review via email: mp+382605@code.launchpad.net

Commit message

lvm-over-multipath: handle lookups of multipath members

Subiquity provides a 'path' value for disks in storage config.
This triggered an edge case where curtin attempted to wipe the
underlying scsi disk *while* multipath is enabled resulting in
an error when attempting to get exclusive access to the disk.

This branch resolves this by checking if a disk or partition
is related to multipath and if so returning the device mapper
disk path.

LP: #1869075

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

This LGTM, thank you!

review: Approve
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 4ead97e..14ad644 100644
--- a/curtin/block/clear_holders.py
+++ b/curtin/block/clear_holders.py
@@ -297,6 +297,10 @@ def wipe_superblock(device):
297 mpath_id = multipath.find_mpath_id(blockdev)297 mpath_id = multipath.find_mpath_id(blockdev)
298 for mp_part_id in multipath.find_mpath_partitions(mpath_id):298 for mp_part_id in multipath.find_mpath_partitions(mpath_id):
299 multipath.remove_partition(mp_part_id)299 multipath.remove_partition(mp_part_id)
300 # handle /dev/sdX which are held by multipath layer
301 if multipath.is_mpath_member(blockdev):
302 LOG.debug('Skipping multipath partition path member: %s', blockdev)
303 return
300304
301 _wipe_superblock(blockdev)305 _wipe_superblock(blockdev)
302306
diff --git a/curtin/block/multipath.py b/curtin/block/multipath.py
index f0dfbd2..a488e85 100644
--- a/curtin/block/multipath.py
+++ b/curtin/block/multipath.py
@@ -55,6 +55,7 @@ def is_mpath_device(devpath, info=None):
55 if not info:55 if not info:
56 info = udev.udevadm_info(devpath)56 info = udev.udevadm_info(devpath)
57 if info.get('DM_UUID', '').startswith('mpath-'):57 if info.get('DM_UUID', '').startswith('mpath-'):
58 LOG.debug('%s is multipath device', devpath)
58 return True59 return True
5960
60 return False61 return False
@@ -64,6 +65,7 @@ def is_mpath_member(devpath, info=None):
64 """ Check if a device is a multipath member (a path), returns boolean. """65 """ Check if a device is a multipath member (a path), returns boolean. """
65 try:66 try:
66 util.subp(['multipath', '-c', devpath], capture=True)67 util.subp(['multipath', '-c', devpath], capture=True)
68 LOG.debug('%s is multipath device member', devpath)
67 return True69 return True
68 except util.ProcessExecutionError:70 except util.ProcessExecutionError:
69 return False71 return False
diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
index e3c4ce1..ac9f964 100644
--- a/curtin/commands/block_meta.py
+++ b/curtin/commands/block_meta.py
@@ -455,6 +455,10 @@ def get_path_to_storage_volume(volume, storage_config):
455 # sys/class/block access is valid. ie, there are no455 # sys/class/block access is valid. ie, there are no
456 # udev generated values in sysfs456 # udev generated values in sysfs
457 volume_path = os.path.realpath(vol_value)457 volume_path = os.path.realpath(vol_value)
458 # convert /dev/sdX to /dev/mapper/mpathX value
459 if multipath.is_mpath_member(volume_path):
460 volume_path = '/dev/mapper/' + (
461 multipath.get_mpath_id_from_device(volume_path))
458 elif disk_key == 'device_id':462 elif disk_key == 'device_id':
459 dasd_device = dasd.DasdDevice(vol_value)463 dasd_device = dasd.DasdDevice(vol_value)
460 volume_path = dasd_device.devname464 volume_path = dasd_device.devname
diff --git a/examples/tests/multipath-lvm-part-wipe.yaml b/examples/tests/multipath-lvm-part-wipe.yaml
461new file mode 100644465new file mode 100644
index 0000000..bfe39ea
--- /dev/null
+++ b/examples/tests/multipath-lvm-part-wipe.yaml
@@ -0,0 +1,125 @@
1showtrace: true
2install:
3 unmount: disabled
4bucket:
5 - &setup |
6 export DEBIAN_FRONTEND=noninteractive
7 aptopts="--quiet --assume-yes"
8 aptopts="$aptopts --option=Dpkg::options::=--force-unsafe-io"
9 aptopts="$aptopts --option=Dpkg::Options::=--force-confold"
10 if ! command -v multipath; then
11 eatmydata apt-get install -y $aptopts multipath-tools
12 echo -e "defaults {\n user_friendly_names yes\n}" > /etc/multipath.conf
13 udevadm trigger --subsystem-match=block --action=add
14 udevadm settle
15 multipath -v3 -R3 -r
16 fi
17 multipath -ll
18 dmsetup remove --force --retry /dev/dm-0
19 dmsetup ls
20 multipath -v3 -R3 -f mpatha
21 udevadm settle
22 parted /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk-a --script -- \
23 mklabel gpt \
24 mkpart primary 1MiB 2MiB \
25 set 1 bios_grub on \
26 mkpart primary 3MiB 999MiB \
27 set 2 boot on \
28 mkpart primary 1000MiB 4099MiB
29 udevadm settle
30 ls -al /dev/disk/by-id
31 vgcreate --force --zero=y --yes root_vg /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk-a-part3
32 pvscan --cache
33 vgscan --verbose --mknodes --cache
34 udevadm settle
35 for x in $(seq 1 10); do
36 if vgs root_vg; then
37 break;
38 fi
39 sleep 1
40 done
41 lvcreate root_vg --name lv1_root --zero=y --wipesignatures=y \
42 --size 2684354560B
43 udevadm settle
44 mkfs.ext4 /dev/root_vg/lv1_root
45 # stop lvm bits
46 for vg in `pvdisplay -C --separator = -o vg_name --noheadings`; do
47 vgchange -an $vg ||:
48 done
49 command -v systemctl && systemctl mask lvm2-pvscan\@.service
50 rm -rf /etc/lvm/archive /etc/lvm/backup /run/lvm/*
51 multipath -r
52 udevadm settle
53 dmsetup ls
54 multipath -ll
55 ls -al /dev/mapper/
56 sleep 5
57 ls -al /sys/class/block/dm-0/holders/
58 /curtin/bin/curtin \
59 -v --config /curtin/config/multipath-lvm-part-wipe.yaml \
60 clear-holders --shutdown-plan
61
62
63# Create a LVM now to test curtin's reuse of existing LVMs
64early_commands:
65 00-setup-lvm: [bash, -exuc, *setup]
66
67storage:
68 version: 1
69 config:
70 - id: main_disk
71 type: disk
72 ptable: gpt
73 name: root_disk
74 multipath: mpatha
75 serial: disk-a
76 path: /dev/sda
77 grub_device: true
78 wipe: superblock
79 - id: boot_bios
80 type: partition
81 size: 1MB
82 number: 1
83 device: main_disk
84 flag: bios_grub
85 wipe: superblock
86 - id: boot_partition
87 type: partition
88 size: 1GB
89 number: 2
90 device: main_disk
91 wipe: superblock
92 - id: main_disk_p3
93 type: partition
94 number: 3
95 size: 4GB
96 device: main_disk
97 wipe: superblock
98 - id: root_vg
99 type: lvm_volgroup
100 name: root_vg
101 devices:
102 - main_disk_p3
103 - id: root_vg_lv1
104 type: lvm_partition
105 name: lv1_root
106 size: 2.5G
107 volgroup: root_vg
108 - id: lv1_root_fs
109 type: format
110 fstype: ext4
111 volume: root_vg_lv1
112 - id: lvroot_mount
113 path: /
114 type: mount
115 device: lv1_root_fs
116 - fstype: ext4
117 volume: boot_partition
118 preserve: false
119 type: format
120 id: format-0
121 - device: format-0
122 path: /boot
123 type: mount
124 id: mount-0
125
diff --git a/tests/unittests/test_clear_holders.py b/tests/unittests/test_clear_holders.py
index 767ae3a..f73b49d 100644
--- a/tests/unittests/test_clear_holders.py
+++ b/tests/unittests/test_clear_holders.py
@@ -492,7 +492,7 @@ class TestClearHolders(CiTestCase):
492 mock_log, m_time,492 mock_log, m_time,
493 mock_swap,493 mock_swap,
494 mock_mpath):494 mock_mpath):
495 """wipe_superblock wipes dev with multipath enabled but inactive."""495 """wipe_superblock skips wiping multipath member path."""
496 mock_swap.return_value = False496 mock_swap.return_value = False
497 mock_block.sysfs_to_devpath.return_value = self.test_blockdev497 mock_block.sysfs_to_devpath.return_value = self.test_blockdev
498 mock_block.is_extended_partition.return_value = False498 mock_block.is_extended_partition.return_value = False
@@ -508,8 +508,7 @@ class TestClearHolders(CiTestCase):
508 ])508 ])
509 clear_holders.wipe_superblock(self.test_syspath)509 clear_holders.wipe_superblock(self.test_syspath)
510 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)510 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
511 mock_block.wipe_volume.assert_called_with(511 self.assertEqual(0, mock_block.wipe_volume.call_count)
512 self.test_blockdev, exclusive=True, mode='superblock', strict=True)
513512
514 @mock.patch('curtin.block.clear_holders.multipath')513 @mock.patch('curtin.block.clear_holders.multipath')
515 @mock.patch('curtin.block.clear_holders.is_swap_device')514 @mock.patch('curtin.block.clear_holders.is_swap_device')
@@ -548,7 +547,7 @@ class TestClearHolders(CiTestCase):
548 mock_swap,547 mock_swap,
549 mock_mpath,548 mock_mpath,
550 m_get_holders):549 m_get_holders):
551 """wipe_superblock wipes mpath device and removes mp parts first."""550 """wipe_superblock wipes removes mp parts first and wipes dev."""
552 mock_swap.return_value = False551 mock_swap.return_value = False
553 mock_block.sysfs_to_devpath.return_value = self.test_blockdev552 mock_block.sysfs_to_devpath.return_value = self.test_blockdev
554 mock_block.is_zfs_member.return_value = False553 mock_block.is_zfs_member.return_value = False
@@ -563,12 +562,13 @@ class TestClearHolders(CiTestCase):
563 mock_block.get_sysfs_partitions.side_effect = iter([562 mock_block.get_sysfs_partitions.side_effect = iter([
564 [], # partitions are now gone563 [], # partitions are now gone
565 ])564 ])
565 mock_mpath.is_mpath_member.return_value = False
566 m_get_holders.return_value = []566 m_get_holders.return_value = []
567 clear_holders.wipe_superblock(self.test_syspath)567 clear_holders.wipe_superblock(self.test_syspath)
568 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)568 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
569 mock_mpath.remove_partition.assert_called_with('mpatha-part1')
569 mock_block.wipe_volume.assert_called_with(570 mock_block.wipe_volume.assert_called_with(
570 self.test_blockdev, exclusive=True, mode='superblock', strict=True)571 self.test_blockdev, exclusive=True, mode='superblock', strict=True)
571 mock_mpath.remove_partition.assert_called_with('mpatha-part1')
572572
573 @mock.patch('curtin.block.clear_holders.LOG')573 @mock.patch('curtin.block.clear_holders.LOG')
574 @mock.patch('curtin.block.clear_holders.block')574 @mock.patch('curtin.block.clear_holders.block')
diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
index 155c3ba..079984f 100644
--- a/tests/unittests/test_curthooks.py
+++ b/tests/unittests/test_curthooks.py
@@ -584,8 +584,10 @@ class TestSetupGrub(CiTestCase):
584 self.target, '/dev/vdb'],),584 self.target, '/dev/vdb'],),
585 self.mock_subp.call_args_list[0][0])585 self.mock_subp.call_args_list[0][0])
586586
587 @patch('curtin.commands.block_meta.multipath')
587 @patch('curtin.commands.curthooks.os.path.exists')588 @patch('curtin.commands.curthooks.os.path.exists')
588 def test_uses_grub_install_on_storage_config(self, m_exists):589 def test_uses_grub_install_on_storage_config(self, m_exists, m_multipath):
590 m_multipath.is_mpath_member.return_value = False
589 cfg = {591 cfg = {
590 'storage': {592 'storage': {
591 'version': 1,593 'version': 1,
@@ -600,6 +602,7 @@ class TestSetupGrub(CiTestCase):
600 },602 },
601 }603 }
602 self.subp_output.append(('', ''))604 self.subp_output.append(('', ''))
605 self.subp_output.append(('', ''))
603 m_exists.return_value = True606 m_exists.return_value = True
604 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)607 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
605 self.assertEquals(608 self.assertEquals(
@@ -609,10 +612,12 @@ class TestSetupGrub(CiTestCase):
609 self.target, '/dev/vdb'],),612 self.target, '/dev/vdb'],),
610 self.mock_subp.call_args_list[0][0])613 self.mock_subp.call_args_list[0][0])
611614
615 @patch('curtin.commands.block_meta.multipath')
612 @patch('curtin.block.is_valid_device')616 @patch('curtin.block.is_valid_device')
613 @patch('curtin.commands.curthooks.os.path.exists')617 @patch('curtin.commands.curthooks.os.path.exists')
614 def test_uses_grub_install_on_storage_config_uefi(618 def test_uses_grub_install_on_storage_config_uefi(
615 self, m_exists, m_is_valid_device):619 self, m_exists, m_is_valid_device, m_multipath):
620 m_multipath.is_mpath_member.return_value = False
616 self.mock_is_uefi_bootable.return_value = True621 self.mock_is_uefi_bootable.return_value = True
617 cfg = {622 cfg = {
618 'storage': {623 'storage': {
diff --git a/tests/vmtests/test_multipath_lvm.py b/tests/vmtests/test_multipath_lvm.py
index c2a5d18..39b8587 100644
--- a/tests/vmtests/test_multipath_lvm.py
+++ b/tests/vmtests/test_multipath_lvm.py
@@ -64,4 +64,13 @@ class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
64 __test__ = True64 __test__ = True
6565
6666
67class TestMultipathLvmPartWipeAbs(TestMultipathLvmAbs):
68 conf_file = "examples/tests/multipath-lvm-part-wipe.yaml"
69
70
71class FocalTestMultipathLvmPartWipe(relbase.focal,
72 TestMultipathLvmPartWipeAbs):
73 __test__ = True
74
75
67# vi: ts=4 expandtab syntax=python76# vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches