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

464 lines
20 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 oslo_utils import uuidutils
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 from_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 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 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')
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')
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 cgts_vg_extend(cur, disk, pv_cgts_vg, partition_number,
part_size_mib, part_start_mib, part_end_mib):
part_device_node = build_partition_device_node(
disk.get('device_node'), partition_number)
part_device_path = build_partition_device_path(
disk.get('device_path'), partition_number)
LOG.info("Extra cgts-vg partition size: %s start: %s "
"end: %s device node: %s device path: %s" %
(part_size_mib, part_start_mib, part_end_mib,
part_device_node, part_device_path))
# Create a new partition
part_uuid = uuidutils.generate_uuid()
cur.execute(
"insert into partition(uuid, idisk_id, idisk_uuid, size_mib,"
"device_node, device_path, status, type_guid, type_name, "
"forihostid, foripvid, start_mib, end_mib) "
"values(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
(part_uuid, disk.get('id'), disk.get('uuid'), part_size_mib,
part_device_node, part_device_path,
constants.PARTITION_READY_STATUS,
constants.USER_PARTITION_PHYSICAL_VOLUME,
constants.PARTITION_NAME_PV, disk.get('forihostid'),
None, part_start_mib, part_end_mib))
# Create a new pv
pv_uuid = uuidutils.generate_uuid()
cur.execute(
"insert into i_pv(uuid, pv_state, pv_type, disk_or_part_uuid, "
"disk_or_part_device_node, disk_or_part_device_path, lvm_pv_name, "
"lvm_vg_name, forihostid, forilvgid) "
"values(%s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
(pv_uuid, constants.PV_ADD, constants.PV_TYPE_PARTITION,
part_uuid, part_device_node, part_device_path, part_device_node,
constants.LVG_CGTS_VG, disk.get('forihostid'),
pv_cgts_vg.get('forilvgid')))
# Get the PV.
cur.execute("select i_pv.id from i_pv where uuid=%s",
(pv_uuid,))
pv = cur.fetchone()
# Update the partition to associate with pv id.
cur.execute(
"update partition set foripvid=%s where uuid=%s",
(pv.get('id'), part_uuid))
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 and "
"idisk_uuid = %s",
(controller['id'], controller_rootfs_disk['uuid'],))
db_partitions = cur.fetchall()
LOG.debug("DB partitions: %s" % db_partitions)
# Create a db partition 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)
# Get the PVs for the controller.
cur.execute(
"select i_pv.id, i_pv.disk_or_part_uuid, "
"i_pv.disk_or_part_device_node, "
"i_pv.disk_or_part_device_path, i_pv.lvm_pv_size,"
"i_pv.lvm_pv_name, i_pv.lvm_vg_name, i_pv.forilvgid,"
"i_pv.pv_type from i_pv where forihostid = %s",
(controller['id'],))
db_pvs = cur.fetchall()
LOG.debug("DB pvs: %s" % db_pvs)
# Create a db pv map for the controller root disk
pv_map = {pv['disk_or_part_device_node']: pv for pv in db_pvs
if controller_rootfs_disk['device_node'] in
pv['disk_or_part_device_node']}
LOG.info("DB pv map: %s" % pv_map)
# Cgts-vg physical volumes
cgts_vg_pvs = [pv for pv in db_pvs
if ((controller_rootfs_disk['device_path'] in
pv['disk_or_part_device_path']) and
pv['lvm_vg_name'] == constants.LVG_CGTS_VG)]
update_db_partitions = [] # Requires DB updates for partitions
update_db_pvs = [] # Requires DB updates for pvs
installed_lvm_device = None # LVM device that needs adjusting
adjustments = {} # LVM device partition adjustments
extend_cgts_vg = False # Flag to determine cgts-vg extension
# 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
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['device_node']]
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['device_node']]
if adjustments['size_mib'] < 0:
# The primary LVM partition for cgts-vg in new release
# is smaller than the old release. Creating new
# partition and pv to extend cgts-vg is needed.
# The device node/path of the new LVM partition and pv
# is after the primary LVM partition and the device
# node/path of the user created partitions and pvs need
# to be moved backwards.
# ie.
# primary LVM partition
# device node - /dev/sda5
# device path - /dev/disk/by-path/xxx-part5
# new LVM partition
# device node - /dev/sda6
# device path - /dev/disk/by-path/xxx-part6
# user created LVM partitions
# device node - /dev/sda7
# device path - /dev/disk/by-path/xxx-part7
# ...
extend_cgts_vg = True
increased_partition_number = \
installed_lvm_device['partition_number'] + 2
# partition_map is not an ordered dictionary, we
# need to sort partition_map by its key(device node)
# to ensure the adjustments for user created partitions
# are correct
for device, partition in sorted(
six.iteritems(partition_map)):
# Adjust the device node/path of user created
# partitions. The start/end/size of the partitions
# will not be changed.
partition['device_node'] = \
build_partition_device_node(
controller_rootfs_disk['device_node'],
increased_partition_number)
partition['device_path'] = \
build_partition_device_path(
controller_rootfs_disk['device_path'],
increased_partition_number)
update_db_partitions.append(partition)
increased_partition_number += 1
# Adjust the device node/path of user created pv.
pv = pv_map.get(device)
if pv:
pv['disk_or_part_device_node'] = \
partition['device_node']
pv['disk_or_part_device_path'] = \
partition['device_path']
pv['lvm_pv_name'] = partition['device_node']
update_db_pvs.append(pv)
# Reverse the updating order. The partitions that
# moving backwards need to updated first because of
# the UniqueConstraint "u_partition_path_host_id"
# for partition table
update_db_partitions = update_db_partitions[::-1]
else:
# The primary LVM partition for cgts-vg in new release
# is equal to or larger than the old release. 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, device_node=%s, device_path=%s "
"where id=%s",
(partition['start_mib'], partition['end_mib'],
partition['size_mib'], partition['device_node'],
partition['device_path'], partition['id']),)
if update_db_pvs:
LOG.info("Required pv adjustments: %s" % update_db_pvs)
for pv in update_db_pvs:
cur.execute(
"update i_pv set disk_or_part_device_node=%s, "
"disk_or_part_device_path=%s, lvm_pv_name=%s "
"where id=%s",
(pv['disk_or_part_device_node'],
pv['disk_or_part_device_path'],
pv['lvm_pv_name'], pv['id']))
if extend_cgts_vg:
part_number = \
installed_lvm_device['partition_number'] + 1
part_size_mib = abs(adjustments['size_mib'])
part_start_mib = int(installed_lvm_device['end_mib'])
part_end_mib = part_start_mib + part_size_mib
cgts_vg_extend(
cur, controller_rootfs_disk,
cgts_vg_pvs[0], part_number, part_size_mib,
part_start_mib, part_end_mib)
if __name__ == "__main__":
sys.exit(main())