6256b0d106
This update replaced the compute personality & subfunction to worker, and updated internal and customer visible references. In addition, the compute-huge package has been renamed to worker-utils as it contains various scripts/services that used to affine running tasks or interface IRQ to specific CPUs. The worker_reserved.conf is now installed to /etc/platform. The cpu function 'VM' has also been renamed to 'Application'. Tests Performed: Non-containerized deployment AIO-SX: Sanity and Nightly automated test suite AIO-DX: Sanity and Nightly automated test suite 2+2 System: Sanity and Nightly automated test suite 2+2 System: Horizon Patch Orchestration Kubernetes deployment: AIO-SX: Create, delete, reboot and rebuild instances 2+2+2 System: worker nodes are unlock enable and no alarms Story: 2004022 Task: 27013 Change-Id: I0e0be6b3a6f25f7fb8edf64ea4326854513aa396 Signed-off-by: Tao Liu <tao.liu@windriver.com>
723 lines
29 KiB
Python
723 lines
29 KiB
Python
#
|
|
# Copyright (c) 2017-2018 Wind River Systems, Inc.
|
|
#
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
#
|
|
|
|
import jsonpatch
|
|
import math
|
|
import six
|
|
|
|
import pecan
|
|
from pecan import rest
|
|
|
|
import wsme
|
|
from wsme import types as wtypes
|
|
import wsmeext.pecan as wsme_pecan
|
|
|
|
from sysinv.api.controllers.v1 import base
|
|
from sysinv.api.controllers.v1 import collection
|
|
from sysinv.api.controllers.v1 import link
|
|
from sysinv.api.controllers.v1 import types
|
|
from sysinv.api.controllers.v1 import utils
|
|
from sysinv.common import constants
|
|
from sysinv.common import exception
|
|
from sysinv.common import utils as cutils
|
|
from sysinv import objects
|
|
from sysinv.openstack.common.gettextutils import _
|
|
from sysinv.openstack.common import log
|
|
from sysinv.openstack.common import uuidutils
|
|
|
|
LOG = log.getLogger(__name__)
|
|
|
|
|
|
class PartitionPatchType(types.JsonPatchType):
|
|
|
|
@staticmethod
|
|
def mandatory_attrs():
|
|
return ['/address', '/ihost_uuid']
|
|
|
|
|
|
class Partition(base.APIBase):
|
|
uuid = types.uuid
|
|
"Unique UUID for this partition"
|
|
|
|
start_mib = int
|
|
"Partition start"
|
|
|
|
end_mib = int
|
|
"Partition end"
|
|
|
|
size_mib = int
|
|
"The size of the partition"
|
|
|
|
device_node = wtypes.text
|
|
"The device node of the partition"
|
|
|
|
device_path = wtypes.text
|
|
"The device path of the partition"
|
|
|
|
type_guid = types.uuid
|
|
"Unique type UUID for this partition"
|
|
|
|
type_name = wtypes.text
|
|
"The type name for this partition"
|
|
|
|
idisk_id = int
|
|
"The disk's id on which the partition resides"
|
|
|
|
idisk_uuid = types.uuid
|
|
"The disk's id on which the partition resides"
|
|
|
|
status = int
|
|
"Shows the status of the partition"
|
|
|
|
foripvid = int
|
|
"The ipvid that this partition belongs to"
|
|
|
|
forihostid = int
|
|
"The ihostid that this partition belongs to"
|
|
|
|
ihost_uuid = types.uuid
|
|
"The UUID of the host this partition belongs to"
|
|
|
|
ipv_uuid = types.uuid
|
|
"The UUID of the physical volume this partition belongs to"
|
|
|
|
links = [link.Link]
|
|
"A list containing a self link and associated partition links"
|
|
|
|
capabilities = {wtypes.text: utils.ValidTypes(wtypes.text,
|
|
six.integer_types)}
|
|
"This partition's meta data"
|
|
|
|
def __init__(self, **kwargs):
|
|
self.fields = objects.partition.fields.keys()
|
|
for k in self.fields:
|
|
setattr(self, k, kwargs.get(k))
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_partition, expand=True):
|
|
partition = Partition(**rpc_partition.as_dict())
|
|
if not expand:
|
|
partition.unset_fields_except(
|
|
['uuid', 'start_mib', 'end_mib', 'size_mib', 'device_path',
|
|
'device_node', 'type_guid', 'type_name', 'idisk_id',
|
|
'foripvid', 'ihost_uuid', 'idisk_uuid', 'ipv_uuid', 'status',
|
|
'created_at', 'updated_at', 'capabilities'])
|
|
|
|
# Never expose the id attribute.
|
|
partition.forihostid = wtypes.Unset
|
|
partition.idisk_id = wtypes.Unset
|
|
partition.foripvid = wtypes.Unset
|
|
|
|
partition.links = [link.Link.make_link('self', pecan.request.host_url,
|
|
'partitions', partition.uuid),
|
|
link.Link.make_link('bookmark',
|
|
pecan.request.host_url,
|
|
'partitions', partition.uuid,
|
|
bookmark=True)
|
|
]
|
|
return partition
|
|
|
|
|
|
class PartitionCollection(collection.Collection):
|
|
"""API representation of a collection of partitions."""
|
|
|
|
partitions = [Partition]
|
|
"A list containing partition objects"
|
|
|
|
def __init__(self, **kwargs):
|
|
self._type = 'partitions'
|
|
|
|
@classmethod
|
|
def convert_with_links(cls, rpc_partitions, limit, url=None,
|
|
expand=False, **kwargs):
|
|
collection = PartitionCollection()
|
|
collection.partitions = [Partition.convert_with_links(
|
|
p, expand)
|
|
for p in rpc_partitions]
|
|
collection.next = collection.get_next(limit, url=url, **kwargs)
|
|
return collection
|
|
|
|
|
|
LOCK_NAME = 'PartitionController'
|
|
|
|
|
|
class PartitionController(rest.RestController):
|
|
"""REST controller for partitions."""
|
|
|
|
_custom_actions = {
|
|
'detail': ['GET'],
|
|
}
|
|
|
|
def __init__(self, from_ihosts=False, from_idisk=False, from_ipv=False):
|
|
self._from_ihosts = from_ihosts
|
|
self._from_idisk = from_idisk
|
|
self._from_ipv = from_ipv
|
|
|
|
def _get_partitions_collection(self, ihost_uuid, disk_uuid, ipv_uuid,
|
|
marker, limit, sort_key, sort_dir,
|
|
expand=False, resource_url=None):
|
|
|
|
if self._from_ihosts and not ihost_uuid:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Host id not specified."))
|
|
|
|
if self._from_idisk and not disk_uuid:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Disk id not specified."))
|
|
|
|
if self._from_ipv and not ipv_uuid:
|
|
raise exception.InvalidParameterValue(_(
|
|
"Physical Volume id not specified."))
|
|
|
|
limit = utils.validate_limit(limit)
|
|
sort_dir = utils.validate_sort_dir(sort_dir)
|
|
|
|
marker_obj = None
|
|
if marker:
|
|
marker_obj = objects.partition.get_by_uuid(
|
|
pecan.request.context,
|
|
marker)
|
|
|
|
if self._from_ihosts and self._from_idisk:
|
|
partitions = pecan.request.dbapi.partition_get_by_idisk(
|
|
disk_uuid,
|
|
limit,
|
|
marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
elif self._from_ihosts:
|
|
partitions = pecan.request.dbapi.partition_get_by_ihost(
|
|
ihost_uuid, limit,
|
|
marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
elif self._from_ipv:
|
|
partitions = pecan.request.dbapi.partition_get_by_ipv(
|
|
ipv_uuid,
|
|
limit,
|
|
marker_obj,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
# Only return user created partitions.
|
|
partitions = [
|
|
p for p in partitions
|
|
if p.type_guid == constants.USER_PARTITION_PHYSICAL_VOLUME]
|
|
|
|
return PartitionCollection.convert_with_links(partitions, limit,
|
|
url=resource_url,
|
|
expand=expand,
|
|
sort_key=sort_key,
|
|
sort_dir=sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(PartitionCollection, types.uuid, types.uuid,
|
|
types.uuid, types.uuid, int, wtypes.text, wtypes.text)
|
|
def get_all(self, ihost_uuid=None, idisk_uuid=None, ipv_uuid=None,
|
|
marker=None, limit=None, sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of partitions."""
|
|
|
|
return self._get_partitions_collection(ihost_uuid, idisk_uuid, ipv_uuid,
|
|
marker, limit, sort_key,
|
|
sort_dir)
|
|
|
|
@wsme_pecan.wsexpose(PartitionCollection, types.uuid, types.uuid, int,
|
|
wtypes.text, wtypes.text)
|
|
def detail(self, ihost_uuid=None, marker=None, limit=None,
|
|
sort_key='id', sort_dir='asc'):
|
|
"""Retrieve a list of partitions with detail."""
|
|
parent = pecan.request.path.split('/')[:-1][-1]
|
|
if parent != "partitions":
|
|
raise exception.HTTPNotFound
|
|
|
|
expand = True
|
|
resource_url = '/'.join(['partitions', 'detail'])
|
|
return self._get_partitions_collection(ihost_uuid, marker, limit, sort_key,
|
|
sort_dir, expand, resource_url)
|
|
|
|
@wsme_pecan.wsexpose(Partition, types.uuid)
|
|
def get_one(self, partition_uuid):
|
|
"""Retrieve information about the given partition."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
rpc_partition = objects.partition.get_by_uuid(
|
|
pecan.request.context, partition_uuid)
|
|
return Partition.convert_with_links(rpc_partition)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme.validate(types.uuid, [PartitionPatchType])
|
|
@wsme_pecan.wsexpose(Partition, types.uuid,
|
|
body=[PartitionPatchType])
|
|
def patch(self, partition_uuid, patch):
|
|
"""Update an existing partition."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
LOG.info("Partition patch_data: %s" % patch)
|
|
|
|
rpc_partition = objects.partition.get_by_uuid(
|
|
pecan.request.context, partition_uuid)
|
|
|
|
# replace ihost_uuid and partition_uuid with corresponding
|
|
patch_obj = jsonpatch.JsonPatch(patch)
|
|
ihost = None
|
|
for p in patch_obj:
|
|
if p['path'] == '/ihost_uuid':
|
|
p['path'] = '/forihostid'
|
|
ihost = objects.host.get_by_uuid(pecan.request.context,
|
|
p['value'])
|
|
p['value'] = ihost.id
|
|
|
|
# Perform checks based on the current vs.requested modifications.
|
|
if not ihost:
|
|
ihost = pecan.request.dbapi.ihost_get(rpc_partition.forihostid)
|
|
LOG.info("from partition get ihost=%s" % ihost.hostname)
|
|
_partition_pre_patch_checks(rpc_partition, patch_obj, ihost)
|
|
|
|
try:
|
|
partition = Partition(**jsonpatch.apply_patch(
|
|
rpc_partition.as_dict(), patch_obj))
|
|
except utils.JSONPATCH_EXCEPTIONS as e:
|
|
raise exception.PatchError(patch=patch, reason=e)
|
|
|
|
# Perform post patch semantic checks.
|
|
_semantic_checks(constants.PARTITION_CMD_MODIFY, partition.as_dict())
|
|
partition.status = constants.PARTITION_MODIFYING_STATUS
|
|
try:
|
|
# Update only the fields that have changed
|
|
for field in objects.partition.fields:
|
|
if rpc_partition[field] != getattr(partition, field):
|
|
rpc_partition[field] = getattr(partition, field)
|
|
|
|
# Save.
|
|
rpc_partition.save()
|
|
|
|
# Instruct puppet to implement the change.
|
|
pecan.request.rpcapi.update_partition_config(pecan.request.context,
|
|
rpc_partition)
|
|
return Partition.convert_with_links(rpc_partition)
|
|
except exception.HTTPNotFound:
|
|
msg = _("Partition update failed: host %s partition %s : patch %s"
|
|
% (ihost['hostname'], partition['device_path'], patch))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(Partition, body=Partition)
|
|
def post(self, partition):
|
|
"""Create a new partition."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
try:
|
|
partition = partition.as_dict()
|
|
LOG.debug("partition post dict= %s" % partition)
|
|
|
|
new_partition = _create(partition)
|
|
except exception.SysinvException as e:
|
|
LOG.exception(e)
|
|
raise wsme.exc.ClientSideError(_("Invalid data"))
|
|
return Partition.convert_with_links(new_partition)
|
|
|
|
@cutils.synchronized(LOCK_NAME)
|
|
@wsme_pecan.wsexpose(None, types.uuid, status_code=204)
|
|
def delete(self, partition_uuid):
|
|
"""Delete a partition."""
|
|
if self._from_ihosts:
|
|
raise exception.OperationNotPermitted
|
|
|
|
partition = objects.partition.get_by_uuid(
|
|
pecan.request.context,
|
|
partition_uuid)
|
|
_delete(partition)
|
|
|
|
|
|
def _check_host(partition, ihost, idisk):
|
|
"""Semantic checks for valid host"""
|
|
# Partitions should only be created on workers/controllers.
|
|
if not ihost.personality:
|
|
raise wsme.exc.ClientSideError(_("Host %s has uninitialized "
|
|
"personality.") %
|
|
ihost.hostname)
|
|
elif ihost.personality not in [constants.CONTROLLER, constants.WORKER]:
|
|
raise wsme.exc.ClientSideError(_("Host personality must be a one of "
|
|
"[%s, %s]") %
|
|
(constants.CONTROLLER,
|
|
constants.WORKER))
|
|
|
|
# The disk must be present on the specified host.
|
|
if ihost['id'] != idisk['forihostid']:
|
|
raise wsme.exc.ClientSideError(_("The requested disk (%s) for the partition "
|
|
"is not present on host %s.") %
|
|
(idisk.uuid, ihost.hostname))
|
|
|
|
|
|
def _partition_pre_patch_checks(partition_obj, patch_obj, host_obj):
|
|
"""Check current vs. updated parameters."""
|
|
# Reject operation if we are upgrading the system.
|
|
cutils._check_upgrade(pecan.request.dbapi, host_obj)
|
|
for p in patch_obj:
|
|
if p['path'] == '/size_mib':
|
|
if not cutils.is_int_like(p['value']):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Requested partition size must be an integer "
|
|
"greater than 0: %s GiB") % p['value'] / 1024)
|
|
if int(p['value']) <= 0:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Requested partition size must be an integer "
|
|
"greater than 0: %s GiB") % p['value'] / 1024)
|
|
if int(p['value']) <= partition_obj.size_mib:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Requested partition size must be larger than current "
|
|
"size: %s GiB <= %s GiB") % (p['value'] / 1024,
|
|
math.floor(float(partition_obj.size_mib) / 1024 * 1000) / 1000.0))
|
|
|
|
|
|
def _is_user_created_partition(guid):
|
|
"""Check if a GUID is of LVM PV type."""
|
|
if guid == constants.USER_PARTITION_PHYSICAL_VOLUME or guid is None:
|
|
return True
|
|
return False
|
|
|
|
|
|
def _build_device_node_path(partition):
|
|
"""Builds the partition device path and device node based on last
|
|
partition number and assigned disk.
|
|
"""
|
|
idisk_uuid = partition.get('idisk_uuid')
|
|
idisk = pecan.request.dbapi.idisk_get(idisk_uuid)
|
|
partitions = pecan.request.dbapi.partition_get_by_idisk(
|
|
idisk_uuid, sort_key='device_path')
|
|
if partitions:
|
|
if constants.DEVICE_NAME_NVME in idisk.device_node:
|
|
device_node = "%sp%s" %\
|
|
(idisk.device_node, len(partitions) + 1)
|
|
else:
|
|
device_node = "%s%s" % (idisk.device_node, len(partitions) + 1)
|
|
device_path = "%s-part%s" % (idisk.device_path, len(partitions) + 1)
|
|
else:
|
|
if constants.DEVICE_NAME_NVME in idisk.device_node:
|
|
device_node = idisk.device_node + "p1"
|
|
else:
|
|
device_node = idisk.device_node + '1'
|
|
device_path = idisk.device_path + '-part1'
|
|
|
|
return device_node, device_path
|
|
|
|
|
|
def _enough_avail_space_on_disk(partition_size_mib, idisk):
|
|
"""Checks that there is enough space on the disk to accommodate the
|
|
required partition.
|
|
:returns None if the disk can't accommodate the partition
|
|
The disk's ID if the disk can accommodate the partition
|
|
"""
|
|
return idisk.available_mib >= partition_size_mib
|
|
|
|
|
|
def _check_partition_type(partition):
|
|
"""Checks that a partition is a user created partition and raises Client
|
|
Error if not.
|
|
"""
|
|
if not _is_user_created_partition(partition.get('type_guid')):
|
|
raise wsme.exc.ClientSideError(_("This type of partition does not "
|
|
"support the requested operation."))
|
|
|
|
|
|
def _check_for_outstanding_requests(partition, idisk):
|
|
"""Checks that a requested partition change isn't on a host/disk that
|
|
already has an outstanding request.
|
|
"""
|
|
# TODO(rchurch): Check existing partitions and make sure we don't have any
|
|
# partitions being changed for an existing host/disk pairing. If
|
|
# so => reject request.
|
|
pass
|
|
|
|
|
|
def _are_partition_operations_simultaneous(ihost, partition, operation):
|
|
"""Check that Create and Delete requests are serialized per host.
|
|
:param ihost the ihost object
|
|
:param partition dict partition request
|
|
:param operation Delete/Create
|
|
:return ClientSideError if there is another partition operation processed
|
|
"""
|
|
host_partitions = pecan.request.dbapi.partition_get_all(
|
|
forihostid=partition['forihostid'])
|
|
|
|
if (ihost.invprovision in
|
|
[constants.PROVISIONED, constants.PROVISIONING]):
|
|
if not (all(host_partition.get('status') in
|
|
[constants.PARTITION_READY_STATUS,
|
|
constants.PARTITION_IN_USE_STATUS,
|
|
constants.PARTITION_CREATE_ON_UNLOCK_STATUS,
|
|
constants.PARTITION_ERROR_STATUS,
|
|
constants.PARTITION_ERROR_STATUS_INTERNAL]
|
|
for host_partition in host_partitions)):
|
|
raise wsme.exc.ClientSideError(
|
|
"Cannot %s a partition while another partition "
|
|
"is being %sd. Wait for all other partitions to "
|
|
"finish %sing." % (operation, operation, operation[:-1]))
|
|
|
|
|
|
def _semantic_checks(operation, partition):
|
|
# Semantic checks
|
|
LOG.debug("PART Partition semantic checks for %s operation" % operation)
|
|
ihost = pecan.request.dbapi.ihost_get(partition['forihostid'])
|
|
|
|
# Get disk.
|
|
idiskid = partition.get('idisk_id') or partition.get('idisk_uuid')
|
|
idisk = pecan.request.dbapi.idisk_get(idiskid)
|
|
|
|
# Check host and host state.
|
|
_check_host(partition, ihost, idisk)
|
|
|
|
# Make sure this partition's type is valid.
|
|
_check_partition_type(partition)
|
|
|
|
# Check existing partitions and make sure we don't have any partitions
|
|
# being changed for an existing host/disk pairing. If so => reject request.
|
|
_check_for_outstanding_requests(partition, idisk)
|
|
|
|
# Semantic checks based on operation.
|
|
if operation == constants.PARTITION_CMD_CREATE:
|
|
############
|
|
# CREATING #
|
|
############
|
|
if int(partition['size_mib']) <= 0:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Partition size must be greater than 0."))
|
|
|
|
# Check if there is enough space on the disk to accommodate the
|
|
# partition.
|
|
if not _enough_avail_space_on_disk(partition.get('size_mib'), idisk):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Requested size %s GiB is larger than the %s GiB "
|
|
"available.") % (partition['size_mib'] / 1024,
|
|
math.floor(float(idisk.available_mib) / 1024 * 1000) / 1000.0))
|
|
|
|
_are_partition_operations_simultaneous(ihost, partition,
|
|
constants.PARTITION_CMD_CREATE)
|
|
|
|
# Enough space is availabe, save the disk ID.
|
|
if uuidutils.is_uuid_like(idiskid):
|
|
idisk_id = idisk['id']
|
|
else:
|
|
idisk_id = idiskid
|
|
partition.update({'idisk_id': idisk_id})
|
|
|
|
elif operation == constants.PARTITION_CMD_MODIFY:
|
|
#############
|
|
# MODIFYING #
|
|
#############
|
|
# Only allow in-service modify of partitions. If the host isn't
|
|
# provisioned just limit operations to create/delete.
|
|
if ihost.invprovision != constants.PROVISIONED:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Only partition Add/Delete operations are allowed on an "
|
|
"unprovisioned host."))
|
|
|
|
# Allow modification of in-use PVs only for cinder-volumes
|
|
ipv_uuid = partition.get('ipv_uuid')
|
|
ipv_lvg_name = None
|
|
if ipv_uuid:
|
|
ipv_lvg_name = pecan.request.dbapi.ipv_get(ipv_uuid)['lvm_vg_name']
|
|
if (ipv_lvg_name != constants.LVG_CINDER_VOLUMES and
|
|
(ipv_uuid or
|
|
partition.get('status') == constants.PARTITION_IN_USE_STATUS)):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not modify partition. A physical volume (%s) is "
|
|
"currently associated with this partition.") %
|
|
partition.get('device_node'))
|
|
|
|
if (ipv_lvg_name == constants.LVG_CINDER_VOLUMES):
|
|
if (utils.get_system_mode() == constants.SYSTEM_MODE_SIMPLEX):
|
|
if ihost['administrative'] != constants.ADMIN_LOCKED:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Cannot modify the partition (%(dev_node)s) associated with "
|
|
"the physical volume (%(PV)s) while the host is unlocked.") %
|
|
{'dev_node': partition.get('device_node'), 'PV': ipv_uuid})
|
|
# TODO(oponcea) Deny modifications if instances are still running.
|
|
elif utils.is_host_active_controller(ihost):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can only modify the partition (%(dev_node)s) associated with the physical "
|
|
"volume (%(PV)s) if the personality is 'Controller-Standby'") %
|
|
{'dev_node': partition.get('device_node'), 'PV': ipv_uuid})
|
|
|
|
# Prevent modifying a partition that is in creating state.
|
|
allowed_states = [constants.PARTITION_READY_STATUS]
|
|
if ipv_lvg_name == constants.LVG_CINDER_VOLUMES:
|
|
allowed_states.append(constants.PARTITION_IN_USE_STATUS)
|
|
status = partition.get('status')
|
|
if status not in allowed_states:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not modify partition. Only partitions in the %s state "
|
|
"can be modified.") %
|
|
constants.PARTITION_STATUS_MSG[
|
|
constants.PARTITION_READY_STATUS])
|
|
|
|
# Check that the partition to modify is the last partition.
|
|
if not cutils.is_partition_the_last(pecan.request.dbapi,
|
|
partition):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not modify partition. Only the last partition on disk "
|
|
"can be modified."))
|
|
|
|
# Obtain the current partition info.
|
|
crt_part = pecan.request.dbapi.partition_get(partition.get('uuid'))
|
|
crt_part_size = crt_part.size_mib
|
|
new_part_size = partition.get('size_mib')
|
|
extra_size = new_part_size - crt_part_size
|
|
|
|
# Check if there is enough space to enlarge the partition.
|
|
if not _enough_avail_space_on_disk(extra_size, idisk):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Requested extra size %s GiB is larger than the %s GiB "
|
|
"available.") % (extra_size / 1024,
|
|
math.floor(float(idisk.available_mib) / 1024 * 1000) / 1000.0))
|
|
|
|
elif operation == constants.PARTITION_CMD_DELETE:
|
|
############
|
|
# DELETING #
|
|
############
|
|
# Make sure that there is no PV associated with this partition
|
|
if (partition.get('ipv_uuid') or
|
|
partition.get('status') == constants.PARTITION_IN_USE_STATUS):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not delete partition. A physical volume (%s) is "
|
|
"currently associated with this partition") %
|
|
partition.get('device_node'))
|
|
|
|
_are_partition_operations_simultaneous(ihost, partition,
|
|
constants.PARTITION_CMD_DELETE)
|
|
|
|
status = partition.get('status')
|
|
if status == constants.PARTITION_READY_STATUS:
|
|
# Check that the partition to delete is the last partition.
|
|
if not cutils.is_partition_the_last(pecan.request.dbapi,
|
|
partition):
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not delete partition. Only the last partition on "
|
|
"disk can be deleted."))
|
|
elif status not in constants.PARTITION_STATUS_OK_TO_DELETE:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Can not delete partition. Only partitions in one of these "
|
|
"states can be deleted: %s") % ", ".join(
|
|
map(constants.PARTITION_STATUS_MSG.get,
|
|
constants.PARTITION_STATUS_OK_TO_DELETE)))
|
|
else:
|
|
raise wsme.exc.ClientSideError(
|
|
_("Internal Error: Invalid Partition operation: %s" % operation))
|
|
|
|
return partition
|
|
|
|
|
|
def _create(partition, iprofile=None, applyprofile=None):
|
|
# Reject operation if we are upgrading the system.
|
|
ihostid = partition.get('forihostid') or partition.get('ihost_uuid')
|
|
ihost = pecan.request.dbapi.ihost_get(ihostid)
|
|
cutils._check_upgrade(pecan.request.dbapi, ihost)
|
|
|
|
if uuidutils.is_uuid_like(ihostid):
|
|
forihostid = ihost['id']
|
|
else:
|
|
forihostid = ihostid
|
|
partition.update({'forihostid': forihostid})
|
|
|
|
# Add any additional default values
|
|
|
|
# Semantic Checks
|
|
_semantic_checks(constants.PARTITION_CMD_CREATE, partition)
|
|
|
|
# Set the proposed device_path
|
|
partition['device_node'], partition['device_path'] =\
|
|
_build_device_node_path(partition)
|
|
|
|
# Set the status of the new partition
|
|
if (ihost.invprovision in [constants.PROVISIONED,
|
|
constants.PROVISIONING] and
|
|
not iprofile):
|
|
partition['status'] = constants.PARTITION_CREATE_IN_SVC_STATUS
|
|
else:
|
|
partition['status'] = constants.PARTITION_CREATE_ON_UNLOCK_STATUS
|
|
# If the host is unprovisioned, reflect the size of this partition
|
|
# in the available space reported for the disk.
|
|
idiskid = partition.get('idisk_id') or partition.get('idisk_uuid')
|
|
idisk = pecan.request.dbapi.idisk_get(idiskid)
|
|
new_available_mib = idisk.available_mib - partition['size_mib']
|
|
pecan.request.dbapi.idisk_update(
|
|
idiskid,
|
|
{'available_mib': new_available_mib})
|
|
|
|
try:
|
|
# Update the database
|
|
new_partition = pecan.request.dbapi.partition_create(forihostid,
|
|
partition)
|
|
# Check if this host has been provisioned. If so, attempt an in-service
|
|
# action. If not, we'll just stage the DB changes to and let the unlock
|
|
# apply the manifest changes
|
|
# - PROVISIONED: standard controller/worker (after config_controller)
|
|
# - PROVISIONING: AIO (after config_controller) and before worker
|
|
# configuration
|
|
if (ihost.invprovision in [constants.PROVISIONED,
|
|
constants.PROVISIONING] and
|
|
not iprofile):
|
|
# Instruct puppet to implement the change
|
|
pecan.request.rpcapi.update_partition_config(pecan.request.context,
|
|
partition)
|
|
except exception.HTTPNotFound:
|
|
msg = _("Creating partition failed for host %s ") % (ihost['hostname'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
except exception.PartitionAlreadyExists:
|
|
msg = _("Disk partition %s already exists." % partition.get('device_path'))
|
|
raise wsme.exc.ClientSideError(msg)
|
|
|
|
return new_partition
|
|
|
|
|
|
def _delete(partition):
|
|
# Reject operation if we are upgrading the system unless it is a new host.
|
|
ihostid = partition.get('forihostid') or partition.get('ihost_uuid')
|
|
ihost = pecan.request.dbapi.ihost_get(ihostid)
|
|
cutils._check_upgrade(pecan.request.dbapi, ihost)
|
|
|
|
# Semantic Checks.
|
|
_semantic_checks(constants.PARTITION_CMD_DELETE, partition)
|
|
|
|
if partition.get('status') in constants.PARTITION_STATUS_SEND_DELETE_RPC:
|
|
|
|
# Set the status of the partition
|
|
part_dict = {'status': constants.PARTITION_DELETING_STATUS}
|
|
|
|
# Mark the partition as deleting and send the request to the host.
|
|
try:
|
|
|
|
pecan.request.dbapi.partition_update(partition['uuid'], part_dict)
|
|
|
|
# Instruct puppet to implement the change
|
|
pecan.request.rpcapi.update_partition_config(pecan.request.context,
|
|
partition)
|
|
|
|
except exception.HTTPNotFound:
|
|
msg = _("Marking partition for deletion failed: host %s") %\
|
|
(ihost['hostname'])
|
|
raise wsme.exc.ClientSideError(msg)
|
|
else:
|
|
if (partition.get('status') ==
|
|
constants.PARTITION_CREATE_ON_UNLOCK_STATUS):
|
|
idiskid = partition.get('idisk_id') or partition.get('idisk_uuid')
|
|
idisk = pecan.request.dbapi.idisk_get(idiskid)
|
|
new_available_mib = idisk.available_mib + partition['size_mib']
|
|
pecan.request.dbapi.idisk_update(
|
|
idiskid,
|
|
{'available_mib': new_available_mib})
|
|
# Handle the delete case where the create failed (partitioning issue or
|
|
# puppet issue) and we don't have a valid device_path or when the
|
|
# partition will be created on unlock. Just delete the partition entry.
|
|
try:
|
|
pecan.request.dbapi.partition_destroy(partition['uuid'])
|
|
except exception.HTTPNotFound:
|
|
msg = _("Partition deletion failed for host %s") %\
|
|
(ihost['hostname'])
|
|
raise wsme.exc.ClientSideError(msg)
|