diff options
Diffstat (limited to 'cloudinit/config')
-rw-r--r-- | cloudinit/config/cc_growpart.py | 83 |
1 files changed, 80 insertions, 3 deletions
diff --git a/cloudinit/config/cc_growpart.py b/cloudinit/config/cc_growpart.py index 9f338ad1..6399bfb7 100644 --- a/cloudinit/config/cc_growpart.py +++ b/cloudinit/config/cc_growpart.py @@ -68,7 +68,9 @@ import os import os.path import re import stat +import platform +from functools import lru_cache from cloudinit import log as logging from cloudinit.settings import PER_ALWAYS from cloudinit import subp @@ -93,6 +95,58 @@ 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=<lvmuuid> and also DM_VG_NAME + return 'DM_LV_NAME=' in out + else: + LOG.info("Not an LVM Logical Volume partition") + return False + + +@lru_cache() +def get_pvs_for_lv(devpath): + myenv = {'LANG': 'C'} + + 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", "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): resize_class = None if mode == "auto": @@ -208,13 +262,18 @@ 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 # 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) + # 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 + bname = os.path.basename(rpath) syspath = "/sys/class/block/%s" % bname @@ -244,7 +303,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 def devent2dev(devent): @@ -294,8 +353,9 @@ def resize_devices(resizer, devices): "device '%s' not a block device" % blockdev,)) continue + is_lvm = is_lvm_lv(blockdev) try: - (disk, ptnum) = 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),)) @@ -316,6 +376,23 @@ def resize_devices(resizer, devices): "failed to resize: disk=%s, ptnum=%s: %s" % (disk, ptnum, e),)) + 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'}) + info.append((devent, RESIZE.CHANGED, + "Logical Volume %s extended" % devices[0],)) + else: + 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" % + (blockdev, e),)) + return info |