summaryrefslogtreecommitdiff
path: root/cloudinit/config/cc_growpart.py
diff options
context:
space:
mode:
authorEduardo Otubo <otubo@redhat.com>2021-03-30 18:08:25 +0200
committerGitHub <noreply@github.com>2021-03-30 10:08:25 -0600
commit74fa008bfcd3263eb691cc0b3f7a055b17569f8b (patch)
tree35e37aefdcbd563189dca70bd3ac357b320a993c /cloudinit/config/cc_growpart.py
parent3b7e2e82310d417c0d59b268a6f47bc8f7996cab (diff)
downloadvyos-cloud-init-74fa008bfcd3263eb691cc0b3f7a055b17569f8b.tar.gz
vyos-cloud-init-74fa008bfcd3263eb691cc0b3f7a055b17569f8b.zip
Add support to resize rootfs if using LVM (#721)
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 LP: #1799953 Signed-off-by: Eduardo Otubo <otubo@redhat.com> Signed-off-by: Scott Moser <smoser@brickies.net>
Diffstat (limited to 'cloudinit/config/cc_growpart.py')
-rw-r--r--cloudinit/config/cc_growpart.py83
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