summaryrefslogtreecommitdiff
path: root/nova/cmd/manage.py
diff options
context:
space:
mode:
Diffstat (limited to 'nova/cmd/manage.py')
-rw-r--r--nova/cmd/manage.py181
1 files changed, 170 insertions, 11 deletions
diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py
index fd6a499b78..45ae678ab4 100644
--- a/nova/cmd/manage.py
+++ b/nova/cmd/manage.py
@@ -122,6 +122,10 @@ def format_dict(dct, dict_property="Property", dict_value='Value',
"""
pt = prettytable.PrettyTable([dict_property, dict_value])
pt.align = 'l'
+ # starting in PrettyTable 3.4.0 we need to also set the header
+ # as align now only applies to the data.
+ if hasattr(pt, 'header_align'):
+ pt.header_align = 'l'
for k, v in sorted(dct.items(), key=sort_key):
# convert dict to str to check length
if isinstance(v, dict):
@@ -752,15 +756,7 @@ class CellV2Commands(object):
# worry about parsing and splitting a URL which could have special
# characters in the password, which makes parsing a nightmare.
url = sqla_url.make_url(connection)
-
- # TODO(gibi): remove hasattr() conditional in favor of "url.set()"
- # when SQLAlchemy 1.4 is the minimum version in requirements
- if hasattr(url, "set"):
- url = url.set(database=url.database + '_cell0')
- else:
- # TODO(zzzeek): remove when SQLAlchemy 1.4
- # is the minimum version in requirements
- url.database = url.database + '_cell0'
+ url = url.set(database=url.database + '_cell0')
return urlparse.unquote(str(url))
@@ -2217,7 +2213,7 @@ class PlacementCommands(object):
output(_('No cells to process.'))
return 4
- placement = report.SchedulerReportClient()
+ placement = report.report_client_singleton()
neutron = None
if heal_port_allocations:
@@ -2718,7 +2714,7 @@ class PlacementCommands(object):
if verbose:
output = lambda msg: print(msg)
- placement = report.SchedulerReportClient()
+ placement = report.report_client_singleton()
# Resets two in-memory dicts for knowing instances per compute node
self.cn_uuid_mapping = collections.defaultdict(tuple)
self.instances_mapping = collections.defaultdict(list)
@@ -3192,6 +3188,168 @@ class VolumeAttachmentCommands(object):
return 1
+class ImagePropertyCommands:
+
+ @action_description(_("Show the value of an instance image property."))
+ @args(
+ 'instance_uuid', metavar='<instance_uuid>',
+ help='UUID of the instance')
+ @args(
+ 'property', metavar='<image_property>',
+ help='Image property to show')
+ def show(self, instance_uuid=None, image_property=None):
+ """Show value of a given instance image property.
+
+ Return codes:
+ * 0: Command completed successfully.
+ * 1: An unexpected error happened.
+ * 2: Instance not found.
+ * 3: Image property not found.
+ """
+ try:
+ ctxt = context.get_admin_context()
+ im = objects.InstanceMapping.get_by_instance_uuid(
+ ctxt, instance_uuid)
+ with context.target_cell(ctxt, im.cell_mapping) as cctxt:
+ instance = objects.Instance.get_by_uuid(
+ cctxt, instance_uuid, expected_attrs=['system_metadata'])
+ image_property = instance.system_metadata.get(
+ f'image_{image_property}')
+ if image_property:
+ print(image_property)
+ return 0
+ else:
+ print(f'Image property {image_property} not found '
+ f'for instance {instance_uuid}.')
+ return 3
+ except (
+ exception.InstanceNotFound,
+ exception.InstanceMappingNotFound,
+ ) as e:
+ print(str(e))
+ return 2
+ except Exception as e:
+ print(f'Unexpected error, see nova-manage.log for the full '
+ f'trace: {str(e)}')
+ LOG.exception('Unexpected error')
+ return 1
+
+ def _validate_image_properties(self, image_properties):
+ """Validate the provided image property names and values
+
+ :param image_properties: List of image property names and values
+ """
+ # Sanity check the format of the provided properties, this should be
+ # in the format of name=value.
+ if any(x for x in image_properties if '=' not in x):
+ raise exception.InvalidInput(
+ "--property should use the format key=value")
+
+ # Transform the list of delimited properties to a dict
+ image_properties = dict(prop.split('=') for prop in image_properties)
+
+ # Validate the names of each property by checking against the o.vo
+ # fields currently listed by ImageProps. We can't use from_dict to
+ # do this as it silently ignores invalid property keys.
+ for image_property_name in image_properties.keys():
+ if image_property_name not in objects.ImageMetaProps.fields:
+ raise exception.InvalidImagePropertyName(
+ image_property_name=image_property_name)
+
+ # Validate the values by creating an object from the provided dict.
+ objects.ImageMetaProps.from_dict(image_properties)
+
+ # Return the dict so we can update the instance system_metadata
+ return image_properties
+
+ def _update_image_properties(self, instance, image_properties):
+ """Update instance image properties
+
+ :param instance: The instance to update
+ :param image_properties: List of image properties and values to update
+ """
+ # Check the state of the instance
+ allowed_states = [
+ obj_fields.InstanceState.STOPPED,
+ obj_fields.InstanceState.SHELVED,
+ obj_fields.InstanceState.SHELVED_OFFLOADED,
+ ]
+ if instance.vm_state not in allowed_states:
+ raise exception.InstanceInvalidState(
+ instance_uuid=instance.uuid, attr='vm_state',
+ state=instance.vm_state,
+ method='image_property set (must be STOPPED, SHELVED, OR '
+ 'SHELVED_OFFLOADED).')
+
+ # Validate the property names and values
+ image_properties = self._validate_image_properties(image_properties)
+
+ # Update the image properties and save the instance record
+ for image_property, value in image_properties.items():
+ instance.system_metadata[f'image_{image_property}'] = value
+
+ # Save and return 0
+ instance.save()
+ return 0
+
+ @action_description(_(
+ "Set the values of instance image properties stored in the database. "
+ "This is only allowed for " "instances with a STOPPED, SHELVED or "
+ "SHELVED_OFFLOADED vm_state."))
+ @args(
+ 'instance_uuid', metavar='<instance_uuid>',
+ help='UUID of the instance')
+ @args(
+ '--property', metavar='<image_property>', action='append',
+ dest='image_properties',
+ help='Image property to set using the format name=value. For example: '
+ '--property hw_disk_bus=virtio --property hw_cdrom_bus=sata')
+ def set(self, instance_uuid=None, image_properties=None):
+ """Set instance image property values
+
+ Return codes:
+ * 0: Command completed successfully.
+ * 1: An unexpected error happened.
+ * 2: Unable to find instance.
+ * 3: Instance is in an invalid state.
+ * 4: Invalid input format.
+ * 5: Invalid image property name.
+ * 6: Invalid image property value.
+ """
+ try:
+ ctxt = context.get_admin_context()
+ im = objects.InstanceMapping.get_by_instance_uuid(
+ ctxt, instance_uuid)
+ with context.target_cell(ctxt, im.cell_mapping) as cctxt:
+ instance = objects.Instance.get_by_uuid(
+ cctxt, instance_uuid, expected_attrs=['system_metadata'])
+ return self._update_image_properties(
+ instance, image_properties)
+ except ValueError as e:
+ print(str(e))
+ return 6
+ except exception.InvalidImagePropertyName as e:
+ print(str(e))
+ return 5
+ except exception.InvalidInput as e:
+ print(str(e))
+ return 4
+ except exception.InstanceInvalidState as e:
+ print(str(e))
+ return 3
+ except (
+ exception.InstanceNotFound,
+ exception.InstanceMappingNotFound,
+ ) as e:
+ print(str(e))
+ return 2
+ except Exception as e:
+ print('Unexpected error, see nova-manage.log for the full '
+ 'trace: %s ' % str(e))
+ LOG.exception('Unexpected error')
+ return 1
+
+
CATEGORIES = {
'api_db': ApiDbCommands,
'cell_v2': CellV2Commands,
@@ -3199,6 +3357,7 @@ CATEGORIES = {
'placement': PlacementCommands,
'libvirt': LibvirtCommands,
'volume_attachment': VolumeAttachmentCommands,
+ 'image_property': ImagePropertyCommands,
}