From a3be4b500938c2f708cd15f24d599f696435b02a Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Wed, 2 Dec 2020 16:07:40 +0100 Subject: [PATCH 1/9] Add support to resize rootfs if using LVM This patch adds support to resize a single partition of a VM if it's using an LVM underneath. The patch detects if it's LVM if the given block device is a device mapper by its name (e.g. /dev/dm-1) and if it has slave devices under it on sysfs. After that syspath is updated to the real block device and growpart will be called to resize it (and automatically its Physical Volume). The Volume Group will be updated automatically and a final call to extend the rootfs to the remaining space available will be made. Using the same growpart configuration, the user can specify only one device to be resized when using LVM and growpart, otherwise cloud-init won't know which one should be resized and will fail. rhbz: #1810878 Signed-off-by: Eduardo Otubo --- cloudinit/config/cc_growpart.py | 48 ++++++++++++++- doc/examples/cloud-config-growpart.txt | 2 + .../test_handler/test_handler_growpart.py | 58 ++++++++++++++++++- 3 files changed, 104 insertions(+), 4 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 9f338ad15a9..f9e6c6a218b 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -210,11 +210,35 @@ def get_size(filename): def device_part_info(devpath): # convert an entry in /dev/ to parent disk and partition number + myenv = os.environ.copy() + myenv['LANG'] = 'C' # input of /dev/vdb or /dev/disk/by-label/foo # rpath is hopefully a real-ish path in /dev (vda, sdb..) rpath = os.path.realpath(devpath) + is_lvm = True if re.search('.*dm.*', rpath) else False + if is_lvm: + try: + (out, _err) = subp.subp(["lvm", "lvs", devpath, "-o", "vgname", + "--noheadings"], env=myenv) + vgname = out.strip() + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Volume Group information " + "from %s", devpath) + raise ResizeFailedException(e) from e + + try: + (out, err) = subp.subp(["lvm", "vgs", vgname.strip(), "-o", + "pvname", "--noheadings"], env=myenv) + rpath = out.strip() + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Physical Volume " + "information from Volume Group %s", vgname) + raise ResizeFailedException(e) from e + bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname @@ -223,7 +247,7 @@ def device_part_info(devpath): if util.is_FreeBSD(): freebsd_part = "/dev/" + util.find_freebsd_part(devpath) m = re.search('^(/dev/.+)p([0-9])$', freebsd_part) - return (m.group(1), m.group(2)) + return (m.group(1), m.group(2), is_lvm) if not os.path.exists(syspath): raise ValueError("%s had no syspath (%s)" % (devpath, syspath)) @@ -244,7 +268,7 @@ def device_part_info(devpath): # diskdevpath has something like 253:0 # and udev has put links in /dev/block/253:0 to the device name in /dev/ - return (diskdevpath, ptnum) + return (diskdevpath, ptnum, is_lvm) def devent2dev(devent): @@ -272,6 +296,8 @@ def devent2dev(devent): def resize_devices(resizer, devices): # returns a tuple of tuples containing (entry-in-devices, action, message) + myenv = os.environ.copy() + myenv['LANG'] = 'C' info = [] for devent in devices: try: @@ -295,7 +321,7 @@ def resize_devices(resizer, devices): continue try: - (disk, ptnum) = device_part_info(blockdev) + (disk, ptnum, is_lvm) = device_part_info(blockdev) except (TypeError, ValueError) as e: info.append((devent, RESIZE.SKIPPED, "device_part_info(%s) failed: %s" % (blockdev, e),)) @@ -316,6 +342,22 @@ def resize_devices(resizer, devices): "failed to resize: disk=%s, ptnum=%s: %s" % (disk, ptnum, e),)) + if is_lvm and type(resizer).__name__ == "ResizeGrowPart": + try: + if len(devices) == 1: + (_out, _err) = subp.subp(["lvm", "lvextend", "---extents", + "100%FREE", blockdev], env=myenv) + info.append((devent, RESIZE.CHANGED, + "Logical Volume %s extended" % devices[0],)) + else: + raise ValueError("Exactly one device should be configured " + "to be resized when using LVM. More than " + "one configured: %s" % devices) + except (subp.ProcessExecutionError, ValueError) as e: + info.append((devent, RESIZE.NOCHANGE, + "Logical Volume %s resize failed: %s" % + (blockdev, e),)) + return info diff --git a/doc/examples/cloud-config-growpart.txt b/doc/examples/cloud-config-growpart.txt index 393d5164883..092681172f9 100644 --- a/doc/examples/cloud-config-growpart.txt +++ b/doc/examples/cloud-config-growpart.txt @@ -13,6 +13,8 @@ # # devices: # a list of things to resize. +# if the devices are under LVM, the list should be a single entry, +# cloud-init will then extend the single entry, otherwise it will fail. # items can be filesystem paths or devices (in /dev) # examples: # devices: [/, /dev/vdb1] diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py index 7f039b79969..f5933b0ee70 100644 --- a/tests/unittests/test_handler/test_handler_growpart.py +++ b/tests/unittests/test_handler/test_handler_growpart.py @@ -172,6 +172,53 @@ def setUp(self): self.name = "growpart" self.log = logging.getLogger("TestResize") + def test_lvm_resize(self): + # LVM resize should work only if a single device is configured. More + # than one device should fail. + lvm_pass = ["/dev/XXdm-0"] + lvm_fail = ["/dev/XXdm-1", "/dev/YYdm-1"] + devstat_ret = Bunch(st_mode=25008, st_ino=6078, st_dev=5, + st_nlink=1, st_uid=0, st_gid=6, st_size=0, + st_atime=0, st_mtime=0, st_ctime=0) + real_stat = os.stat + resize_calls = [] + + class myresizer(object): + def resize(self, diskdev, partnum, partdev): + resize_calls.append((diskdev, partnum, partdev)) + if partdev == "/dev/XXdm-0": + return (1024, 2048) + return (1024, 1024) # old size, new size + + def mystat(path): + if path in lvm_pass or path in lvm_fail: + return devstat_ret + return real_stat(path) + + try: + opinfo = cc_growpart.device_part_info + cc_growpart.device_part_info = simple_device_part_info_lvm + os.stat = mystat + + resized = cc_growpart.resize_devices(myresizer(), lvm_pass) + not_resized = cc_growpart.resize_devices(myresizer(), lvm_fail) + + def find(name, res): + for f in res: + if f[0] == name: + return f + return None + + self.assertEqual(cc_growpart.RESIZE.CHANGED, + find("/dev/XXdm-0", resized)[1]) + self.assertEqual(cc_growpart.RESIZE.NOCHANGE, + find("/dev/XXdm-1", not_resized)[1]) + self.assertEqual(cc_growpart.RESIZE.NOCHANGE, + find("/dev/YYdm-1", not_resized)[1]) + finally: + cc_growpart.device_part_info = opinfo + os.stat = real_stat + def test_simple_devices(self): # test simple device list # this patches out devent2dev, os.stat, and device_part_info @@ -227,10 +274,19 @@ def find(name, res): os.stat = real_stat +def simple_device_part_info_lvm(devpath): + # simple stupid return (/dev/vda, 1) for /dev/vda + ret = re.search("([^0-9]*)([0-9]*)$", devpath) + is_lvm = True + x = (ret.group(1), ret.group(2), is_lvm) + return x + + def simple_device_part_info(devpath): # simple stupid return (/dev/vda, 1) for /dev/vda ret = re.search("([^0-9]*)([0-9]*)$", devpath) - x = (ret.group(1), ret.group(2)) + is_lvm = False + x = (ret.group(1), ret.group(2), is_lvm) return x From a8c509ad34418e949a9659cf50de171783b676e7 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 10 Dec 2020 15:01:59 +0100 Subject: [PATCH 2/9] Fixing pylint unused variable --- cloudinit/config/cc_growpart.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index f9e6c6a218b..40b81b678dd 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -230,7 +230,7 @@ def device_part_info(devpath): raise ResizeFailedException(e) from e try: - (out, err) = subp.subp(["lvm", "vgs", vgname.strip(), "-o", + (out, _err) = subp.subp(["lvm", "vgs", vgname.strip(), "-o", "pvname", "--noheadings"], env=myenv) rpath = out.strip() except subp.ProcessExecutionError as e: From fc09687529a90d60055c630fe73f089abf79e768 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Fri, 22 Jan 2021 14:59:44 +0100 Subject: [PATCH 3/9] Resolving all comments on the first pull request: * Moved LVM detection and PV information to its own function * Other small fixes * Adjusted the tests for the new functions --- cloudinit/config/cc_growpart.py | 90 ++++++++++++------- .../test_handler/test_handler_growpart.py | 10 +-- 2 files changed, 63 insertions(+), 37 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 40b81b678dd..07556f9fec2 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -69,6 +69,7 @@ import re import stat +from functools import lru_cache from cloudinit import log as logging from cloudinit.settings import PER_ALWAYS from cloudinit import subp @@ -93,6 +94,53 @@ class RESIZE(object): LOG = logging.getLogger(__name__) +@lru_cache() +def is_lvm_lv(devpath): + if util.is_Linux(): + # all lvm lvs will have a realpath as a 'dm-*' name. + rpath = os.path.realpath(devpath) + if not os.path.basename(rpath).startswith("dm-"): + return False + out, _ = subp.subp("udevadm", "info", devpath) + # lvs should have DM_LV_NAME= and also DM_VG_NAME + return 'DM_LV_NAME=' in out + else: + return False + + +@lru_cache() +def get_pvs_for_lv(devpath): + myenv = {'LANG': 'C'} + + if util.is_Linux(): + try: + (out, _err) = subp.subp(["lvm", "lvs", devpath, "--options=vgname", + "--noheadings"], update_env=myenv) + vgname = out.strip() + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Volume Group information " + "from %s", devpath) + raise ResizeFailedException(e) from e + + try: + (out, _err) = subp.subp(["lvm", "vgs", vgname, "--options=pvname", + "--noheadings"], update_env=myenv) + pvs = [p.strip() for p in out.splitlines()] + if len(pvs) > 1: + util.logexc(LOG, "Do not know how to resize multiple Physical" + " Volumes") + else: + return pvs[0] + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Physical Volume " + "information from Volume Group %s", vgname) + raise ResizeFailedException(e) from e + else: + return False + + def resizer_factory(mode): resize_class = None if mode == "auto": @@ -208,36 +256,16 @@ def get_size(filename): os.close(fd) -def device_part_info(devpath): +def device_part_info(devpath, is_lvm): # convert an entry in /dev/ to parent disk and partition number - myenv = os.environ.copy() - myenv['LANG'] = 'C' # input of /dev/vdb or /dev/disk/by-label/foo # rpath is hopefully a real-ish path in /dev (vda, sdb..) rpath = os.path.realpath(devpath) - is_lvm = True if re.search('.*dm.*', rpath) else False + # first check if this is an LVM and get its PVs if is_lvm: - try: - (out, _err) = subp.subp(["lvm", "lvs", devpath, "-o", "vgname", - "--noheadings"], env=myenv) - vgname = out.strip() - except subp.ProcessExecutionError as e: - if e.exit_code != 0: - util.logexc(LOG, "Failed: can't get Volume Group information " - "from %s", devpath) - raise ResizeFailedException(e) from e - - try: - (out, _err) = subp.subp(["lvm", "vgs", vgname.strip(), "-o", - "pvname", "--noheadings"], env=myenv) - rpath = out.strip() - except subp.ProcessExecutionError as e: - if e.exit_code != 0: - util.logexc(LOG, "Failed: can't get Physical Volume " - "information from Volume Group %s", vgname) - raise ResizeFailedException(e) from e + rpath = get_pvs_for_lv(devpath) bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname @@ -247,7 +275,7 @@ def device_part_info(devpath): if util.is_FreeBSD(): freebsd_part = "/dev/" + util.find_freebsd_part(devpath) m = re.search('^(/dev/.+)p([0-9])$', freebsd_part) - return (m.group(1), m.group(2), is_lvm) + return (m.group(1), m.group(2)) if not os.path.exists(syspath): raise ValueError("%s had no syspath (%s)" % (devpath, syspath)) @@ -268,7 +296,7 @@ def device_part_info(devpath): # diskdevpath has something like 253:0 # and udev has put links in /dev/block/253:0 to the device name in /dev/ - return (diskdevpath, ptnum, is_lvm) + return diskdevpath, ptnum def devent2dev(devent): @@ -296,8 +324,6 @@ def devent2dev(devent): def resize_devices(resizer, devices): # returns a tuple of tuples containing (entry-in-devices, action, message) - myenv = os.environ.copy() - myenv['LANG'] = 'C' info = [] for devent in devices: try: @@ -320,8 +346,9 @@ def resize_devices(resizer, devices): "device '%s' not a block device" % blockdev,)) continue + is_lvm = is_lvm_lv(blockdev) try: - (disk, ptnum, is_lvm) = device_part_info(blockdev) + disk, ptnum = device_part_info(blockdev, is_lvm) except (TypeError, ValueError) as e: info.append((devent, RESIZE.SKIPPED, "device_part_info(%s) failed: %s" % (blockdev, e),)) @@ -342,11 +369,12 @@ def resize_devices(resizer, devices): "failed to resize: disk=%s, ptnum=%s: %s" % (disk, ptnum, e),)) - if is_lvm and type(resizer).__name__ == "ResizeGrowPart": + if is_lvm and isinstance(resizer, ResizeGrowPart): try: if len(devices) == 1: - (_out, _err) = subp.subp(["lvm", "lvextend", "---extents", - "100%FREE", blockdev], env=myenv) + (_out, _err) = subp.subp(["lvm", "lvextend", "--extents=", + "100%FREE", blockdev], + update_env={'LANG': 'C'}) info.append((devent, RESIZE.CHANGED, "Logical Volume %s extended" % devices[0],)) else: diff --git a/tests/unittests/test_handler/test_handler_growpart.py b/tests/unittests/test_handler/test_handler_growpart.py index f5933b0ee70..cc0a9248e63 100644 --- a/tests/unittests/test_handler/test_handler_growpart.py +++ b/tests/unittests/test_handler/test_handler_growpart.py @@ -274,19 +274,17 @@ def find(name, res): os.stat = real_stat -def simple_device_part_info_lvm(devpath): +def simple_device_part_info_lvm(devpath, is_lvm): # simple stupid return (/dev/vda, 1) for /dev/vda ret = re.search("([^0-9]*)([0-9]*)$", devpath) - is_lvm = True - x = (ret.group(1), ret.group(2), is_lvm) + x = (ret.group(1), ret.group(2)) return x -def simple_device_part_info(devpath): +def simple_device_part_info(devpath, is_lvm): # simple stupid return (/dev/vda, 1) for /dev/vda ret = re.search("([^0-9]*)([0-9]*)$", devpath) - is_lvm = False - x = (ret.group(1), ret.group(2), is_lvm) + x = (ret.group(1), ret.group(2)) return x From c48f3c56d2db79abd39a82876bb815e6a2faa3f7 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Wed, 27 Jan 2021 11:07:24 +0100 Subject: [PATCH 4/9] Final fixes from reviews --- cloudinit/config/cc_growpart.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 07556f9fec2..ad9814c1561 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -372,13 +372,13 @@ def resize_devices(resizer, devices): if is_lvm and isinstance(resizer, ResizeGrowPart): try: if len(devices) == 1: - (_out, _err) = subp.subp(["lvm", "lvextend", "--extents=", - "100%FREE", blockdev], - update_env={'LANG': 'C'}) + (_out, _err) = subp.subp( + ["lvm", "lvextend", "--extents=100%FREE", blockdev], + update_env={'LANG': 'C'}) info.append((devent, RESIZE.CHANGED, "Logical Volume %s extended" % devices[0],)) else: - raise ValueError("Exactly one device should be configured " + util.logexc(LOG, "Exactly one device should be configured " "to be resized when using LVM. More than " "one configured: %s" % devices) except (subp.ProcessExecutionError, ValueError) as e: From 700c111b283937468cb799c0bd1aca10a3c83d5c Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Wed, 17 Mar 2021 15:45:16 +0100 Subject: [PATCH 5/9] Changing from warning to info on get_pvs_for_lv() and minor fixes --- cloudinit/config/cc_growpart.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index ad9814c1561..0077c9cf9f7 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -68,6 +68,7 @@ import os.path import re import stat +import platform from functools import lru_cache from cloudinit import log as logging @@ -128,8 +129,8 @@ def get_pvs_for_lv(devpath): "--noheadings"], update_env=myenv) pvs = [p.strip() for p in out.splitlines()] if len(pvs) > 1: - util.logexc(LOG, "Do not know how to resize multiple Physical" - " Volumes") + LOG.info("Do not know how to resize multiple Physical" + " Volumes") else: return pvs[0] except subp.ProcessExecutionError as e: @@ -138,6 +139,8 @@ def get_pvs_for_lv(devpath): "information from Volume Group %s", vgname) raise ResizeFailedException(e) from e else: + LOG.warning("No support for get_pvs_for_lv on %s", + platform.system()) return False @@ -264,8 +267,9 @@ def device_part_info(devpath, is_lvm): rpath = os.path.realpath(devpath) # first check if this is an LVM and get its PVs - if is_lvm: - rpath = get_pvs_for_lv(devpath) + lvm_rpath = get_pvs_for_lv(devpath) + if is_lvm and lvm_rpath: + rpath = lvm_rpath bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname From abaa2b088aee5608afbc6062b920416bdae21bc8 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Fri, 19 Mar 2021 16:38:31 +0100 Subject: [PATCH 6/9] Hopefully last round of fixes :-D --- cloudinit/config/cc_growpart.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 0077c9cf9f7..bb85179b4e9 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -139,9 +139,9 @@ def get_pvs_for_lv(devpath): "information from Volume Group %s", vgname) raise ResizeFailedException(e) from e else: - LOG.warning("No support for get_pvs_for_lv on %s", - platform.system()) - return False + LOG.info("No support for get_pvs_for_lv on %s", + platform.system()) + return None def resizer_factory(mode): @@ -382,9 +382,9 @@ def resize_devices(resizer, devices): info.append((devent, RESIZE.CHANGED, "Logical Volume %s extended" % devices[0],)) else: - util.logexc(LOG, "Exactly one device should be configured " - "to be resized when using LVM. More than " - "one configured: %s" % devices) + LOG.info("Exactly one device should be configured to be " + "resized when using LVM. More than one configured" + ": %s", devices) except (subp.ProcessExecutionError, ValueError) as e: info.append((devent, RESIZE.NOCHANGE, "Logical Volume %s resize failed: %s" % From 504a2556ac587a17af399a0789e457b9eecd052d Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 25 Mar 2021 11:31:55 +0100 Subject: [PATCH 7/9] Checking if there's LVM present first to avoid stack tracing --- cloudinit/config/cc_growpart.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index bb85179b4e9..2e574f94743 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -97,7 +97,7 @@ class RESIZE(object): @lru_cache() def is_lvm_lv(devpath): - if util.is_Linux(): + if util.is_Linux() and subp.which('lvm'): # all lvm lvs will have a realpath as a 'dm-*' name. rpath = os.path.realpath(devpath) if not os.path.basename(rpath).startswith("dm-"): @@ -267,9 +267,10 @@ def device_part_info(devpath, is_lvm): rpath = os.path.realpath(devpath) # first check if this is an LVM and get its PVs - lvm_rpath = get_pvs_for_lv(devpath) - if is_lvm and lvm_rpath: - rpath = lvm_rpath + if is_lvm: + lvm_rpath = get_pvs_for_lv(devpath) + if lvm_rpath: + rpath = lvm_rpath bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname From c6ca230160b7f5fea9a3136d944c7a9f4aabf1ba Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 25 Mar 2021 11:38:19 +0100 Subject: [PATCH 8/9] Adding better logging on function is_lvm_lv() and moving lvm command check to get_pvs_for_lv --- cloudinit/config/cc_growpart.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 2e574f94743..da7dd2f9bc5 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -97,7 +97,7 @@ class RESIZE(object): @lru_cache() def is_lvm_lv(devpath): - if util.is_Linux() and subp.which('lvm'): + if util.is_Linux(): # all lvm lvs will have a realpath as a 'dm-*' name. rpath = os.path.realpath(devpath) if not os.path.basename(rpath).startswith("dm-"): @@ -106,6 +106,7 @@ def is_lvm_lv(devpath): # lvs should have DM_LV_NAME= and also DM_VG_NAME return 'DM_LV_NAME=' in out else: + LOG.info("Not an LVM Logical Volume partition") return False @@ -113,7 +114,7 @@ def is_lvm_lv(devpath): def get_pvs_for_lv(devpath): myenv = {'LANG': 'C'} - if util.is_Linux(): + if util.is_Linux() and subp.which('lvm'): try: (out, _err) = subp.subp(["lvm", "lvs", devpath, "--options=vgname", "--noheadings"], update_env=myenv) @@ -139,7 +140,7 @@ def get_pvs_for_lv(devpath): "information from Volume Group %s", vgname) raise ResizeFailedException(e) from e else: - LOG.info("No support for get_pvs_for_lv on %s", + LOG.info("No support for LVM on %s or command 'lvm' not present", platform.system()) return None @@ -267,10 +268,9 @@ def device_part_info(devpath, is_lvm): rpath = os.path.realpath(devpath) # first check if this is an LVM and get its PVs - if is_lvm: - lvm_rpath = get_pvs_for_lv(devpath) - if lvm_rpath: - rpath = lvm_rpath + lvm_rpath = get_pvs_for_lv(devpath) + if is_lvm and lvm_rpath: + rpath = lvm_rpath bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname From 1ff19556d38404cb643bb0be655005b5fedc6d44 Mon Sep 17 00:00:00 2001 From: Eduardo Otubo Date: Thu, 25 Mar 2021 14:57:58 +0100 Subject: [PATCH 9/9] Moving sanity check to the top of the function. --- cloudinit/config/cc_growpart.py | 58 +++++++++++++++++---------------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index da7dd2f9bc5..6399bfb79cf 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -114,35 +114,37 @@ def is_lvm_lv(devpath): def get_pvs_for_lv(devpath): myenv = {'LANG': 'C'} - if util.is_Linux() and subp.which('lvm'): - try: - (out, _err) = subp.subp(["lvm", "lvs", devpath, "--options=vgname", - "--noheadings"], update_env=myenv) - vgname = out.strip() - except subp.ProcessExecutionError as e: - if e.exit_code != 0: - util.logexc(LOG, "Failed: can't get Volume Group information " - "from %s", devpath) - raise ResizeFailedException(e) from e + if not util.is_Linux(): + LOG.info("No support for LVM on %s", platform.system()) + return None + if not subp.which('lvm'): + LOG.info("No 'lvm' command present") + return None - try: - (out, _err) = subp.subp(["lvm", "vgs", vgname, "--options=pvname", - "--noheadings"], update_env=myenv) - pvs = [p.strip() for p in out.splitlines()] - if len(pvs) > 1: - LOG.info("Do not know how to resize multiple Physical" - " Volumes") - else: - return pvs[0] - except subp.ProcessExecutionError as e: - if e.exit_code != 0: - util.logexc(LOG, "Failed: can't get Physical Volume " - "information from Volume Group %s", vgname) - raise ResizeFailedException(e) from e - else: - LOG.info("No support for LVM on %s or command 'lvm' not present", - platform.system()) - return None + try: + (out, _err) = subp.subp(["lvm", "lvs", devpath, "--options=vgname", + "--noheadings"], update_env=myenv) + vgname = out.strip() + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Volume Group information " + "from %s", devpath) + raise ResizeFailedException(e) from e + + try: + (out, _err) = subp.subp(["lvm", "vgs", vgname, "--options=pvname", + "--noheadings"], update_env=myenv) + pvs = [p.strip() for p in out.splitlines()] + if len(pvs) > 1: + LOG.info("Do not know how to resize multiple Physical" + " Volumes") + else: + return pvs[0] + except subp.ProcessExecutionError as e: + if e.exit_code != 0: + util.logexc(LOG, "Failed: can't get Physical Volume " + "information from Volume Group %s", vgname) + raise ResizeFailedException(e) from e def resizer_factory(mode):