config/controllerconfig/controllerconfig/upgrade-scripts/10-sysinv-adjust-partitions.py

281 lines
11 KiB
Python
Executable File

#!/usr/bin/env python
# Copyright (c) 2020 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
# This script will update the partition schema for partitions on the root disk
# of AIO controllers. This is required as the default LVM partiton grew in the
# N+1 release.
import psycopg2
import sys
import six
import subprocess
from sysinv.common import constants
from psycopg2.extras import RealDictCursor
from controllerconfig.common import log
LOG = log.get_logger(__name__)
def main():
action = None
from_release = None
to_release = None # noqa
arg = 1
while arg < len(sys.argv):
if arg == 1:
from_release = sys.argv[arg]
elif arg == 2:
to_release = sys.argv[arg] # noqa
elif arg == 3:
action = sys.argv[arg]
else:
print ("Invalid option %s." % sys.argv[arg])
return 1
arg += 1
log.configure()
LOG.debug("%s invoked with from_release = %s to_release = %s action = %s"
% (sys.argv[0], from_release, to_release, action))
if to_release == "20.06" and action == "migrate":
try:
adjust_user_partitions()
except Exception as ex:
LOG.exception(ex)
return 1
def _command(arguments1, arguments2=None):
"""Execute a command and capture stdout, stderr & return code."""
LOG.debug("Executing command: '%s'" % " ".join(arguments1))
process = subprocess.Popen(
arguments1,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
if arguments2:
process2 = subprocess.Popen(
arguments2,
stdin=process.stdout,
stdout=subprocess.PIPE,
shell=False)
process.stdout.close()
process = process2
out, err = process.communicate()
return out, err, process.returncode
def get_sgdisk_info(device_path):
"""Obtain partition info: type GUID, type name, UUID, start, end, size.
:param: device_path: the disk's device path
:returns: list of partition info
"""
sgdisk_part_info = []
fields = ['part_number', 'device_node', 'type_guid', 'type_name', 'uuid',
'start_mib', 'end_mib', 'size_mib']
sgdisk_command = '{} {}'.format('/usr/bin/partition_info.sh',
device_path)
try:
sgdisk_process = subprocess.Popen(sgdisk_command,
stdout=subprocess.PIPE,
shell=True)
except Exception as e:
LOG.exception("Could not retrieve partition information: %s" % e)
raise
sgdisk_output = sgdisk_process.stdout.read()
rows = [row for row in sgdisk_output.split(';') if row.strip()]
for row in rows:
values = row.split()
partition = dict(zip(fields, values))
if 'part_number' in partition.keys():
partition['part_number'] = int(partition['part_number'])
sgdisk_part_info.append(partition)
return sgdisk_part_info
def get_partitions(device_path, device_node):
"""Obtain existing partitions from a disk."""
partitions = []
sgdisk_part_info = get_sgdisk_info(device_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')
if constants.DEVICE_NAME_NVME in device_node:
part_device_node = '{}p{}'.format(device_node, partition_number)
else:
part_device_node = '{}{}'.format(device_node, partition_number)
part_device_path = '{}-part{}'.format(device_path,
partition_number)
start_mib = partition.get('start_mib')
end_mib = partition.get('end_mib')
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
def is_aio_system_type():
conn = psycopg2.connect("dbname='sysinv' user='postgres'")
with conn:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("SELECT * from i_system")
system = cur.fetchone()
return system['system_type'] == 'All-in-one'
def adjust_user_partitions():
if not is_aio_system_type:
LOG.info("This is not an AIO system. No partition changes required.")
return
conn = psycopg2.connect("dbname=sysinv user=postgres")
with conn:
with conn.cursor(cursor_factory=RealDictCursor) as cur:
cur.execute("select i_host.id, i_host.rootfs_device from i_host "
"where personality='controller'")
controllers = cur.fetchall()
if not controllers:
LOG.exception("Failed to fetch controller host information")
raise
for controller in controllers:
# Get the root disk for the controller.
cur.execute(
"select * from i_idisk where forihostid=%s and "
"capabilities like %s", (controller['id'], '%rootfs%',))
controller_rootfs_disk = cur.fetchone()
if not controller_rootfs_disk:
LOG.exception("Could not locate controller root disk.")
raise
LOG.debug("controller_rootfs_disk: %s" %
controller_rootfs_disk)
# Get the partitions for the controller root disk.
cur.execute(
"select partition.id, partition.device_node, "
"partition.device_path, partition.start_mib, "
"partition.end_mib, partition.size_mib "
"from partition where forihostid = %s",
(controller['id'],))
db_partitions = cur.fetchall()
LOG.debug("DB partitions: %s" % db_partitions)
# Create a map
partition_map = {p['device_node']: p for p in db_partitions}
LOG.debug("DB partition map: %s" % partition_map)
installed_partitions = get_partitions(
controller_rootfs_disk['device_path'],
controller_rootfs_disk['device_node'])
LOG.debug("installed partitions: %s" % installed_partitions)
update_db_partitions = [] # Requires DB updates
installed_lvm_device = None # LVM device that needs adjusting
adjustments = {} # LVM device partition adjustments
# Go through the installed partitions and determine any changes
for i in installed_partitions:
# Grab the partition from the db map
d = partition_map[i['device_node']]
if ((int(i['start_mib']) != int(d['start_mib'])) or
(int(i['end_mib']) != int(d['end_mib'])) or
(int(i['size_mib']) != int(d['size_mib']))):
LOG.info("MISMATCH:installed part: %s %s %s %s" % (
i['device_node'], i['start_mib'],
i['end_mib'], i['size_mib']))
LOG.info("MISMATCH: db part: %s %s %s %s" % (
d['device_node'], d['start_mib'],
d['end_mib'], d['size_mib']))
if i['type_name'] == 'Linux.LVM':
# This is key partition that will be used to adjust
# any additional user created partitions, identify
# and save the adjustments
installed_lvm_device = i['device_node']
adjustments['start_mib'] = (int(i['start_mib']) -
int(d['start_mib']))
adjustments['end_mib'] = (int(i['end_mib']) -
int(d['end_mib']))
adjustments['size_mib'] = (int(i['size_mib']) -
int(d['size_mib']))
else:
# Adjust the non-LVM partitions to match what is
# installed
d['start_mib'] = i['start_mib']
d['end_mib'] = i['end_mib']
d['size_mib'] = i['size_mib']
# Save the new partition for updating
update_db_partitions.append(d)
# Remove the partition from the db map
del partition_map[i['device_node']]
else:
# Partition is the same. No changes needed
# Remove the partition from the db map
del partition_map[i['device_node']]
if installed_lvm_device:
# Found a difference in the installed partition map for the
# primary LVM partition
LOG.debug("DB unhandled part map: %s" % partition_map)
# Update the primary installed LVM partition based on
# calculated adjustments.
d = partition_map[installed_lvm_device]
d['start_mib'] = (int(d['start_mib']) +
adjustments['start_mib'])
d['end_mib'] = (int(d['end_mib']) +
adjustments['end_mib'])
d['size_mib'] = (int(d['size_mib']) +
adjustments['size_mib'])
update_db_partitions.append(d)
del partition_map[installed_lvm_device]
# Adjust the start/end of user created partitions. Size
# will not be changed.
for device, partition in six.iteritems(partition_map):
partition['start_mib'] = (int(partition['start_mib']) +
adjustments['end_mib'])
partition['end_mib'] = (int(partition['end_mib']) +
adjustments['end_mib'])
update_db_partitions.append(partition)
if update_db_partitions:
# Found partitions that need updating
LOG.info("Required partition adjustments: %s" %
update_db_partitions)
for partition in update_db_partitions:
cur.execute(
"update partition set start_mib=%s, end_mib=%s, "
"size_mib=%s where id=%s", (partition['start_mib'],
partition['end_mib'],
partition['size_mib'],
partition['id']),)
if __name__ == "__main__":
sys.exit(main())