diff options
Diffstat (limited to 'openstackclient/compute')
| -rw-r--r-- | openstackclient/compute/v2/hypervisor.py | 137 | ||||
| -rw-r--r-- | openstackclient/compute/v2/hypervisor_stats.py | 40 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server.py | 295 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server_migration.py | 48 | ||||
| -rw-r--r-- | openstackclient/compute/v2/usage.py | 69 |
5 files changed, 387 insertions, 202 deletions
diff --git a/openstackclient/compute/v2/hypervisor.py b/openstackclient/compute/v2/hypervisor.py index 5f7497b5..d4b4003b 100644 --- a/openstackclient/compute/v2/hypervisor.py +++ b/openstackclient/compute/v2/hypervisor.py @@ -18,8 +18,8 @@ import json import re -from novaclient import api_versions from novaclient import exceptions as nova_exceptions +from openstack import utils as sdk_utils from osc_lib.cli import format_columns from osc_lib.command import command from osc_lib import exceptions @@ -28,11 +28,44 @@ from osc_lib import utils from openstackclient.i18n import _ +def _get_hypervisor_columns(item, client): + column_map = {'name': 'hypervisor_hostname'} + hidden_columns = ['location', 'servers'] + + if sdk_utils.supports_microversion(client, '2.88'): + hidden_columns.extend([ + 'current_workload', + 'disk_available', + 'local_disk_free', + 'local_disk_size', + 'local_disk_used', + 'memory_free', + 'memory_size', + 'memory_used', + 'running_vms', + 'vcpus_used', + 'vcpus', + ]) + else: + column_map.update({ + 'disk_available': 'disk_available_least', + 'local_disk_free': 'free_disk_gb', + 'local_disk_size': 'local_gb', + 'local_disk_used': 'local_gb_used', + 'memory_free': 'free_ram_mb', + 'memory_used': 'memory_mb_used', + 'memory_size': 'memory_mb', + }) + + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class ListHypervisor(command.Lister): _description = _("List hypervisors") def get_parser(self, prog_name): - parser = super(ListHypervisor, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( '--matching', metavar='<hostname>', @@ -67,7 +100,7 @@ class ListHypervisor(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute list_opts = {} @@ -78,7 +111,7 @@ class ListHypervisor(command.Lister): raise exceptions.CommandError(msg) if parsed_args.marker: - if compute_client.api_version < api_versions.APIVersion('2.33'): + if not sdk_utils.supports_microversion(compute_client, '2.33'): msg = _( '--os-compute-api-version 2.33 or greater is required to ' 'support the --marker option' @@ -87,7 +120,7 @@ class ListHypervisor(command.Lister): list_opts['marker'] = parsed_args.marker if parsed_args.limit: - if compute_client.api_version < api_versions.APIVersion('2.33'): + if not sdk_utils.supports_microversion(compute_client, '2.33'): msg = _( '--os-compute-api-version 2.33 or greater is required to ' 'support the --limit option' @@ -95,23 +128,43 @@ class ListHypervisor(command.Lister): raise exceptions.CommandError(msg) list_opts['limit'] = parsed_args.limit - columns = ( + column_headers = ( "ID", "Hypervisor Hostname", "Hypervisor Type", "Host IP", "State" ) + columns = ( + 'id', + 'name', + 'hypervisor_type', + 'host_ip', + 'state' + ) if parsed_args.long: - columns += ("vCPUs Used", "vCPUs", "Memory MB Used", "Memory MB") + if not sdk_utils.supports_microversion(compute_client, '2.88'): + column_headers += ( + 'vCPUs Used', + 'vCPUs', + 'Memory MB Used', + 'Memory MB' + ) + columns += ( + 'vcpus_used', + 'vcpus', + 'memory_used', + 'memory_size' + ) if parsed_args.matching: - data = compute_client.hypervisors.search(parsed_args.matching) + data = compute_client.find_hypervisor( + parsed_args.matching, ignore_missing=False) else: - data = compute_client.hypervisors.list(**list_opts) + data = compute_client.hypervisors(**list_opts, details=True) return ( - columns, + column_headers, (utils.get_item_properties(s, columns) for s in data), ) @@ -120,7 +173,7 @@ class ShowHypervisor(command.ShowOne): _description = _("Display hypervisor details") def get_parser(self, prog_name): - parser = super(ShowHypervisor, self).get_parser(prog_name) + parser = super().get_parser(prog_name) parser.add_argument( "hypervisor", metavar="<hypervisor>", @@ -129,20 +182,25 @@ class ShowHypervisor(command.ShowOne): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - hypervisor = utils.find_resource(compute_client.hypervisors, - parsed_args.hypervisor)._info.copy() + compute_client = self.app.client_manager.sdk_connection.compute + hypervisor = compute_client.find_hypervisor( + parsed_args.hypervisor, ignore_missing=False).copy() + + # Some of the properties in the hypervisor object need to be processed + # before they get reported to the user. We spend this section + # extracting the relevant details to be reported by modifying our + # copy of the hypervisor object. + aggregates = compute_client.aggregates() + hypervisor['aggregates'] = list() + service_details = hypervisor['service_details'] - aggregates = compute_client.aggregates.list() - hypervisor["aggregates"] = list() if aggregates: # Hypervisors in nova cells are prefixed by "<cell>@" - if "@" in hypervisor['service']['host']: - cell, service_host = hypervisor['service']['host'].split( - '@', 1) + if "@" in service_details['host']: + cell, service_host = service_details['host'].split('@', 1) else: cell = None - service_host = hypervisor['service']['host'] + service_host = service_details['host'] if cell: # The host aggregates are also prefixed by "<cell>@" @@ -154,42 +212,45 @@ class ShowHypervisor(command.ShowOne): member_of = [aggregate.name for aggregate in aggregates if service_host in aggregate.hosts] - hypervisor["aggregates"] = member_of + hypervisor['aggregates'] = member_of try: - uptime = compute_client.hypervisors.uptime(hypervisor['id'])._info + if sdk_utils.supports_microversion(compute_client, '2.88'): + uptime = hypervisor['uptime'] or '' + del hypervisor['uptime'] + else: + del hypervisor['uptime'] + uptime = compute_client.get_hypervisor_uptime( + hypervisor['id'])['uptime'] # Extract data from uptime value # format: 0 up 0, 0 users, load average: 0, 0, 0 # example: 17:37:14 up 2:33, 3 users, # load average: 0.33, 0.36, 0.34 m = re.match( r"\s*(.+)\sup\s+(.+),\s+(.+)\susers?,\s+load average:\s(.+)", - uptime['uptime']) + uptime) if m: - hypervisor["host_time"] = m.group(1) - hypervisor["uptime"] = m.group(2) - hypervisor["users"] = m.group(3) - hypervisor["load_average"] = m.group(4) + hypervisor['host_time'] = m.group(1) + hypervisor['uptime'] = m.group(2) + hypervisor['users'] = m.group(3) + hypervisor['load_average'] = m.group(4) except nova_exceptions.HTTPNotImplemented: pass - hypervisor["service_id"] = hypervisor["service"]["id"] - hypervisor["service_host"] = hypervisor["service"]["host"] - del hypervisor["service"] + hypervisor['service_id'] = service_details['id'] + hypervisor['service_host'] = service_details['host'] + del hypervisor['service_details'] - if compute_client.api_version < api_versions.APIVersion('2.28'): + if not sdk_utils.supports_microversion(compute_client, '2.28'): # microversion 2.28 transformed this to a JSON blob rather than a # string; on earlier fields, do this manually - if hypervisor['cpu_info']: - hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info']) - else: - hypervisor['cpu_info'] = {} - - columns = tuple(sorted(hypervisor)) + hypervisor['cpu_info'] = json.loads(hypervisor['cpu_info'] or '{}') + display_columns, columns = _get_hypervisor_columns( + hypervisor, compute_client) data = utils.get_dict_properties( hypervisor, columns, formatters={ 'cpu_info': format_columns.DictColumn, }) - return (columns, data) + return display_columns, data diff --git a/openstackclient/compute/v2/hypervisor_stats.py b/openstackclient/compute/v2/hypervisor_stats.py index 4493e080..cb63a800 100644 --- a/openstackclient/compute/v2/hypervisor_stats.py +++ b/openstackclient/compute/v2/hypervisor_stats.py @@ -11,19 +11,49 @@ # under the License. # - """Hypervisor Stats action implementations""" from osc_lib.command import command +from osc_lib import utils from openstackclient.i18n import _ +def _get_hypervisor_stat_columns(item): + column_map = { + # NOTE(gtema): If we decide to use SDK names - empty this + 'disk_available': 'disk_available_least', + 'local_disk_free': 'free_disk_gb', + 'local_disk_size': 'local_gb', + 'local_disk_used': 'local_gb_used', + 'memory_free': 'free_ram_mb', + 'memory_size': 'memory_mb', + 'memory_used': 'memory_mb_used', + + } + hidden_columns = ['id', 'links', 'location', 'name'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + class ShowHypervisorStats(command.ShowOne): _description = _("Display hypervisor stats details") def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - hypervisor_stats = compute_client.hypervisors.statistics().to_dict() - - return zip(*sorted(hypervisor_stats.items())) + # The command is deprecated since it is being dropped in Nova. + self.log.warning( + _("This command is deprecated.") + ) + compute_client = self.app.client_manager.sdk_connection.compute + # We do API request directly cause this deprecated method is not and + # will not be supported by OpenStackSDK. + response = compute_client.get( + '/os-hypervisors/statistics', + microversion='2.1') + hypervisor_stats = response.json().get('hypervisor_statistics') + + display_columns, columns = _get_hypervisor_stat_columns( + hypervisor_stats) + data = utils.get_dict_properties( + hypervisor_stats, columns) + return (display_columns, data) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 39b2bdc8..85693e17 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -66,6 +66,32 @@ class PowerStateColumn(cliff_columns.FormattableColumn): return 'N/A' +class AddressesColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of a server's addresses.""" + + def human_readable(self): + try: + return utils.format_dict_of_list({ + k: [i['addr'] for i in v if 'addr' in i] + for k, v in self._value.items()}) + except Exception: + return 'N/A' + + def machine_readable(self): + return {k: [i['addr'] for i in v if 'addr' in i] + for k, v in self._value.items()} + + +class HostColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of a hostname.""" + + def human_readable(self): + if self._value is None: + return '' + + return self._value + + def _get_ip_address(addresses, address_type, ip_address_family): # Old style addresses if address_type in addresses: @@ -111,14 +137,61 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): the latest details of a server after creating it. :rtype: a dict of server details """ + # Note: Some callers of this routine pass a novaclient server, and others + # pass an SDK server. Column names may be different across those cases. info = server.to_dict() if refresh: server = utils.find_resource(compute_client.servers, info['id']) info.update(server.to_dict()) + # Some commands using this routine were originally implemented with the + # nova python wrappers, and were later migrated to use the SDK. Map the + # SDK's property names to the original property names to maintain backward + # compatibility for existing users. Data is duplicated under both the old + # and new name so users can consume the data by either name. + column_map = { + 'access_ipv4': 'accessIPv4', + 'access_ipv6': 'accessIPv6', + 'admin_password': 'adminPass', + 'admin_password': 'adminPass', + 'volumes': 'os-extended-volumes:volumes_attached', + 'availability_zone': 'OS-EXT-AZ:availability_zone', + 'block_device_mapping': 'block_device_mapping_v2', + 'compute_host': 'OS-EXT-SRV-ATTR:host', + 'created_at': 'created', + 'disk_config': 'OS-DCF:diskConfig', + 'flavor_id': 'flavorRef', + 'has_config_drive': 'config_drive', + 'host_id': 'hostId', + 'fault': 'fault', + 'hostname': 'OS-EXT-SRV-ATTR:hostname', + 'hypervisor_hostname': 'OS-EXT-SRV-ATTR:hypervisor_hostname', + 'image_id': 'imageRef', + 'instance_name': 'OS-EXT-SRV-ATTR:instance_name', + 'is_locked': 'locked', + 'kernel_id': 'OS-EXT-SRV-ATTR:kernel_id', + 'launch_index': 'OS-EXT-SRV-ATTR:launch_index', + 'launched_at': 'OS-SRV-USG:launched_at', + 'power_state': 'OS-EXT-STS:power_state', + 'project_id': 'tenant_id', + 'ramdisk_id': 'OS-EXT-SRV-ATTR:ramdisk_id', + 'reservation_id': 'OS-EXT-SRV-ATTR:reservation_id', + 'root_device_name': 'OS-EXT-SRV-ATTR:root_device_name', + 'scheduler_hints': 'OS-SCH-HNT:scheduler_hints', + 'task_state': 'OS-EXT-STS:task_state', + 'terminated_at': 'OS-SRV-USG:terminated_at', + 'updated_at': 'updated', + 'user_data': 'OS-EXT-SRV-ATTR:user_data', + 'vm_state': 'OS-EXT-STS:vm_state', + } + + info.update({ + column_map[column]: data for column, data in info.items() + if column in column_map}) + # Convert the image blob to a name image_info = info.get('image', {}) - if image_info: + if image_info and any(image_info.values()): image_id = image_info.get('id', '') try: image = image_client.get_image(image_id) @@ -166,7 +239,9 @@ def _prep_server_detail(compute_client, image_client, server, refresh=True): # NOTE(dtroyer): novaclient splits these into separate entries... # Format addresses in a useful way - info['addresses'] = format_columns.DictListColumn(server.networks) + info['addresses'] = ( + AddressesColumn(info['addresses']) if 'addresses' in info + else format_columns.DictListColumn(info.get('networks'))) # Map 'metadata' field to 'properties' info['properties'] = format_columns.DictColumn(info.pop('metadata')) @@ -268,9 +343,11 @@ class AddFixedIP(command.ShowOne): return ((), ()) kwargs = { - 'net_id': net_id, - 'fixed_ip': parsed_args.fixed_ip_address, + 'net_id': net_id } + if parsed_args.fixed_ip_address: + kwargs['fixed_ips'] = [ + {"ip_address": parsed_args.fixed_ip_address}] if parsed_args.tag: kwargs['tag'] = parsed_args.tag @@ -429,8 +506,7 @@ class AddPort(command.Command): port_id = parsed_args.port kwargs = { - 'port_id': port_id, - 'fixed_ip': None, + 'port_id': port_id } if parsed_args.tag: @@ -484,8 +560,7 @@ class AddNetwork(command.Command): net_id = parsed_args.network kwargs = { - 'net_id': net_id, - 'fixed_ip': None, + 'net_id': net_id } if parsed_args.tag: @@ -721,11 +796,6 @@ class NICAction(argparse.Action): if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) - # Handle the special auto/none cases - if values in ('auto', 'none'): - getattr(namespace, self.dest).append(values) - return - if self.key: if ',' in values or '=' in values: msg = _( @@ -735,6 +805,12 @@ class NICAction(argparse.Action): raise argparse.ArgumentTypeError(msg % values) values = '='.join([self.key, values]) + else: + # Handle the special auto/none cases but only when a key isn't set + # (otherwise those could be valid values for the key) + if values in ('auto', 'none'): + getattr(namespace, self.dest).append(values) + return # We don't include 'tag' here by default since that requires a # particular microversion @@ -890,9 +966,7 @@ class CreateServer(command.ShowOne): required=True, help=_('Create server with this flavor (name or ID)'), ) - disk_group = parser.add_mutually_exclusive_group( - required=True, - ) + disk_group = parser.add_mutually_exclusive_group() disk_group.add_argument( '--image', metavar='<image>', @@ -1450,14 +1524,14 @@ class CreateServer(command.ShowOne): if volume: block_device_mapping_v2 = [{ 'uuid': volume, - 'boot_index': '0', + 'boot_index': 0, 'source_type': 'volume', 'destination_type': 'volume' }] elif snapshot: block_device_mapping_v2 = [{ 'uuid': snapshot, - 'boot_index': '0', + 'boot_index': 0, 'source_type': 'snapshot', 'destination_type': 'volume', 'delete_on_termination': False @@ -1466,7 +1540,7 @@ class CreateServer(command.ShowOne): # Tell nova to create a root volume from the image provided. block_device_mapping_v2 = [{ 'uuid': image.id, - 'boot_index': '0', + 'boot_index': 0, 'source_type': 'image', 'destination_type': 'volume', 'volume_size': parsed_args.boot_from_volume @@ -1603,6 +1677,15 @@ class CreateServer(command.ShowOne): block_device_mapping_v2.append(mapping) + if not image and not any( + [bdm.get('boot_index') == 0 for bdm in block_device_mapping_v2] + ): + msg = _( + 'An image (--image, --image-property) or bootable volume ' + '(--volume, --snapshot, --block-device) is required' + ) + raise exceptions.CommandError(msg) + nics = parsed_args.nics if 'auto' in nics or 'none' in nics: @@ -1614,6 +1697,14 @@ class CreateServer(command.ShowOne): ) raise exceptions.CommandError(msg) + if compute_client.api_version < api_versions.APIVersion('2.37'): + msg = _( + '--os-compute-api-version 2.37 or greater is ' + 'required to support explicit auto-allocation of a ' + 'network or to disable network allocation' + ) + raise exceptions.CommandError(msg) + nics = nics[0] else: for nic in nics: @@ -1818,8 +1909,9 @@ class CreateServerDump(command.Command): Trigger crash dump in server(s) with features like kdump in Linux. It will create a dump file in the server(s) dumping the server(s)' - memory, and also crash the server(s). OSC sees the dump file - (server dump) as a kind of resource. + memory, and also crash the server(s). This is contingent on guest operating + system support, and the location of the dump file inside the guest will + depend on the exact guest operating system. This command requires ``--os-compute-api-version`` 2.17 or greater. """ @@ -1835,12 +1927,10 @@ class CreateServerDump(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server, - ).trigger_crash_dump() + compute_client = self.app.client_manager.sdk_connection.compute + for name_or_id in parsed_args.server: + server = compute_client.find_server(name_or_id) + server.trigger_crash_dump(compute_client) class DeleteServer(command.Command): @@ -2278,7 +2368,7 @@ class ListServer(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -2303,10 +2393,11 @@ class ListServer(command.Lister): # flavor name is given, map it to ID. flavor_id = None if parsed_args.flavor: - flavor_id = utils.find_resource( - compute_client.flavors, - parsed_args.flavor, - ).id + flavor = compute_client.find_flavor(parsed_args.flavor) + if flavor is None: + msg = _('Unable to find flavor: %s') % parsed_args.flavor + raise exceptions.CommandError(msg) + flavor_id = flavor.id # Nova only supports list servers searching by image ID. So if a # image name is given, map it to ID. @@ -2322,19 +2413,21 @@ class ListServer(command.Lister): 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, 'name': parsed_args.name, - 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, 'flavor': flavor_id, 'image': image_id, 'host': parsed_args.host, - 'tenant_id': project_id, - 'all_tenants': parsed_args.all_projects, + 'project_id': project_id, + 'all_projects': parsed_args.all_projects, 'user_id': user_id, 'deleted': parsed_args.deleted, 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + if parsed_args.instance_name is not None: + search_opts['instance_name'] = parsed_args.instance_name + if parsed_args.availability_zone: search_opts['availability_zone'] = parsed_args.availability_zone @@ -2366,7 +2459,7 @@ class ListServer(command.Lister): search_opts['power_state'] = power_state if parsed_args.tags: - if compute_client.api_version < api_versions.APIVersion('2.26'): + if not sdk_utils.supports_microversion(compute_client, '2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --tag option' @@ -2376,7 +2469,7 @@ class ListServer(command.Lister): search_opts['tags'] = ','.join(parsed_args.tags) if parsed_args.not_tags: - if compute_client.api_version < api_versions.APIVersion('2.26'): + if not sdk_utils.supports_microversion(compute_client, '2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --not-tag option' @@ -2386,7 +2479,7 @@ class ListServer(command.Lister): search_opts['not-tags'] = ','.join(parsed_args.not_tags) if parsed_args.locked: - if compute_client.api_version < api_versions.APIVersion('2.73'): + if not sdk_utils.supports_microversion(compute_client, '2.73'): msg = _( '--os-compute-api-version 2.73 or greater is required to ' 'support the --locked option' @@ -2395,7 +2488,7 @@ class ListServer(command.Lister): search_opts['locked'] = True elif parsed_args.unlocked: - if compute_client.api_version < api_versions.APIVersion('2.73'): + if not sdk_utils.supports_microversion(compute_client, '2.73'): msg = _( '--os-compute-api-version 2.73 or greater is required to ' 'support the --unlocked option' @@ -2404,10 +2497,14 @@ class ListServer(command.Lister): search_opts['locked'] = False + if parsed_args.limit is not None: + search_opts['limit'] = parsed_args.limit + search_opts['paginated'] = False + LOG.debug('search options: %s', search_opts) if search_opts['changes-before']: - if compute_client.api_version < api_versions.APIVersion('2.66'): + if not sdk_utils.supports_microversion(compute_client, '2.66'): msg = _('--os-compute-api-version 2.66 or later is required') raise exceptions.CommandError(msg) @@ -2441,15 +2538,15 @@ class ListServer(command.Lister): if parsed_args.long: columns += ( - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', + 'task_state', + 'power_state', ) column_headers += ( 'Task State', 'Power State', ) - columns += ('networks',) + columns += ('addresses',) column_headers += ('Networks',) if parsed_args.long: @@ -2471,7 +2568,7 @@ class ListServer(command.Lister): # microversion 2.47 puts the embedded flavor into the server response # body but omits the id, so if not present we just expose the original # flavor name in the output - if compute_client.api_version >= api_versions.APIVersion('2.47'): + if sdk_utils.supports_microversion(compute_client, '2.47'): columns += ('flavor_name',) column_headers += ('Flavor',) else: @@ -2493,8 +2590,8 @@ class ListServer(command.Lister): if parsed_args.long: columns += ( - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', + 'availability_zone', + 'hypervisor_hostname', 'metadata', ) column_headers += ( @@ -2503,50 +2600,48 @@ class ListServer(command.Lister): 'Properties', ) - marker_id = None - # support for additional columns if parsed_args.columns: for c in parsed_args.columns: if c in ('Project ID', 'project_id'): - columns += ('tenant_id',) + columns += ('project_id',) column_headers += ('Project ID',) if c in ('User ID', 'user_id'): columns += ('user_id',) column_headers += ('User ID',) if c in ('Created At', 'created_at'): - columns += ('created',) + columns += ('created_at',) column_headers += ('Created At',) if c in ('Security Groups', 'security_groups'): columns += ('security_groups_name',) column_headers += ('Security Groups',) if c in ("Task State", "task_state"): - columns += ('OS-EXT-STS:task_state',) + columns += ('task_state',) column_headers += ('Task State',) if c in ("Power State", "power_state"): - columns += ('OS-EXT-STS:power_state',) + columns += ('power_state',) column_headers += ('Power State',) if c in ("Image ID", "image_id"): columns += ('Image ID',) column_headers += ('Image ID',) if c in ("Flavor ID", "flavor_id"): - columns += ('Flavor ID',) + columns += ('flavor_id',) column_headers += ('Flavor ID',) if c in ('Availability Zone', "availability_zone"): - columns += ('OS-EXT-AZ:availability_zone',) + columns += ('availability_zone',) column_headers += ('Availability Zone',) if c in ('Host', "host"): - columns += ('OS-EXT-SRV-ATTR:host',) + columns += ('hypervisor_hostname',) column_headers += ('Host',) if c in ('Properties', "properties"): columns += ('Metadata',) column_headers += ('Properties',) - # convert back to tuple - column_headers = tuple(column_headers) - columns = tuple(columns) + # remove duplicates + column_headers = tuple(dict.fromkeys(column_headers)) + columns = tuple(dict.fromkeys(columns)) - if parsed_args.marker: + if parsed_args.marker is not None: # Check if both "--marker" and "--deleted" are used. # In that scenario a lookup is not needed as the marker # needs to be an ID, because find_resource does not @@ -2554,16 +2649,10 @@ class ListServer(command.Lister): if parsed_args.deleted: marker_id = parsed_args.marker else: - marker_id = utils.find_resource( - compute_client.servers, - parsed_args.marker, - ).id + marker_id = compute_client.find_server(parsed_args.marker).id + search_opts['marker'] = marker_id - data = compute_client.servers.list( - search_opts=search_opts, - marker=marker_id, - limit=parsed_args.limit, - ) + data = list(compute_client.servers(**search_opts)) images = {} flavors = {} @@ -2618,12 +2707,12 @@ class ListServer(command.Lister): # "Flavor Name" is not crucial, so we swallow any # exceptions try: - flavors[f_id] = compute_client.flavors.get(f_id) + flavors[f_id] = compute_client.find_flavor(f_id) except Exception: pass else: try: - flavors_list = compute_client.flavors.list(is_public=None) + flavors_list = compute_client.flavors(is_public=None) for i in flavors_list: flavors[i.id] = i except Exception: @@ -2632,7 +2721,7 @@ class ListServer(command.Lister): # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. for s in data: - if compute_client.api_version >= api_versions.APIVersion('2.69'): + if sdk_utils.supports_microversion(compute_client, '2.69'): # NOTE(tssurya): From 2.69, we will have the keys 'flavor' # and 'image' missing in the server response during # infrastructure failure situations. @@ -2641,7 +2730,7 @@ class ListServer(command.Lister): if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue - if 'id' in s.image: + if 'id' in s.image and s.image.id is not None: image = images.get(s.image['id']) if image: s.image_name = image.name @@ -2654,7 +2743,7 @@ class ListServer(command.Lister): s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV - if compute_client.api_version < api_versions.APIVersion('2.47'): + if not sdk_utils.supports_microversion(compute_client, '2.47'): flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name @@ -2664,7 +2753,7 @@ class ListServer(command.Lister): # Add a list with security group name as attribute for s in data: - if hasattr(s, 'security_groups'): + if hasattr(s, 'security_groups') and s.security_groups is not None: s.security_groups_name = [x["name"] for x in s.security_groups] else: s.security_groups_name = [] @@ -2677,10 +2766,10 @@ class ListServer(command.Lister): # it's on, providing useful information to a user in this # situation. if ( - compute_client.api_version >= api_versions.APIVersion('2.16') and + sdk_utils.supports_microversion(compute_client, '2.16') and parsed_args.long ): - if any([hasattr(s, 'host_status') for s in data]): + if any([s.host_status is not None for s in data]): columns += ('Host Status',) column_headers += ('Host Status',) @@ -2690,16 +2779,17 @@ class ListServer(command.Lister): utils.get_item_properties( s, columns, mixed_case_fields=( - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', + 'task_state', + 'power_state', + 'availability_zone', + 'host', ), formatters={ - 'OS-EXT-STS:power_state': PowerStateColumn, - 'networks': format_columns.DictListColumn, + 'power_state': PowerStateColumn, + 'addresses': AddressesColumn, 'metadata': format_columns.DictColumn, 'security_groups_name': format_columns.ListColumn, + 'hypervisor_hostname': HostColumn, }, ) for s in data ), @@ -2709,8 +2799,9 @@ class ListServer(command.Lister): class LockServer(command.Command): - _description = _("Lock server(s). A non-admin user will not be able to " - "execute actions") + _description = _("""Lock server(s) + +A non-admin user will not be able to execute actions.""") def get_parser(self, prog_name): parser = super(LockServer, self).get_parser(prog_name) @@ -3707,7 +3798,11 @@ class RemoveServerVolume(command.Command): class RescueServer(command.Command): - _description = _("Put server in rescue mode") + _description = _( + "Put server in rescue mode. " + "Specify ``--os-compute-api-version 2.87`` or higher to rescue a " + "server booted from a volume." + ) def get_parser(self, prog_name): parser = super(RescueServer, self).get_parser(prog_name) @@ -4283,32 +4378,34 @@ class ShowServer(command.ShowOne): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + compute_client = self.app.client_manager.sdk_connection.compute + + # Find by name or ID, then get the full details of the server + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) + server = compute_client.get_server(server) if parsed_args.diagnostics: - (resp, data) = server.diagnostics() - if not resp.status_code == 200: - self.app.stderr.write(_( - "Error retrieving diagnostics data\n" - )) - return ({}, {}) + data = compute_client.get_server_diagnostics(server) return zip(*sorted(data.items())) topology = None if parsed_args.topology: - if compute_client.api_version < api_versions.APIVersion('2.78'): + if not sdk_utils.supports_microversion(compute_client, '2.78'): msg = _( '--os-compute-api-version 2.78 or greater is required to ' 'support the --topology option' ) raise exceptions.CommandError(msg) - topology = server.topology() + topology = server.fetch_topology(compute_client) data = _prep_server_detail( - compute_client, self.app.client_manager.image, server, + # TODO(dannosliwcd): Replace these clients with SDK clients after + # all callers of _prep_server_detail() are using the SDK. + self.app.client_manager.compute, + self.app.client_manager.image, + server, refresh=False) if topology: @@ -4473,7 +4570,7 @@ class SshServer(command.Command): class StartServer(command.Command): - _description = _("Start server(s).") + _description = _("Start server(s)") def get_parser(self, prog_name): parser = super(StartServer, self).get_parser(prog_name) @@ -4505,7 +4602,7 @@ class StartServer(command.Command): class StopServer(command.Command): - _description = _("Stop server(s).") + _description = _("Stop server(s)") def get_parser(self, prog_name): parser = super(StopServer, self).get_parser(prog_name) diff --git a/openstackclient/compute/v2/server_migration.py b/openstackclient/compute/v2/server_migration.py index 919b67bd..016d15d7 100644 --- a/openstackclient/compute/v2/server_migration.py +++ b/openstackclient/compute/v2/server_migration.py @@ -15,6 +15,7 @@ import uuid from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib.command import command from osc_lib import exceptions from osc_lib import utils @@ -130,22 +131,22 @@ class ListMigration(command.Lister): # the same as the column header names. columns = [ 'source_node', 'dest_node', 'source_compute', 'dest_compute', - 'dest_host', 'status', 'instance_uuid', 'old_instance_type_id', - 'new_instance_type_id', 'created_at', 'updated_at', + 'dest_host', 'status', 'server_id', 'old_flavor_id', + 'new_flavor_id', 'created_at', 'updated_at', ] # Insert migrations UUID after ID - if compute_client.api_version >= api_versions.APIVersion("2.59"): + if sdk_utils.supports_microversion(compute_client, "2.59"): column_headers.insert(0, "UUID") columns.insert(0, "uuid") - if compute_client.api_version >= api_versions.APIVersion("2.23"): + if sdk_utils.supports_microversion(compute_client, "2.23"): column_headers.insert(0, "Id") columns.insert(0, "id") column_headers.insert(len(column_headers) - 2, "Type") columns.insert(len(columns) - 2, "migration_type") - if compute_client.api_version >= api_versions.APIVersion("2.80"): + if sdk_utils.supports_microversion(compute_client, "2.80"): if parsed_args.project: column_headers.insert(len(column_headers) - 2, "Project") columns.insert(len(columns) - 2, "project_id") @@ -159,19 +160,23 @@ class ListMigration(command.Lister): ) def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity - search_opts = { - 'host': parsed_args.host, - 'status': parsed_args.status, - } + search_opts = {} + + if parsed_args.host is not None: + search_opts['host'] = parsed_args.host + + if parsed_args.status is not None: + search_opts['status'] = parsed_args.status if parsed_args.server: - search_opts['instance_uuid'] = utils.find_resource( - compute_client.servers, - parsed_args.server, - ).id + server = compute_client.find_server(parsed_args.server) + if server is None: + msg = _('Unable to find server: %s') % parsed_args.server + raise exceptions.CommandError(msg) + search_opts['instance_uuid'] = server.id if parsed_args.type: migration_type = parsed_args.type @@ -181,7 +186,7 @@ class ListMigration(command.Lister): search_opts['migration_type'] = migration_type if parsed_args.marker: - if compute_client.api_version < api_versions.APIVersion('2.59'): + if not sdk_utils.supports_microversion(compute_client, "2.59"): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --marker option' @@ -190,16 +195,17 @@ class ListMigration(command.Lister): search_opts['marker'] = parsed_args.marker if parsed_args.limit: - if compute_client.api_version < api_versions.APIVersion('2.59'): + if not sdk_utils.supports_microversion(compute_client, "2.59"): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --limit option' ) raise exceptions.CommandError(msg) search_opts['limit'] = parsed_args.limit + search_opts['paginated'] = False if parsed_args.changes_since: - if compute_client.api_version < api_versions.APIVersion('2.59'): + if not sdk_utils.supports_microversion(compute_client, "2.59"): msg = _( '--os-compute-api-version 2.59 or greater is required to ' 'support the --changes-since option' @@ -208,7 +214,7 @@ class ListMigration(command.Lister): search_opts['changes_since'] = parsed_args.changes_since if parsed_args.changes_before: - if compute_client.api_version < api_versions.APIVersion('2.66'): + if not sdk_utils.supports_microversion(compute_client, "2.66"): msg = _( '--os-compute-api-version 2.66 or greater is required to ' 'support the --changes-before option' @@ -217,7 +223,7 @@ class ListMigration(command.Lister): search_opts['changes_before'] = parsed_args.changes_before if parsed_args.project: - if compute_client.api_version < api_versions.APIVersion('2.80'): + if not sdk_utils.supports_microversion(compute_client, "2.80"): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --project option' @@ -231,7 +237,7 @@ class ListMigration(command.Lister): ).id if parsed_args.user: - if compute_client.api_version < api_versions.APIVersion('2.80'): + if not sdk_utils.supports_microversion(compute_client, "2.80"): msg = _( '--os-compute-api-version 2.80 or greater is required to ' 'support the --user option' @@ -244,7 +250,7 @@ class ListMigration(command.Lister): parsed_args.user_domain, ).id - migrations = compute_client.migrations.list(**search_opts) + migrations = list(compute_client.migrations(**search_opts)) return self.print_migrations(parsed_args, compute_client, migrations) diff --git a/openstackclient/compute/v2/usage.py b/openstackclient/compute/v2/usage.py index 69fa04e8..86f538a7 100644 --- a/openstackclient/compute/v2/usage.py +++ b/openstackclient/compute/v2/usage.py @@ -15,12 +15,10 @@ """Usage action implementations""" -import collections import datetime import functools from cliff import columns as cliff_columns -from novaclient import api_versions from osc_lib.command import command from osc_lib import utils @@ -58,7 +56,7 @@ class ProjectColumn(cliff_columns.FormattableColumn): class CountColumn(cliff_columns.FormattableColumn): def human_readable(self): - return len(self._value) + return len(self._value) if self._value is not None else None class FloatColumn(cliff_columns.FormattableColumn): @@ -69,7 +67,7 @@ class FloatColumn(cliff_columns.FormattableColumn): def _formatters(project_cache): return { - 'tenant_id': functools.partial( + 'project_id': functools.partial( ProjectColumn, project_cache=project_cache), 'server_usages': CountColumn, 'total_memory_mb_usage': FloatColumn, @@ -102,10 +100,10 @@ def _merge_usage(usage, next_usage): def _merge_usage_list(usages, next_usage_list): for next_usage in next_usage_list: - if next_usage.tenant_id in usages: - _merge_usage(usages[next_usage.tenant_id], next_usage) + if next_usage.project_id in usages: + _merge_usage(usages[next_usage.project_id], next_usage) else: - usages[next_usage.tenant_id] = next_usage + usages[next_usage.project_id] = next_usage class ListUsage(command.Lister): @@ -138,9 +136,9 @@ class ListUsage(command.Lister): else: return project - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute columns = ( - "tenant_id", + "project_id", "server_usages", "total_memory_mb_usage", "total_vcpus_usage", @@ -154,36 +152,25 @@ class ListUsage(command.Lister): "Disk GB-Hours" ) - dateformat = "%Y-%m-%d" + date_cli_format = "%Y-%m-%d" + date_api_format = "%Y-%m-%dT%H:%M:%S" now = datetime.datetime.utcnow() if parsed_args.start: - start = datetime.datetime.strptime(parsed_args.start, dateformat) + start = datetime.datetime.strptime( + parsed_args.start, date_cli_format) else: start = now - datetime.timedelta(weeks=4) if parsed_args.end: - end = datetime.datetime.strptime(parsed_args.end, dateformat) + end = datetime.datetime.strptime(parsed_args.end, date_cli_format) else: end = now + datetime.timedelta(days=1) - if compute_client.api_version < api_versions.APIVersion("2.40"): - usage_list = compute_client.usage.list(start, end, detailed=True) - else: - # If the number of instances used to calculate the usage is greater - # than CONF.api.max_limit, the usage will be split across multiple - # requests and the responses will need to be merged back together. - usages = collections.OrderedDict() - usage_list = compute_client.usage.list(start, end, detailed=True) - _merge_usage_list(usages, usage_list) - marker = _get_usage_list_marker(usage_list) - while marker: - next_usage_list = compute_client.usage.list( - start, end, detailed=True, marker=marker) - marker = _get_usage_list_marker(next_usage_list) - if marker: - _merge_usage_list(usages, next_usage_list) - usage_list = list(usages.values()) + usage_list = list(compute_client.usages( + start=start.strftime(date_api_format), + end=end.strftime(date_api_format), + detailed=True)) # Cache the project list project_cache = {} @@ -196,8 +183,8 @@ class ListUsage(command.Lister): if parsed_args.formatter == 'table' and len(usage_list) > 0: self.app.stdout.write(_("Usage from %(start)s to %(end)s: \n") % { - "start": start.strftime(dateformat), - "end": end.strftime(dateformat), + "start": start.strftime(date_cli_format), + "end": end.strftime(date_cli_format), }) return ( @@ -239,17 +226,19 @@ class ShowUsage(command.ShowOne): def take_action(self, parsed_args): identity_client = self.app.client_manager.identity - compute_client = self.app.client_manager.compute - dateformat = "%Y-%m-%d" + compute_client = self.app.client_manager.sdk_connection.compute + date_cli_format = "%Y-%m-%d" + date_api_format = "%Y-%m-%dT%H:%M:%S" now = datetime.datetime.utcnow() if parsed_args.start: - start = datetime.datetime.strptime(parsed_args.start, dateformat) + start = datetime.datetime.strptime( + parsed_args.start, date_cli_format) else: start = now - datetime.timedelta(weeks=4) if parsed_args.end: - end = datetime.datetime.strptime(parsed_args.end, dateformat) + end = datetime.datetime.strptime(parsed_args.end, date_cli_format) else: end = now + datetime.timedelta(days=1) @@ -262,19 +251,21 @@ class ShowUsage(command.ShowOne): # Get the project from the current auth project = self.app.client_manager.auth_ref.project_id - usage = compute_client.usage.get(project, start, end) + usage = compute_client.get_usage( + project=project, start=start.strftime(date_api_format), + end=end.strftime(date_api_format)) if parsed_args.formatter == 'table': self.app.stdout.write(_( "Usage from %(start)s to %(end)s on project %(project)s: \n" ) % { - "start": start.strftime(dateformat), - "end": end.strftime(dateformat), + "start": start.strftime(date_cli_format), + "end": end.strftime(date_cli_format), "project": project, }) columns = ( - "tenant_id", + "project_id", "server_usages", "total_memory_mb_usage", "total_vcpus_usage", |
