From dc0b23c4f2585a6c8a52a9eab035059d6c078c6a Mon Sep 17 00:00:00 2001 From: David Sullivan Date: Wed, 24 Nov 2021 09:34:40 -0600 Subject: [PATCH] Support upgrades with additional partition types During the inital change to support upgrades to 30gb (https://review.opendev.org/c/starlingx/config/+/817967) we assumed every partition was assigned to the cgts-vg. This commit will add support for the other types of partitions that may be present on the system - nova-local and unassigned. We will prioritize resizing an unassigned partition first. If all partitions are assigned to a PV we will use the largest partition. Additionally an issue was found in DX systems with heterogenous controller configurations. If the rootfs for controller-1 is a different disk from controller-0 the initial script would fail when it tried to enumerate the partitions on that disk. We address this by not using the database to find the rootfs disk. We instead search for the device using findmnt and lsblk. Test Plan: AIO-SX: Test with unassigned and other partitions AIO-SX: Test with nova-local and cgts-vg partitions (larger nova-local) AIO-SX: Test with nova-local and cgts-vg partitions (larger cgts-vg) DX-Std: Test with heterogenous controller disks Story: 2008966 Task: 44056 Change-Id: Id9c4fccb4c955ff8ea5d0f238b0a3c7b677785b2 Signed-off-by: David Sullivan --- .../09-resize-for-platform-backup.py | 177 ++++++++---------- 1 file changed, 77 insertions(+), 100 deletions(-) diff --git a/controllerconfig/controllerconfig/upgrade-scripts/09-resize-for-platform-backup.py b/controllerconfig/controllerconfig/upgrade-scripts/09-resize-for-platform-backup.py index 774e8ba347..2608d01944 100644 --- a/controllerconfig/controllerconfig/upgrade-scripts/09-resize-for-platform-backup.py +++ b/controllerconfig/controllerconfig/upgrade-scripts/09-resize-for-platform-backup.py @@ -5,18 +5,18 @@ # import math -import operator import psycopg2 import sys import subprocess -from sysinv.common import constants -from psycopg2.extras import RealDictCursor from controllerconfig.common import log +from operator import itemgetter +from psycopg2.extras import RealDictCursor LOG = log.get_logger(__name__) BACKUP_GUID = 'ba5eba11-0000-1111-2222-000000000002' +SYSINV_GUID = 'ba5eba11-0000-1111-2222-000000000001' def main(): @@ -50,6 +50,7 @@ def main(): def adjust_backup_partition(): + installed_backup_size = get_backup_size() conn = psycopg2.connect("dbname=sysinv user=postgres") with conn: with conn.cursor(cursor_factory=RealDictCursor) as cur: @@ -66,18 +67,9 @@ def adjust_backup_partition(): cur, controller, controller_rootfs_disk) LOG.info("Database partition data: %s" % db_partitions) - installed_partitions = get_partitions( - controller_rootfs_disk['device_path'], - controller_rootfs_disk['device_node']) - installed_partition_map = { - p['device_node']: p for p in installed_partitions} - LOG.info("Installed partitions: %s" % installed_partitions) - backup_partition = next(p for p in db_partitions if p['type_guid'].lower() == BACKUP_GUID) - backup_device_node = backup_partition['device_node'] original_backup_size = backup_partition['size_mib'] - installed_backup_size = int(installed_partition_map[backup_device_node]['size_mib']) # noqa: E501 if installed_backup_size == original_backup_size: LOG.info("Backup partition size unchanged, nothing to do. " "Installed: %s DB: %s" % @@ -89,44 +81,45 @@ def adjust_backup_partition(): db_partitions, backup_partition, backup_change) # Ensure the last partition will fit on the disk - disk_size = get_disk_size(controller_rootfs_disk['device_node']) - 1 # noqa: E501 + disk_size = controller_rootfs_disk['size_mib'] - 1 last_partition = adjusted_partitions[-1] required_space = max(0, last_partition['end_mib'] - disk_size) - if required_space > 0: - LOG.info("Reducing partition: %s by %s" % - (last_partition['device_node'], required_space)) - last_partition['end_mib'] -= required_space - last_partition['size_mib'] -= required_space - - if last_partition['size_mib'] < 0: - raise Exception("Invalid partition configuration. Partitions: %s" % adjusted_partitions) # noqa: E501 - - update_partitions(cur, adjusted_partitions) if required_space == 0: + update_partitions(cur, adjusted_partitions) LOG.info("Adjusted partitions fit rootfs, can continue. " "Partitions: %s " % adjusted_partitions) continue - cgts_vg = get_cgts_vg(cur, controller) - cgts_vg_free_space = int(cgts_vg['lvm_vg_size'] / cgts_vg['lvm_vg_total_pe']) * cgts_vg['lvm_vg_free_pe'] # noqa: E501 + added_partitions = [p for p in db_partitions if + p['type_guid'].lower() == SYSINV_GUID] + unassigned_partitions = [p for p in added_partitions if + p['foripvid'] is None] - # There may be available space in the cgts_vg - if cgts_vg_free_space >= required_space: - LOG.info("cgts_vg has sufficient space, can continue. " - "cgts_vg: %s " % cgts_vg) - continue + if not added_partitions: + # This is not an AIO system, we'll resize the last partiton + added_partitions.append(last_partition) - # Otherwise we'll reduce the backup fs by up to 15GB and remove - # the rest from the docker fs - required_space -= cgts_vg_free_space - required_gb = int(math.ceil(required_space / 1024.0)) - backup_fs_reduction = min(15, required_gb) - update_host_fs(cur, controller, 'backup', backup_fs_reduction) + partitions = unassigned_partitions if unassigned_partitions else added_partitions # noqa + partition = max(partitions, key=itemgetter('size_mib')) - required_gb -= backup_fs_reduction - if required_gb > 0: - update_host_fs(cur, controller, 'docker', required_gb) + if partition['size_mib'] < required_space: + LOG.exception( + "Insufficient space to resize partition %s - %s" % + (partition, required_space)) + raise + + reduced_partitions = move_partitions( + adjusted_partitions, partition, required_space * -1) + final_partitions = adjusted_partitions[:adjusted_partitions.index(partition)] # noqa + final_partitions.extend(reduced_partitions) + update_partitions(cur, final_partitions) + + host_pvs = get_pvs(cur, controller) + partition_vg_name = get_vg_name(partition, host_pvs) + + if partition_vg_name == 'cgts-vg': + resize_cgts_vg(cur, controller, required_space) def get_host_rootfs(cursor, host): @@ -141,34 +134,16 @@ def get_db_partitions(cursor, host, rootfs): return cursor.fetchall() -def get_partitions(device_path, device_node): - """Obtain existing partitions from a disk.""" - partitions = [] - sgdisk_part_info = get_sgdisk_info(device_path) +def get_backup_size(): + lsblk_command = 'lsblk -pno PKNAME $(findmnt -n / -o SOURCE)' + lsblk = subprocess.Popen(lsblk_command, stdout=subprocess.PIPE, shell=True) + root_disk_path = lsblk.stdout.read() + part_info = get_sgdisk_info(root_disk_path) - for partition in sgdisk_part_info: - partition_number = partition.get('part_number') - type_name = partition.get('type_name') - part_size_mib = partition.get('size_mib') - part_device_node = build_partition_device_node( - device_node, partition_number) - part_device_path = build_partition_device_path( - device_path, partition_number) - start_mib = partition.get('start_mib') - end_mib = partition.get('end_mib') + backup_size = next(part['size_mib'] for part in part_info if + part['type_guid'].lower() == BACKUP_GUID) - part_attrs = { - 'partition_number': partition_number, - 'device_path': part_device_path, - 'device_node': part_device_node, - 'type_name': type_name, - 'start_mib': start_mib, - 'end_mib': end_mib, - 'size_mib': part_size_mib, - } - partitions.append(part_attrs) - - return partitions + return int(backup_size) def get_sgdisk_info(device_path): @@ -206,40 +181,17 @@ def get_sgdisk_info(device_path): return sgdisk_part_info -def build_partition_device_node(disk_device_node, partition_number): - if constants.DEVICE_NAME_NVME in disk_device_node: - partition_device_node = '{}p{}'.format( - disk_device_node, partition_number) - else: - partition_device_node = '{}{}'.format( - disk_device_node, partition_number) - - LOG.debug("partition_device_node: %s" % partition_device_node) - - return partition_device_node - - -def build_partition_device_path(disk_device_path, partition_number): - partition_device_path = '{}-part{}'.format( - disk_device_path, partition_number) - - LOG.debug("partition_device_path: %s" % partition_device_path) - - return partition_device_path - - def move_partitions(db_values, start, size): """ - Updates the list of partitions based on the new size of the platform backup - partition + Updates the list of partitions based on the new size of a given partition :param: db_values: A list of partitions to adjust - :param: start: The platform-backup partition - :param: size: The new size of the platform-backup partition + :param: start: The partition being adjusted + :param: size: The change in size of the partition :returns: A sorted list of updated partitions """ - partitions = sorted(db_values, key=operator.itemgetter('start_mib')) + partitions = sorted(db_values, key=itemgetter('start_mib')) partitions = partitions[partitions.index(start):] - # Update the platform backup size and end_mib + # Update the specified partition size and end_mib partitions[0]['size_mib'] += size partitions[0]['end_mib'] += size # Shift the rest of the partitions @@ -259,14 +211,39 @@ def update_partitions(cursor, updated_partitions): partition['size_mib'], partition['id'])) -def get_disk_size(rootfs): - disk_size_cmd = '{} {}'.format('blockdev --getsize64', rootfs) - disk_size_process = subprocess.Popen( - disk_size_cmd, stdout=subprocess.PIPE, shell=True) - disk_size = int(disk_size_process.stdout.read().rstrip()) +def get_pvs(cursor, host): + query = "select * from i_pv where forihostid=%s" + cursor.execute(query, (host['id'],)) + return cursor.fetchall() - # Return in mib - return int(disk_size / (1024 * 1024)) + +def get_vg_name(partition, pvs): + pv_id = partition['foripvid'] + if not pv_id: + return None + return next(pv['lvm_vg_name'] for pv in pvs if pv['id'] == pv_id) + + +def resize_cgts_vg(cursor, host, required_space): + cgts_vg = get_cgts_vg(cursor, host) + cgts_vg_free_space = int(cgts_vg['lvm_vg_size'] / cgts_vg['lvm_vg_total_pe']) * cgts_vg['lvm_vg_free_pe'] # noqa: E501 + + # There may be available space in the cgts_vg + if cgts_vg_free_space >= required_space: + LOG.info("cgts_vg has sufficient space, can continue. " + "cgts_vg: %s " % cgts_vg) + return + + # Otherwise we'll reduce the backup fs by up to 15GB and remove + # the rest from the docker fs + required_space -= cgts_vg_free_space + required_gb = int(math.ceil(required_space / 1024.0)) + backup_fs_reduction = min(15, required_gb) + update_host_fs(cursor, host, 'backup', backup_fs_reduction) + + required_gb -= backup_fs_reduction + if required_gb > 0: + update_host_fs(cursor, host, 'docker', required_gb) def get_cgts_vg(cursor, host):