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
1diff --git a/curtin/block/clear_holders.py b/curtin/block/clear_holders.py
2index 4ead97e..14ad644 100644
3--- a/curtin/block/clear_holders.py
4+++ b/curtin/block/clear_holders.py
5@@ -297,6 +297,10 @@ def wipe_superblock(device):
6 mpath_id = multipath.find_mpath_id(blockdev)
7 for mp_part_id in multipath.find_mpath_partitions(mpath_id):
8 multipath.remove_partition(mp_part_id)
9+ # handle /dev/sdX which are held by multipath layer
10+ if multipath.is_mpath_member(blockdev):
11+ LOG.debug('Skipping multipath partition path member: %s', blockdev)
12+ return
13
14 _wipe_superblock(blockdev)
15
16diff --git a/curtin/block/multipath.py b/curtin/block/multipath.py
17index f0dfbd2..a488e85 100644
18--- a/curtin/block/multipath.py
19+++ b/curtin/block/multipath.py
20@@ -55,6 +55,7 @@ def is_mpath_device(devpath, info=None):
21 if not info:
22 info = udev.udevadm_info(devpath)
23 if info.get('DM_UUID', '').startswith('mpath-'):
24+ LOG.debug('%s is multipath device', devpath)
25 return True
26
27 return False
28@@ -64,6 +65,7 @@ def is_mpath_member(devpath, info=None):
29 """ Check if a device is a multipath member (a path), returns boolean. """
30 try:
31 util.subp(['multipath', '-c', devpath], capture=True)
32+ LOG.debug('%s is multipath device member', devpath)
33 return True
34 except util.ProcessExecutionError:
35 return False
36diff --git a/curtin/commands/block_meta.py b/curtin/commands/block_meta.py
37index e3c4ce1..ac9f964 100644
38--- a/curtin/commands/block_meta.py
39+++ b/curtin/commands/block_meta.py
40@@ -455,6 +455,10 @@ def get_path_to_storage_volume(volume, storage_config):
41 # sys/class/block access is valid. ie, there are no
42 # udev generated values in sysfs
43 volume_path = os.path.realpath(vol_value)
44+ # convert /dev/sdX to /dev/mapper/mpathX value
45+ if multipath.is_mpath_member(volume_path):
46+ volume_path = '/dev/mapper/' + (
47+ multipath.get_mpath_id_from_device(volume_path))
48 elif disk_key == 'device_id':
49 dasd_device = dasd.DasdDevice(vol_value)
50 volume_path = dasd_device.devname
51diff --git a/examples/tests/multipath-lvm-part-wipe.yaml b/examples/tests/multipath-lvm-part-wipe.yaml
52new file mode 100644
53index 0000000..bfe39ea
54--- /dev/null
55+++ b/examples/tests/multipath-lvm-part-wipe.yaml
56@@ -0,0 +1,125 @@
57+showtrace: true
58+install:
59+ unmount: disabled
60+bucket:
61+ - &setup |
62+ export DEBIAN_FRONTEND=noninteractive
63+ aptopts="--quiet --assume-yes"
64+ aptopts="$aptopts --option=Dpkg::options::=--force-unsafe-io"
65+ aptopts="$aptopts --option=Dpkg::Options::=--force-confold"
66+ if ! command -v multipath; then
67+ eatmydata apt-get install -y $aptopts multipath-tools
68+ echo -e "defaults {\n user_friendly_names yes\n}" > /etc/multipath.conf
69+ udevadm trigger --subsystem-match=block --action=add
70+ udevadm settle
71+ multipath -v3 -R3 -r
72+ fi
73+ multipath -ll
74+ dmsetup remove --force --retry /dev/dm-0
75+ dmsetup ls
76+ multipath -v3 -R3 -f mpatha
77+ udevadm settle
78+ parted /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk-a --script -- \
79+ mklabel gpt \
80+ mkpart primary 1MiB 2MiB \
81+ set 1 bios_grub on \
82+ mkpart primary 3MiB 999MiB \
83+ set 2 boot on \
84+ mkpart primary 1000MiB 4099MiB
85+ udevadm settle
86+ ls -al /dev/disk/by-id
87+ vgcreate --force --zero=y --yes root_vg /dev/disk/by-id/scsi-0QEMU_QEMU_HARDDISK_disk-a-part3
88+ pvscan --cache
89+ vgscan --verbose --mknodes --cache
90+ udevadm settle
91+ for x in $(seq 1 10); do
92+ if vgs root_vg; then
93+ break;
94+ fi
95+ sleep 1
96+ done
97+ lvcreate root_vg --name lv1_root --zero=y --wipesignatures=y \
98+ --size 2684354560B
99+ udevadm settle
100+ mkfs.ext4 /dev/root_vg/lv1_root
101+ # stop lvm bits
102+ for vg in `pvdisplay -C --separator = -o vg_name --noheadings`; do
103+ vgchange -an $vg ||:
104+ done
105+ command -v systemctl && systemctl mask lvm2-pvscan\@.service
106+ rm -rf /etc/lvm/archive /etc/lvm/backup /run/lvm/*
107+ multipath -r
108+ udevadm settle
109+ dmsetup ls
110+ multipath -ll
111+ ls -al /dev/mapper/
112+ sleep 5
113+ ls -al /sys/class/block/dm-0/holders/
114+ /curtin/bin/curtin \
115+ -v --config /curtin/config/multipath-lvm-part-wipe.yaml \
116+ clear-holders --shutdown-plan
117+
118+
119+# Create a LVM now to test curtin's reuse of existing LVMs
120+early_commands:
121+ 00-setup-lvm: [bash, -exuc, *setup]
122+
123+storage:
124+ version: 1
125+ config:
126+ - id: main_disk
127+ type: disk
128+ ptable: gpt
129+ name: root_disk
130+ multipath: mpatha
131+ serial: disk-a
132+ path: /dev/sda
133+ grub_device: true
134+ wipe: superblock
135+ - id: boot_bios
136+ type: partition
137+ size: 1MB
138+ number: 1
139+ device: main_disk
140+ flag: bios_grub
141+ wipe: superblock
142+ - id: boot_partition
143+ type: partition
144+ size: 1GB
145+ number: 2
146+ device: main_disk
147+ wipe: superblock
148+ - id: main_disk_p3
149+ type: partition
150+ number: 3
151+ size: 4GB
152+ device: main_disk
153+ wipe: superblock
154+ - id: root_vg
155+ type: lvm_volgroup
156+ name: root_vg
157+ devices:
158+ - main_disk_p3
159+ - id: root_vg_lv1
160+ type: lvm_partition
161+ name: lv1_root
162+ size: 2.5G
163+ volgroup: root_vg
164+ - id: lv1_root_fs
165+ type: format
166+ fstype: ext4
167+ volume: root_vg_lv1
168+ - id: lvroot_mount
169+ path: /
170+ type: mount
171+ device: lv1_root_fs
172+ - fstype: ext4
173+ volume: boot_partition
174+ preserve: false
175+ type: format
176+ id: format-0
177+ - device: format-0
178+ path: /boot
179+ type: mount
180+ id: mount-0
181+
182diff --git a/tests/unittests/test_clear_holders.py b/tests/unittests/test_clear_holders.py
183index 767ae3a..f73b49d 100644
184--- a/tests/unittests/test_clear_holders.py
185+++ b/tests/unittests/test_clear_holders.py
186@@ -492,7 +492,7 @@ class TestClearHolders(CiTestCase):
187 mock_log, m_time,
188 mock_swap,
189 mock_mpath):
190- """wipe_superblock wipes dev with multipath enabled but inactive."""
191+ """wipe_superblock skips wiping multipath member path."""
192 mock_swap.return_value = False
193 mock_block.sysfs_to_devpath.return_value = self.test_blockdev
194 mock_block.is_extended_partition.return_value = False
195@@ -508,8 +508,7 @@ class TestClearHolders(CiTestCase):
196 ])
197 clear_holders.wipe_superblock(self.test_syspath)
198 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
199- mock_block.wipe_volume.assert_called_with(
200- self.test_blockdev, exclusive=True, mode='superblock', strict=True)
201+ self.assertEqual(0, mock_block.wipe_volume.call_count)
202
203 @mock.patch('curtin.block.clear_holders.multipath')
204 @mock.patch('curtin.block.clear_holders.is_swap_device')
205@@ -548,7 +547,7 @@ class TestClearHolders(CiTestCase):
206 mock_swap,
207 mock_mpath,
208 m_get_holders):
209- """wipe_superblock wipes mpath device and removes mp parts first."""
210+ """wipe_superblock wipes removes mp parts first and wipes dev."""
211 mock_swap.return_value = False
212 mock_block.sysfs_to_devpath.return_value = self.test_blockdev
213 mock_block.is_zfs_member.return_value = False
214@@ -563,12 +562,13 @@ class TestClearHolders(CiTestCase):
215 mock_block.get_sysfs_partitions.side_effect = iter([
216 [], # partitions are now gone
217 ])
218+ mock_mpath.is_mpath_member.return_value = False
219 m_get_holders.return_value = []
220 clear_holders.wipe_superblock(self.test_syspath)
221 mock_block.sysfs_to_devpath.assert_called_with(self.test_syspath)
222+ mock_mpath.remove_partition.assert_called_with('mpatha-part1')
223 mock_block.wipe_volume.assert_called_with(
224 self.test_blockdev, exclusive=True, mode='superblock', strict=True)
225- mock_mpath.remove_partition.assert_called_with('mpatha-part1')
226
227 @mock.patch('curtin.block.clear_holders.LOG')
228 @mock.patch('curtin.block.clear_holders.block')
229diff --git a/tests/unittests/test_curthooks.py b/tests/unittests/test_curthooks.py
230index 155c3ba..079984f 100644
231--- a/tests/unittests/test_curthooks.py
232+++ b/tests/unittests/test_curthooks.py
233@@ -584,8 +584,10 @@ class TestSetupGrub(CiTestCase):
234 self.target, '/dev/vdb'],),
235 self.mock_subp.call_args_list[0][0])
236
237+ @patch('curtin.commands.block_meta.multipath')
238 @patch('curtin.commands.curthooks.os.path.exists')
239- def test_uses_grub_install_on_storage_config(self, m_exists):
240+ def test_uses_grub_install_on_storage_config(self, m_exists, m_multipath):
241+ m_multipath.is_mpath_member.return_value = False
242 cfg = {
243 'storage': {
244 'version': 1,
245@@ -600,6 +602,7 @@ class TestSetupGrub(CiTestCase):
246 },
247 }
248 self.subp_output.append(('', ''))
249+ self.subp_output.append(('', ''))
250 m_exists.return_value = True
251 curthooks.setup_grub(cfg, self.target, osfamily=self.distro_family)
252 self.assertEquals(
253@@ -609,10 +612,12 @@ class TestSetupGrub(CiTestCase):
254 self.target, '/dev/vdb'],),
255 self.mock_subp.call_args_list[0][0])
256
257+ @patch('curtin.commands.block_meta.multipath')
258 @patch('curtin.block.is_valid_device')
259 @patch('curtin.commands.curthooks.os.path.exists')
260 def test_uses_grub_install_on_storage_config_uefi(
261- self, m_exists, m_is_valid_device):
262+ self, m_exists, m_is_valid_device, m_multipath):
263+ m_multipath.is_mpath_member.return_value = False
264 self.mock_is_uefi_bootable.return_value = True
265 cfg = {
266 'storage': {
267diff --git a/tests/vmtests/test_multipath_lvm.py b/tests/vmtests/test_multipath_lvm.py
268index c2a5d18..39b8587 100644
269--- a/tests/vmtests/test_multipath_lvm.py
270+++ b/tests/vmtests/test_multipath_lvm.py
271@@ -64,4 +64,13 @@ class FocalTestMultipathLvm(relbase.focal, TestMultipathLvmAbs):
272 __test__ = True
273
274
275+class TestMultipathLvmPartWipeAbs(TestMultipathLvmAbs):
276+ conf_file = "examples/tests/multipath-lvm-part-wipe.yaml"
277+
278+
279+class FocalTestMultipathLvmPartWipe(relbase.focal,
280+ TestMultipathLvmPartWipeAbs):
281+ __test__ = True
282+
283+
284 # vi: ts=4 expandtab syntax=python

Subscribers

People subscribed via source and target branches