config/sysinv/sysinv/sysinv/sysinv/cmd/manage-partitions

894 lines
34 KiB
Python
Executable File

#!/usr/bin/env python
# -*- encoding: utf-8 -*-
#
# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright (c) 2017-2018 Wind River Systems, Inc.
#
# SPDX-License-Identifier: Apache-2.0
#
"""
Manage Disk partitions on this host and provide inventory updates
"""
import json
import math
import re
import os
import shutil
import socket
import subprocess
import time
import sys
from collections import defaultdict
from oslo_config import cfg
from sysinv.common import constants
from sysinv.common import service as sysinv_service
from sysinv.openstack.common import context
from sysinv.openstack.common import log
from sysinv.common import utils
from sysinv.openstack.common.gettextutils import _
from sysinv.conductor import rpcapi as conductor_rpcapi
CONF = cfg.CONF
LOG = log.getLogger(__name__)
# Time between loops when waiting for partition to stabilize
# from transitory states.
# Lower is better.
# At this moment, 0.3 seconds was found to give consistent
# results in running over 100 consecutive tests.
PARTITION_LOOP_WAIT_TIME = 0.3
RETURN_SUCCESS = 0
def _sectors_to_MiB(value, sector_size):
"""Transform <value> sectors to MiB and return."""
return value * sector_size / (1024 ** 2)
def _MiB_to_sectors(value, sector_size):
"""Transform <value> MiBs to sectors and return."""
return value * (1024 ** 2) / sector_size
@utils.skip_udev_partition_probe
def _command(arguments, **kwargs):
"""Execute a command and capture stdout, stderr & return code."""
# TODO: change this to debug level log, but until proven stable
# leave as info level log
LOG.info("Executing command: '%s'" % " ".join(arguments))
if 'device_node' in kwargs:
del kwargs['device_node']
process = subprocess.Popen(
arguments,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs)
out, err = process.communicate()
return out, err, process.returncode
@utils.skip_udev_partition_probe
def _command_pipe(arguments1, arguments2=None, **kwargs):
"""Execute a command and capture stdout, stderr & return code."""
LOG.info("Executing pipe command: '%s'" % " ".join(arguments1))
if 'device_node' in kwargs:
del kwargs['device_node']
process = subprocess.Popen(
arguments1,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
**kwargs)
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_disk_sector_size(device_node_or_path):
# Get sector size command.
output, _, _ = _command(['blockdev', '--getss', device_node_or_path])
sector_size_bytes = int(output.rstrip())
return sector_size_bytes
def _get_available_space(disk_device_path):
"""Obtain a disk's available space, in MiB."""
# Get total free space in sectors.
output, _, _ = _command_pipe(['sgdisk', '-p', disk_device_path],
['grep', 'Total free space'],
device_node=disk_device_path)
avail_space_sectors = re.findall('\d+', output)[0].rstrip()
if avail_space_sectors:
avail_space_sectors = int(avail_space_sectors)
else:
LOG.exception(
"Error trying to get the available space on disk %s" %
disk_device_path)
return
# Get the sector size.
sector_size_bytes = _get_disk_sector_size(disk_device_path)
# Free space in MiB.
total_available = _sectors_to_MiB(avail_space_sectors, sector_size_bytes)
# Keep 2 MiB for partition table.
if total_available >= 2:
total_available = total_available - 2
else:
total_available = 0
return total_available
@utils.skip_udev_partition_probe
def _gpt_table_present(device_node):
"""Check if a disk's partition table format is GPT or not.
:param device_node: the disk's device node
:returns False: the format is not GPT
True: the format is GPT
"""
output, _, _ = _command(["udevadm", "settle", "-E", device_node])
output, _, _ = _command(["parted", "-s", device_node, "print"],
device_node=device_node)
if not re.search('Partition Table: gpt', output):
print("Format of disk node %s is not GPT, returning" % device_node)
return False
return True
def _get_disk_device_path(part_device_path):
"""Obtain the device path of a disk from a partition's device path.
:param part_device_path: the partition's device path
:returns the device path of the disk on which the partition resides
"""
return re.match('(/dev/disk/by-path/(.+))-part([0-9]+)',
part_device_path).group(1)
def _get_partition_number(part_device_path):
"""Obtain the number of a partition.
:param part_device_path: the partition's device path
:returns the partition's number
"""
return re.match('.*?([0-9]+)$', part_device_path).group(1)
def _partition_exists(part_device_path):
"""Check if a partition exists.
:param part_device_path: the partitions's device path
:returns True: the partition exists
False: the partition doesn't exist
"""
# Do not rely on the udev symlinks from /dev/disk/by-path as they may be
# flushed during various partition operations.
disk_device_path = _get_disk_device_path(part_device_path)
part_number = _get_partition_number(part_device_path)
output, err, ret = _command(['sgdisk', '-i', part_number, disk_device_path])
if "does not exist" in output:
return False
return True
def _get_no_of_partitions(disk_device_path):
""" Get the no of partitions on a device
:param disk_device_path: disk's device path
:return number of partitions
"""
output, _, _ = _command_pipe(
['sgdisk', '-p', disk_device_path],
['sed', "1,/^Number.*Start.*(sector).*End/d"],
device_node=disk_device_path)
rows = [row.strip() for row in output.splitlines() if row.strip()]
return len(rows)
@utils.skip_udev_partition_probe
def _get_free_space(device_node):
"""Get the free spaces from a disk.
:param device_node: disk's device node/path
:returns array with the free spaces on disk"""
free_spaces = []
output, _, _ = _command_pipe(
['parted', '-s', device_node, 'unit', 'mib', 'print', 'free'],
['grep', 'Free Space'], device_node=device_node)
fields = ['start_mib', 'end_mib', 'size_mib']
output = output.replace("MiB", "").replace("Free Space", "")
rows = [row.strip() for row in output.splitlines() if row.strip()]
for row in rows:
values = row.split()
free_space = dict(zip(fields, values))
free_spaces.append(free_space)
return free_spaces
def _get_partition_start_end_size(disk_device_path, part_number,
sector_size_bytes):
"""Return the start, end and size of a partitions.
:param disk_device_path: disk's device path
:param part_number: partition's number
:returns dictionary {'start_mib': ..., 'end_mib': ..., 'size_mib': ...}
"""
output, err, ret = _command(
['sgdisk', '-i', str(part_number),
disk_device_path], device_node=disk_device_path)
partition = {}
fields = {'start_mib': 'First sector', 'end_mib': 'Last sector',
'size_mib': 'Partition size'}
rows = [row.strip() for row in output.splitlines() if row.strip()]
for key, value in fields.iteritems():
row = next((row for row in rows if value in row), None)
if row:
part_attr = re.findall('\d+', row)[0].rstrip()
partition[key] = math.ceil(
float(part_attr) * sector_size_bytes / (1024 ** 2))
return partition
# While doing changes to partitions, there are brief moments when
# the partition is in a transitory state and it is not mapped by
# the udev.
# This is due to the fact that "udevadm settle" command is event
# based and when we call it we have no guarantee that the event
# from the previous commands actually reached udev yet.
# To guard against such timing issues, we must wait for a partition
# to become "stable". We define the stable state as a number of
# consecutive successful calls to access the partition, with a
# small delay between them.
def _wait_for_partition(device_path, max_retry_count=10,
loop_wait_time=1, success_objective=3):
success_count = 0
for step in range(1, max_retry_count):
_, _, retcode = _command([
'ls', str(device_path)])
if retcode == 0:
success_count += 1
else:
success_count = 0
LOG.warning("Partition/Device %s not present in the system."
"Retrying" % str(device_path))
if success_count == success_objective:
LOG.debug("Partition %s deemed stable" % str(device_path))
break
time.sleep(loop_wait_time)
else:
raise IOError("Partition %s not present in OS" % str(device_path))
def _create_partition(disk_device_path, part_number, start_mib, size_mib,
type_code, skip_wipe=False):
"""Create a partition.
:param start: the start of the partition, in sectors
:param size: the size of the partition, in sectors
:param type_code: the type GUID of the partition
:param skip_wipe: skip wiping partition start and end if True
:returns dictionary containing the start, end and size of the new partition
"""
# Convert to sectors.
sector_size_bytes = _get_disk_sector_size(disk_device_path)
if not skip_wipe:
# Prior to committing, we need to wipe the LVM data from this
# partition so that if the LVM global filter is not set correctly
# we will have stale LVM info polluting the system
_wipe_partition(disk_device_path,
_MiB_to_sectors(start_mib, sector_size_bytes),
_MiB_to_sectors(size_mib, sector_size_bytes),
sector_size_bytes)
output, _, _ = _command(["udevadm", "settle", "-E", disk_device_path])
cmd = ['parted', '-s', disk_device_path, 'unit', 'mib', 'mkpart',
'primary', str(start_mib), str(start_mib + size_mib)]
output, err, ret = _command(cmd, device_node=disk_device_path)
if ret != RETURN_SUCCESS:
raise IOError("Could not create partition %s of %sMiB on disk %s: %s" %
(part_number, size_mib, disk_device_path, str(err)))
output, _, _ = _command([
'sgdisk',
'--typecode={part_number}:{type_code}'.format(
part_number=part_number, type_code=type_code),
'--change-name={part_number}:{part_name}'.format(
part_number=part_number,
part_name=constants.PARTITION_NAME_PV),
disk_device_path], device_node=disk_device_path)
# After a partition is created we have to wait for udev to create the
# corresponding device node. Otherwise if we try to open it will fail.
part_device_path = '{}-part{}'.format(disk_device_path, part_number)
_wait_for_partition(part_device_path,
loop_wait_time=PARTITION_LOOP_WAIT_TIME)
partition = _get_partition_start_end_size(disk_device_path,
part_number,
sector_size_bytes)
return partition
def _delete_partition(disk_device_path, part_number):
"""Delete a partition.
:param disk_device_path: the device path of the disk on which the
partition resides
:param part_number: the partition number
:returns N/A
"""
# Delete the partition with the specified number.
cmd = ['parted', '-s', disk_device_path, 'rm', str(part_number)]
output, err, ret = _command(cmd, device_node=disk_device_path)
if ret != RETURN_SUCCESS:
raise IOError("Could not delete partition %s from disk %s: %s" %
(part_number, disk_device_path, str(err)))
LOG.info("There was no %s partition on disk %s." %
(part_number, disk_device_path))
def _resize_partition(disk_device_path, part_number, new_part_size_mib,
start_mib, type_guid):
"""Modify a partition.
:param disk_device_path: the device path of the disk on which the
partition resides
:param part_number: the partition number
:param new_part_size_mib: the new size for the partition, in MiB
:param type_guid: the type GUID for the partition
:returns dictionary with partition's start, end, start
"""
try:
_delete_partition(disk_device_path, part_number)
except Exception as e:
raise e
try:
partition = _create_partition(
disk_device_path, part_number, start_mib, new_part_size_mib,
type_guid, skip_wipe=True)
except Exception as e:
# An IOException usually means that the partition is in
# a transitory state. We should wait for the partition
# to stabilize and then try to commit the changes again
LOG.error(_("IOError resizing partition %s of %s: %s") %
(part_number, disk_device_path, str(e.message)))
raise e
return partition
def _send_inventory_update(partition_update):
"""Send update to the sysinv conductor."""
# If this is controller-1, in an upgrade, don't send update.
sw_mismatch = os.environ.get('CONTROLLER_SW_VERSIONS_MISMATCH', None)
hostname = socket.gethostname()
if sw_mismatch and hostname == constants.CONTROLLER_1_HOSTNAME:
print("Don't send information to N-1 sysinv conductor, return.")
return
ctxt = context.get_admin_context()
rpcapi = conductor_rpcapi.ConductorAPI(
topic=conductor_rpcapi.MANAGER_TOPIC)
max_tries = 2
num_of_try = 0
while num_of_try < max_tries:
try:
num_of_try = num_of_try + 1
rpcapi.update_partition_information(ctxt, partition_update)
break
except Exception as ex:
print("Exception trying to contact sysinv conductor: %s: %s " %
(type(ex).__name__, str(ex)))
if num_of_try < max_tries and "Timeout" in type(ex).__name__:
print("Could not contact sysinv conductor, try one more time..")
continue
else:
print("Quit trying to send extra info to the conductor, "
"sysinv agent will provide this info later...")
def _wipe_partition(disk_node, start_in_sectors, size_in_sectors, sector_size):
"""Clear the locations within the partition where an LVM header may
exist. """
# clear LVM and header and additional formatting data of this partition
# (i.e. DRBD)
# note: dd outputs to stderr, not stdout
_, err_output, _ = _command(
['dd', 'bs={sector_size}'.format(sector_size=sector_size),
'if=/dev/zero',
'of={part_id}'.format(part_id=disk_node), 'oflag=direct',
'count=34', 'seek={part_end}'.format(part_end=start_in_sectors)])
# TODO: change this to debug level log, but until proven stable
# leave as info level log
LOG.info("Zero-out beginning of partition. Output: %s" % err_output)
seek_end = start_in_sectors + size_in_sectors - 34
# format the last 1MB of the partition
# note: dd outputs to stderr, not stdout
_, err_output, _ = _command(
['dd', 'bs={sector_size}'.format(sector_size=sector_size),
'if=/dev/zero',
'of={part_id}'.format(part_id=disk_node), 'oflag=direct',
'count=34', 'seek={part_end}'.format(part_end=seek_end)])
# TODO: change this to debug level log, but until proven stable
# leave as info level log
LOG.info("Zero-out end of partition. Output: %s" % err_output)
LOG.info("Partition details: %s" %
{"disk_node": disk_node, "start_in_sectors": start_in_sectors,
"size_in_sectors": size_in_sectors, "sector_size": sector_size,
"part_end": seek_end})
def create_partitions(data, mode, pfile):
"""Process data for creating (a) partition(s) and send the update back to
the sysinv conductor.
"""
if mode in ['create-only', 'send-only']:
json_array = []
if mode == 'send-only':
with open(pfile) as inputfile:
payload = json.load(inputfile)
for p in payload:
_send_inventory_update(p)
return
print(data)
json_body = json.loads(data)
for p in json_body:
disk_device_path = p.get('disk_device_path')
part_device_path = p.get('part_device_path')
if _gpt_table_present(device_node=disk_device_path):
size_mib = int(p.get('req_size_mib'))
type_code = p.get('req_guid')
# Obtain parted device and parted disk for the given disk device
# path.
if _partition_exists(part_device_path):
print("Partition %s already exists, returning." %
part_device_path)
continue
# If we only allow to add and remove partition to/from the end,
# then there should only be a max of two free regions (1MiB at
# the beginning and the rest of the available disk, if any).
free_spaces = _get_free_space(device_node=disk_device_path)
if len(free_spaces) > 2:
print("Disk %s is fragmented. Partition creation aborted." %
disk_device_path)
free_space = free_spaces[-1]
number_of_partitions = _get_no_of_partitions(disk_device_path)
# If this is the 1st partition, allocate an extra 1MiB.
if number_of_partitions == 0:
print("First partition, use an extra MiB")
start_mib = 1
else:
# Free space in sectors.
start_mib = int(float(free_space.get('start_mib')))
response = {
'uuid': p.get('req_uuid'),
'ihost_uuid': p.get('ihost_uuid')
}
partition_number = number_of_partitions + 1
try:
new_partition = _create_partition(
disk_device_path, partition_number, start_mib,
size_mib, type_code)
part_device_path = '{}-part{}'.format(disk_device_path,
partition_number)
output, _, _ = _command(["udevadm", "settle", "-E",
disk_device_path])
disk_available_mib = _get_available_space(disk_device_path)
response.update({
'start_mib': new_partition['start_mib'],
'end_mib': new_partition['end_mib'],
'size_mib': new_partition['size_mib'],
'device_path': part_device_path,
'type_guid': p.get('req_guid'),
'type_name': constants.PARTITION_NAME_PV,
'available_mib': disk_available_mib,
'status': constants.PARTITION_READY_STATUS})
except Exception as e:
LOG.error("ERROR: %s" % e.message)
response.update({'status': constants.PARTITION_ERROR_STATUS})
else:
response = {
'uuid': p.get('req_uuid'),
'ihost_uuid': p.get('ihost_uuid'),
'status': constants.PARTITION_ERROR_STATUS_GPT
}
if mode == 'create-only':
json_array.append(response)
else:
# Send results back to the conductor.
_send_inventory_update(response)
if mode == 'create-only':
with open(pfile, 'w') as outfile:
json.dump(json_array, outfile)
class fix_global_filter():
""" Some drbd metadata processing commands execute LVM commands.
Therefore, our backing device has to be visible to LVM.
"""
def __init__(self, device_path):
self.device_path = device_path
self.lvm_conf_file = "/etc/lvm/lvm.conf"
self.lvm_conf_backup_file = "/etc/lvm/lvm.conf.bck-manage-partitions"
self.lvm_conf_temp_file = "/etc/lvm/lvm.conf.tmp-manage-partitions"
def __enter__(self):
# Backup existing config file
shutil.copy(self.lvm_conf_file, self.lvm_conf_backup_file)
# Prepare a new config file.
with open(self.lvm_conf_file, "r") as lvm_conf:
with open(self.lvm_conf_temp_file, "w") as lvm_new_conf:
for line in lvm_conf:
m = re.search('^\s*global_filter\s*=\s*(.*)', line)
if m:
global_filter = eval(m.group(1))
global_filter = [v for v in global_filter if
v != "r|.*|"]
global_filter.append("a|%s|" % self.device_path)
global_filter.append("r|.*|")
new_line = 'global_filter = ' + '[ "' + '", "'.join(
global_filter) + '" ]\n'
lvm_new_conf.write(new_line)
else:
lvm_new_conf.write(line)
# Replace old config with new one.
os.rename(self.lvm_conf_temp_file, self.lvm_conf_file)
# Wait for LVM to reload its config.
_wait_for_partition(self.device_path,
loop_wait_time=PARTITION_LOOP_WAIT_TIME)
for try_ in range(1, 10):
output, _, ret_code = _command(["pvs", self.device_path])
if ret_code == 0:
break
else:
time.sleep(1)
def __exit__(self, type, value, traceback):
# We are done, restore previous config.
os.rename(self.lvm_conf_backup_file, self.lvm_conf_file)
class DrbdFailureException(BaseException):
""" Custom exception to allow DRBD config fallback"""
pass
def modify_partitions(data, mode, pfile):
"""Process data for modifying (a) partition(s) and send the update back to
the sysinv conductor.
"""
json_body = json.loads(data)
for p in json_body:
# Get the partition's device path.
part_device_path = p.get('part_device_path')
disk_device_path = _get_disk_device_path(part_device_path)
new_part_size_mib = p.get('new_size_mib')
start_mib = p.get('start_mib')
type_guid = p.get('req_guid')
if _gpt_table_present(device_node=disk_device_path):
# Separate the partition number from the disk's device path.
part_number = _get_partition_number(part_device_path)
response = {
'uuid': p.get('current_uuid'),
'ihost_uuid': p.get('ihost_uuid')
}
try:
# Check if we have a DRBD partition
is_drbd = False
cmd_template = None
metadata_dump = None
_, _, _ = _command(
["udevadm", "settle", "-E", str(part_device_path)])
_wait_for_partition(part_device_path,
loop_wait_time=PARTITION_LOOP_WAIT_TIME)
output, _, _ = _command([
'wipefs', '--parsable', str(part_device_path)])
for line in output.splitlines():
values = line.split(',')
if len(values) and values[-1] == 'drbd':
is_drbd = True
LOG.info("Partition %s has drbd "
"metadata!" % part_device_path)
if is_drbd:
# Steps based on:
# https://docs.linbit.com/doc/users-guide-84/s-resizing/
# Check if drbd is configured and get a template
# command to use for correctly accessing this device.
# E.g. "drbdmeta 4 v08 <part_device_path> internal dump-md
output, _, _ = _command(
['drbdadm', '-d', 'dump-md', 'all'])
for line in output.splitlines():
if part_device_path in line:
# We found our command, remove 'dump-md' action,
# we will add our own actions later.
cmd_template = line.replace('dump-md', '').split()
break
else:
# drbd meta should not be present on devices that are
# not configured. Ignore it.
is_drbd = False
if is_drbd:
# Make sure that metadata is clean - no operation are in
# flight.
output, err, err_code = _command(
cmd_template + ['apply-al'])
if err_code:
raise Exception(
"Failed cleaning metadata. stdout: '%s', "
"stderr: '%s', return code: '%s'" %
(output, err, err_code))
# Backup metadata
metadata_dump, _, _ = _command(cmd_template + ['dump-md'])
if err_code:
raise DrbdFailureException(
"Failed getting metadata. stdout: '%s', "
"stderr: '%s', return code: '%s'" %
(metadata_dump, err, err_code))
TMP_FILE = "/run/drbd-meta.dump"
with open(TMP_FILE, "w") as f:
for line in metadata_dump.splitlines():
f.write("%s\n" % line)
# Resize the partition.
part = _resize_partition(disk_device_path, part_number,
new_part_size_mib, start_mib,
type_guid)
_command(["udevadm", "settle", "-E", str(part_device_path)])
if is_drbd:
with fix_global_filter(part_device_path):
# Initialize metadata area of resized partition
# (metadata is located at the end of partition).
output, err, err_code = _command(
cmd_template + ['create-md', '--force'])
if err_code:
raise DrbdFailureException(
"Failed to create metadata. stdout: '%s', "
"stderr: '%s', return code: '%s'" %
(output, err, err_code))
# Overwrite empty with backed-up meta
new_output, err, err_code = _command(
cmd_template + ['restore-md', TMP_FILE, '--force'])
if err_code:
raise DrbdFailureException(
"Failed to restore metadata. stdout: '%s',"
" stderr: '%s', return code: '%s', "
"meta: %s" % (output, err, err_code,
"\n".join(new_output)))
if not is_drbd:
# We may have a local PV, resize it.
output, err, err_code = _command(['pvresize',
part_device_path])
if err_code not in [0, 5]:
raise Exception("Pvresize failure. stdout: '%s', "
"stderr: '%s', return code: '%s', " %
(output, err, err_code))
disk_available_mib = _get_available_space(disk_device_path)
response.update({
'start_mib': part['start_mib'],
'end_mib': part['end_mib'],
'size_mib': part['size_mib'],
'device_path': part_device_path,
'available_mib': disk_available_mib,
'type_name': constants.PARTITION_NAME_PV,
'status': constants.PARTITION_READY_STATUS})
except DrbdFailureException as e:
if not os.path.exists('/etc/platform/simplex'):
LOG.error("Partition modification failed due to DRBD cmd "
"failure, recreating DRBD volume from scratch"
"Details: %s", str(e))
_, _, _ = _command(['wipefs', '-a', part_device_path])
output, err, err_code = _command(
cmd_template + ['create-md', '--force'])
if err_code:
LOG.exception(
"Failed creating new metadata. stdout: '%s', "
"stderr: '%s', return code: '%s', " %
(output, err, err_code))
response.update(
{'status': constants.PARTITION_ERROR_STATUS})
else:
# We avoid wiping data if we have a single controller!
LOG.exception("Partition modification failed: %s", str(e))
response.update(
{'status': constants.PARTITION_ERROR_STATUS})
except Exception as e:
LOG.exception("Partition modification failed: %s", str(e))
response.update({'status': constants.PARTITION_ERROR_STATUS})
# Send results back to the conductor.
_send_inventory_update(response)
def delete_partitions(data, mode, pfile):
"""Process data for deleting (a) partition(s) and send the update back to
the sysinv conductor.
"""
json_body = json.loads(data)
for p in json_body:
# Get the partition's device path.
part_device_path = p.get('part_device_path')
disk_device_path = _get_disk_device_path(part_device_path)
if _gpt_table_present(device_node=disk_device_path):
# Separate the partition number from the disk's device path.
part_number = _get_partition_number(part_device_path)
response = {
'uuid': p.get('current_uuid'),
'ihost_uuid': p.get('ihost_uuid')
}
try:
# Delete the partition.
print("Delete partition %s from %s" % (disk_device_path,
part_number))
_delete_partition(disk_device_path, part_number)
disk_available_mib = _get_available_space(disk_device_path)
response.update({'available_mib': disk_available_mib,
'status': constants.PARTITION_DELETED_STATUS})
except Exception as e:
LOG.error("ERROR: %s" % e.message)
response.update({'status': constants.PARTITION_ERROR_STATUS})
else:
response = {
'uuid': p.get('req_uuid'),
'ihost_uuid': p.get('ihost_uuid'),
'status': constants.PARTITION_ERROR_STATUS_GPT
}
# Now that the partition is deleted, make sure that we purge it from
# the LVM cache. Otherwise, if this partition is recreated and the LVM
# global_filter has a view of it, it will become present from an LVM
# perspective
output, _, _ = _command(["pvscan", "--cache"])
# Send results back to the conductor.
_send_inventory_update(response)
def check_partitions(data, mode, pfile):
"""Check/create missing disk partitions
"""
json_body = json.loads(data)
disks = defaultdict(list)
for p in json_body:
disk_device_path = p.get('disk_device_path')
if not _gpt_table_present(device_node=disk_device_path):
utils.disk_wipe(disk_device_path)
utils.execute('parted', disk_device_path, 'mklabel', 'gpt')
time.sleep(1) # Wait for udev to flush partition table data
disks[disk_device_path].append(p)
for partitions in disks.values():
# Filter out any partitions without a start_mib.
sortable_partitions = filter(lambda p: p.get('start_mib') is not None,
partitions)
for p in sorted(sortable_partitions,
lambda p, q: p.get('start_mib') - q.get('start_mib')):
disk = _get_disk_device_path(p.get('device_path'))
if _partition_exists(p.get('device_path')):
print('Partition {} already exists on disk {}'.format(
p.get('device_path'), disk))
continue
partition_number = _get_partition_number(p.get('device_path'))
_create_partition(disk, partition_number, p.get('start_mib'),
p.get('size_mib'), p.get('type_guid'))
_, _, _ = _command(
["udevadm", "settle", "-E", p.get('disk_device_path')])
def add_action_parsers(subparsers):
for action in ['delete', 'modify', 'create', 'check']:
parser = subparsers.add_parser(action)
parser.add_argument('-m', '--mode',
choices=['create-only', 'send-only'])
parser.add_argument('-f', '--pfile')
parser.add_argument('data')
parser.set_defaults(func=globals()[action + '_partitions'])
CONF.register_cli_opt(
cfg.SubCommandOpt('action',
title='Action options',
help='Available partition management options',
handler=add_action_parsers))
@utils.synchronized(constants.PARTITION_MANAGE_LOCK)
def run(action, data, mode, pfile):
action(data, mode, pfile)
def main(argv):
sysinv_service.prepare_service(argv)
global LOG
LOG = log.getLogger("manage-partitions")
if CONF.action.name in ['delete', 'modify', 'create', 'check']:
msg = (_("Called partition '%(action)s' with '%(mode)s' '%(pfile)s' "
"and '%(data)s'") %
{"action": CONF.action.name,
"mode": CONF.action.mode,
"pfile": CONF.action.pfile,
"data": CONF.action.data})
LOG.info(msg)
print(msg)
run(CONF.action.func, CONF.action.data,
CONF.action.mode, CONF.action.pfile)
else:
LOG.error(_("Unknown action: %(action)") % {"action":
CONF.action.name})
if __name__ == "__main__":
main(sys.argv)