Skip to content

Commit e0ecb42

Browse files
CA-393194: Fix pvremove failure
In lvmohba test, the test failed at `SR.destroy(sr_ref) -> removeVG -> pvremove ` with the lvm error message "No devices to process". The root cause is that the PV which was to be removed was not really used as PV when it comes to multipathed devices. The LVM official document says: "Since each LUN has multiple device nodes in /dev  that point to the same underlying data, they all contain the same LVM metadata and thus LVM commands will find the same metadata multiple times and report them as duplicates. These duplicate messages are only warnings and do not mean the LVM operation has failed. Rather, they are alerting the user that only one of the devices has been used as a physical volume and the others are being ignored."  Please refer to https://docs.redhat.com/en/documentation/ red_hat_enterprise_linux/6/html/logical_volume_manager_administration /duplicate_pv_multipath#duplicate_pv_multipath This fix is to find the real PV in a VG before removing the VG. Signed-off-by: Stephen Cheng <stephen.cheng@cloud.com>
1 parent 23be094 commit e0ecb42

File tree

2 files changed

+62
-1
lines changed

2 files changed

+62
-1
lines changed

drivers/lvutil.py

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -529,6 +529,27 @@ def createVG(root, vgname):
529529

530530
# End block
531531

532+
def getPVsInVG(vgname):
533+
# Get PVs in a specific VG
534+
pvs_ret = cmd_lvm([CMD_PVS, '--separator', ' ', '--noheadings', '-o', 'pv_name,vg_name'])
535+
536+
# Parse each line to extract PV and VG information
537+
# No need to handle exceptions here, return empty list if any error
538+
pvs_in_vg = []
539+
lines = pvs_ret.strip().split('\n')
540+
for line in lines:
541+
# To avoid invalid return format
542+
parts = line.split()
543+
if len(parts) != 2:
544+
util.SMlog("Warning: Invalid or empty line in pvs output: %s" % line)
545+
continue
546+
pv, vg = parts
547+
if vg == vgname:
548+
pvs_in_vg.append(pv)
549+
550+
util.SMlog("PVs in VG %s: %s" % (vgname, pvs_in_vg))
551+
return pvs_in_vg
552+
532553
def removeVG(root, vgname):
533554
# Check PVs match VG
534555
try:
@@ -542,9 +563,11 @@ def removeVG(root, vgname):
542563
opterr='error is %d' % inst.code)
543564

544565
try:
566+
# Get PVs in VG before removing the VG
567+
devs_in_vg = getPVsInVG(vgname)
545568
cmd_lvm([CMD_VGREMOVE, vgname])
546569

547-
for dev in root.split(','):
570+
for dev in devs_in_vg:
548571
cmd_lvm([CMD_PVREMOVE, dev])
549572
except util.CommandException as inst:
550573
raise xs_errors.XenError('LVMDelete', \

tests/test_lvutil.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,3 +355,41 @@ def test_warning_if_cmd_takes_too_long(self, m_smlog, m_time, _1, m_pread):
355355
self.assertIn("Long LVM call", m_smlog.call_args[0][0])
356356
self.assertIn(f"took {lvutil.MAX_OPERATION_DURATION*2}", m_smlog.call_args[0][0])
357357

358+
@mock.patch('lvutil.cmd_lvm')
359+
@mock.patch('util.SMlog', autospec=True)
360+
class TestGetPVsInVG(unittest.TestCase):
361+
362+
def test_pvs_in_vg(self, mock_smlog, mock_cmd_lvm):
363+
# Normal case
364+
mock_cmd_lvm.return_value = "pv1 vg1\npv2 vg1\npv3 vg2"
365+
result = lvutil.getPVsInVG("vg1")
366+
self.assertEqual(result, ["pv1", "pv2"])
367+
mock_smlog.assert_called_once_with("PVs in VG vg1: ['pv1', 'pv2']")
368+
369+
def test_no_pvs(self, mock_smlog, mock_cmd_lvm):
370+
# Test when no PVs are returned
371+
mock_cmd_lvm.return_value = ""
372+
result = lvutil.getPVsInVG("vg1")
373+
self.assertEqual(result, [])
374+
mock_smlog.assert_has_calls([
375+
mock.call("Warning: Invalid or empty line in pvs output: "),
376+
mock.call("PVs in VG vg1: []")
377+
])
378+
379+
def test_no_pvs_in_vg(self, mock_smlog, mock_cmd_lvm):
380+
# Test when no PVs belong to the specified VG
381+
mock_cmd_lvm.return_value = "pv1 vg2\npv2 vg2"
382+
result = lvutil.getPVsInVG("vg1")
383+
self.assertEqual(result, [])
384+
mock_smlog.assert_called_once_with("PVs in VG vg1: []")
385+
386+
def test_command_error(self, mock_smlog, mock_cmd_lvm):
387+
# Test invalid return value from cmd_lvm
388+
mock_cmd_lvm.return_value = "Invalid retrun value."
389+
result = lvutil.getPVsInVG("vg1")
390+
self.assertEqual(result, [])
391+
mock_smlog.assert_has_calls([
392+
mock.call("Warning: Invalid or empty line in pvs output: Invalid retrun value."),
393+
mock.call("PVs in VG vg1: []")
394+
])
395+
mock_smlog.assert_called_with("PVs in VG vg1: []")

0 commit comments

Comments
 (0)