diff options
91 files changed, 4055 insertions, 1141 deletions
diff --git a/doc/source/cli/command-objects/local-ip-association.rst b/doc/source/cli/command-objects/local-ip-association.rst new file mode 100644 index 00000000..824ee4d0 --- /dev/null +++ b/doc/source/cli/command-objects/local-ip-association.rst @@ -0,0 +1,11 @@ +============================================= +Local IP Associations (local_ip_associations) +============================================= + +The resource lets users assign Local IPs to user Ports. +This is a sub-resource of the Local IP resource. + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: local ip association * diff --git a/doc/source/cli/command-objects/local-ip.rst b/doc/source/cli/command-objects/local-ip.rst new file mode 100644 index 00000000..c8c5ab47 --- /dev/null +++ b/doc/source/cli/command-objects/local-ip.rst @@ -0,0 +1,12 @@ +===================== +Local IPs (local_ips) +===================== + +Extension that allows users to create a virtual IP that can later be assigned +to multiple ports/VMs (similar to anycast IP) and is guaranteed to only be +reachable within the same physical server/node boundaries + +Network v2 + +.. autoprogram-cliff:: openstack.network.v2 + :command: local ip * diff --git a/doc/source/cli/command-objects/server-migration.rst b/doc/source/cli/command-objects/server-migration.rst index 6e2982cf..9db58e3e 100644 --- a/doc/source/cli/command-objects/server-migration.rst +++ b/doc/source/cli/command-objects/server-migration.rst @@ -9,4 +9,4 @@ supported: live migration, cold migration, resize and evacuation. Compute v2 .. autoprogram-cliff:: openstack.compute.v2 - :command: server migration list + :command: server migration * diff --git a/doc/source/cli/data/nova.csv b/doc/source/cli/data/nova.csv index dd8992f6..0ab2b2fe 100644 --- a/doc/source/cli/data/nova.csv +++ b/doc/source/cli/data/nova.csv @@ -46,7 +46,7 @@ hypervisor-show,hypervisor show,Display the details of the specified hypervisor. hypervisor-stats,hypervisor stats show,Get hypervisor statistics over all compute nodes. hypervisor-uptime,,Display the uptime of the specified hypervisor. image-create,server image create,Create a new image by taking a snapshot of a running server. -instance-action,,Show an action. +instance-action,server event show,Show an action. instance-action-list,,List actions on a server. instance-usage-audit-log,,List/Get server usage audits. interface-attach,server add port / server add floating ip / server add fixed ip,Attach a network interface to a server. diff --git a/doc/source/cli/plugin-commands/aodh.rst b/doc/source/cli/plugin-commands/aodh.rst deleted file mode 100644 index 5d8b4332..00000000 --- a/doc/source/cli/plugin-commands/aodh.rst +++ /dev/null @@ -1,4 +0,0 @@ -aodh ----- - -.. autoprogram-cliff:: openstack.alarming.v2 diff --git a/doc/source/cli/plugin-commands/index.rst b/doc/source/cli/plugin-commands/index.rst index 4e1ce54b..638dcbe5 100644 --- a/doc/source/cli/plugin-commands/index.rst +++ b/doc/source/cli/plugin-commands/index.rst @@ -7,7 +7,6 @@ Plugin Commands .. toctree:: :maxdepth: 1 - aodh barbican designate gnocchi @@ -29,6 +28,10 @@ Plugin Commands .. TODO(efried): Make pages for the following once they're fixed. +.. aodh +.. # aodhclient docs build is failing with recent pyparsing +.. # autoprogram-cliff:: openstack.alarming.v2 + .. cue .. # cueclient is not in global-requirements .. # list-plugins:: openstack.mb.v1 diff --git a/lower-constraints.txt b/lower-constraints.txt index b98b8432..495b102a 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -38,7 +38,7 @@ msgpack-python==0.4.0 munch==2.1.0 netaddr==0.7.18 netifaces==0.10.4 -openstacksdk==0.56.0 +openstacksdk==0.61.0 os-client-config==2.1.0 os-service-types==1.7.0 osc-lib==2.3.0 diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 643cb4e4..677cba03 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -535,6 +535,12 @@ class SetQuota(common.NetDetectionMixin, command.Command): action='store_true', help=_('Force quota update (only supported by compute)') ) + parser.add_argument( + '--check-limit', + action='store_true', + help=_('Check quota limit when updating (only supported by ' + 'network)') + ) return parser def take_action(self, parsed_args): @@ -561,6 +567,9 @@ class SetQuota(common.NetDetectionMixin, command.Command): volume_kwargs[k] = value network_kwargs = {} + if parsed_args.check_limit: + network_kwargs['check_limit'] = True + if self.app.client_manager.is_network_endpoint_enabled(): for k, v in NETWORK_QUOTAS.items(): value = getattr(parsed_args, k, None) diff --git a/openstackclient/compute/v2/aggregate.py b/openstackclient/compute/v2/aggregate.py index e39eb2d2..37522a78 100644 --- a/openstackclient/compute/v2/aggregate.py +++ b/openstackclient/compute/v2/aggregate.py @@ -193,12 +193,14 @@ class ListAggregate(command.Lister): "Name", "Availability Zone", "Properties", + "Hosts", ) columns = ( "ID", "Name", "Availability Zone", "Metadata", + "Hosts", ) else: column_headers = columns = ( diff --git a/openstackclient/compute/v2/flavor.py b/openstackclient/compute/v2/flavor.py index a55aba2a..8a9eb07a 100644 --- a/openstackclient/compute/v2/flavor.py +++ b/openstackclient/compute/v2/flavor.py @@ -48,7 +48,7 @@ def _get_flavor_columns(item): 'is_public': 'os-flavor-access:is_public' } - hidden_columns = ['links', 'location'] + hidden_columns = ['links', 'location', 'original_name'] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, hidden_columns) diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 08627f9b..a46fb904 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -204,7 +204,7 @@ def boolenv(*vars, default=False): return default -class AddFixedIP(command.Command): +class AddFixedIP(command.ShowOne): _description = _("Add fixed IP address to server") def get_parser(self, prog_name): @@ -231,36 +231,73 @@ class AddFixedIP(command.Command): metavar='<tag>', help=_( 'Tag for the attached interface. ' - '(supported by --os-compute-api-version 2.52 or above)' + '(supported by --os-compute-api-version 2.49 or above)' ) ) 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) - - network = compute_client.api.network_find(parsed_args.network) - - kwargs = { - 'port_id': None, - 'net_id': network['id'], - 'fixed_ip': parsed_args.fixed_ip_address, - } + compute_client = self.app.client_manager.sdk_connection.compute + server = compute_client.find_server( + parsed_args.server, + ignore_missing=False + ) if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion('2.49'): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' ) raise exceptions.CommandError(msg) + if self.app.client_manager.is_network_endpoint_enabled(): + network_client = self.app.client_manager.network + net_id = network_client.find_network( + parsed_args.network, + ignore_missing=False + ).id + else: + net_id = parsed_args.network + + if not sdk_utils.supports_microversion(compute_client, '2.44'): + compute_client.add_fixed_ip_to_server( + server.id, + net_id + ) + return ((), ()) + + kwargs = { + 'net_id': net_id, + 'fixed_ip': parsed_args.fixed_ip_address, + } + if parsed_args.tag: kwargs['tag'] = parsed_args.tag - server.interface_attach(**kwargs) + interface = compute_client.create_server_interface(server.id, **kwargs) + + columns = ( + 'port_id', 'server_id', 'net_id', 'mac_addr', 'port_state', + 'fixed_ips', + ) + column_headers = ( + 'Port ID', 'Server ID', 'Network ID', 'MAC Address', 'Port State', + 'Fixed IPs', + ) + if sdk_utils.supports_microversion(compute_client, '2.49'): + columns += ('tag',) + column_headers += ('Tag',) + + return ( + column_headers, + utils.get_item_properties( + interface, + columns, + formatters={ + 'fixed_ips': format_columns.ListDictColumn, + }, + ), + ) class AddFloatingIP(network_common.NetworkAndComputeCommand): @@ -548,24 +585,25 @@ class AddServerVolume(command.ShowOne): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.sdk_connection.compute + volume_client = self.app.client_manager.sdk_connection.volume - server = utils.find_resource( - compute_client.servers, + server = compute_client.find_server( parsed_args.server, + ignore_missing=False, ) - volume = utils.find_resource( - volume_client.volumes, + volume = volume_client.find_volume( parsed_args.volume, + ignore_missing=False, ) kwargs = { + "volumeId": volume.id, "device": parsed_args.device } if parsed_args.tag: - if compute_client.api_version < api_versions.APIVersion('2.49'): + if not sdk_utils.supports_microversion(compute_client, '2.49'): msg = _( '--os-compute-api-version 2.49 or greater is required to ' 'support the --tag option' @@ -575,7 +613,7 @@ class AddServerVolume(command.ShowOne): kwargs['tag'] = parsed_args.tag if parsed_args.enable_delete_on_termination: - if compute_client.api_version < api_versions.APIVersion('2.79'): + if not sdk_utils.supports_microversion(compute_client, '2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --enable-delete-on-termination option.' @@ -585,7 +623,7 @@ class AddServerVolume(command.ShowOne): kwargs['delete_on_termination'] = True if parsed_args.disable_delete_on_termination: - if compute_client.api_version < api_versions.APIVersion('2.79'): + if not sdk_utils.supports_microversion(compute_client, '2.79'): msg = _( '--os-compute-api-version 2.79 or greater is required to ' 'support the --disable-delete-on-termination option.' @@ -594,28 +632,23 @@ class AddServerVolume(command.ShowOne): kwargs['delete_on_termination'] = False - volume_attachment = compute_client.volumes.create_server_volume( - server.id, - volume.id, - **kwargs + volume_attachment = compute_client.create_volume_attachment( + server, + **kwargs, ) - columns = ('id', 'serverId', 'volumeId', 'device') + columns = ('id', 'server id', 'volume id', 'device') column_headers = ('ID', 'Server ID', 'Volume ID', 'Device') - if compute_client.api_version >= api_versions.APIVersion('2.49'): + if sdk_utils.supports_microversion(compute_client, '2.49'): columns += ('tag',) column_headers += ('Tag',) - if compute_client.api_version >= api_versions.APIVersion('2.79'): + if sdk_utils.supports_microversion(compute_client, '2.79'): columns += ('delete_on_termination',) column_headers += ('Delete On Termination',) return ( column_headers, - utils.get_item_properties( - volume_attachment, - columns, - mixed_case_fields=('serverId', 'volumeId'), - ) + utils.get_item_properties(volume_attachment, columns,) ) @@ -2208,15 +2241,19 @@ 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_id = utils.find_resource( + compute_client.flavors, + parsed_args.flavor, + ).id # Nova only supports list servers searching by image ID. So if a # image name is given, map it to ID. image_id = None if parsed_args.image: - image_id = image_client.find_image(parsed_args.image, - ignore_missing=False).id + image_id = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ).id search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -2324,95 +2361,100 @@ class ListServer(command.Lister): try: iso8601.parse_date(search_opts['changes-since']) except (TypeError, iso8601.ParseError): + msg = _('Invalid changes-since value: %s') raise exceptions.CommandError( - _('Invalid changes-since value: %s') % - search_opts['changes-since'] + msg % search_opts['changes-since'] ) + columns = ( + 'id', + 'name', + 'status', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + ) + if parsed_args.long: - columns = ( - 'ID', - 'Name', - 'Status', + columns += ( 'OS-EXT-STS:task_state', 'OS-EXT-STS:power_state', - 'Networks', - 'Image Name', - 'Image ID', - 'Flavor Name', - 'Flavor ID', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - 'Metadata', ) - column_headers = ( - 'ID', - 'Name', - 'Status', + column_headers += ( 'Task State', 'Power State', - 'Networks', + ) + + columns += ('networks',) + column_headers += ('Networks',) + + if parsed_args.long: + columns += ( + 'image_name', + 'image_id', + ) + column_headers += ( 'Image Name', 'Image ID', - 'Flavor Name', - 'Flavor ID', - 'Availability Zone', - 'Host', - 'Properties', ) - mixed_case_fields = [ - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - ] else: if parsed_args.no_name_lookup: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image ID', - 'Flavor ID', - ) + columns += ('image_id',) else: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image Name', + columns += ('image_name',) + column_headers += ('Image',) + + # 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'): + columns += ('flavor_name',) + column_headers += ('Flavor',) + else: + if parsed_args.long: + columns += ( + 'flavor_name', + 'flavor_id', + ) + column_headers += ( 'Flavor Name', + 'Flavor ID', ) - column_headers = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image', - 'Flavor', + else: + if parsed_args.no_name_lookup: + columns += ('flavor_id',) + else: + columns += ('flavor_name',) + column_headers += ('Flavor',) + + if parsed_args.long: + columns += ( + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + 'metadata', + ) + column_headers += ( + 'Availability Zone', + 'Host', + 'Properties', ) - mixed_case_fields = [] marker_id = None # support for additional columns if parsed_args.columns: - # convert tuple to list to edit them - column_headers = list(column_headers) - columns = list(columns) - for c in parsed_args.columns: if c in ('Project ID', 'project_id'): - columns.append('tenant_id') - column_headers.append('Project ID') + columns += ('tenant_id',) + column_headers += ('Project ID',) if c in ('User ID', 'user_id'): - columns.append('user_id') - column_headers.append('User ID') + columns += ('user_id',) + column_headers += ('User ID',) if c in ('Created At', 'created_at'): - columns.append('created') - column_headers.append('Created At') + columns += ('created',) + column_headers += ('Created At',) # convert back to tuple column_headers = tuple(column_headers) @@ -2426,25 +2468,29 @@ 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 = utils.find_resource( + compute_client.servers, + parsed_args.marker, + ).id - data = compute_client.servers.list(search_opts=search_opts, - marker=marker_id, - limit=parsed_args.limit) + data = compute_client.servers.list( + search_opts=search_opts, + marker=marker_id, + limit=parsed_args.limit) images = {} flavors = {} if data and not parsed_args.no_name_lookup: - # Create a dict that maps image_id to image object. - # Needed so that we can display the "Image Name" column. - # "Image Name" is not crucial, so we swallow any exceptions. - # The 'image' attribute can be an empty string if the server was - # booted from a volume. + # create a dict that maps image_id to image object, which is used + # to display the "Image Name" column. Note that 'image.id' can be + # empty for BFV instances and 'image' can be missing entirely if + # there are infra failures if parsed_args.name_lookup_one_by_one or image_id: - for i_id in set(filter(lambda x: x is not None, - (s.image.get('id') for s in data - if s.image))): + for i_id in set( + s.image['id'] for s in data + if s.image and s.image.get('id') + ): + # "Image Name" is not crucial, so we swallow any exceptions try: images[i_id] = image_client.get_image(i_id) except Exception: @@ -2457,12 +2503,17 @@ class ListServer(command.Lister): except Exception: pass - # Create a dict that maps flavor_id to flavor object. - # Needed so that we can display the "Flavor Name" column. - # "Flavor Name" is not crucial, so we swallow any exceptions. + # create a dict that maps flavor_id to flavor object, which is used + # to display the "Flavor Name" column. Note that 'flavor.id' is not + # present on microversion 2.47 or later and 'flavor' won't be + # present if there are infra failures if parsed_args.name_lookup_one_by_one or flavor_id: - for f_id in set(filter(lambda x: x is not None, - (s.flavor.get('id') for s in data))): + for f_id in set( + s.flavor['id'] for s in data + if s.flavor and s.flavor.get('id') + ): + # "Flavor Name" is not crucial, so we swallow any + # exceptions try: flavors[f_id] = compute_client.flavors.get(f_id) except Exception: @@ -2486,6 +2537,7 @@ class ListServer(command.Lister): # processing of the image and flavor informations. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue + if 'id' in s.image: image = images.get(s.image['id']) if image: @@ -2498,29 +2550,30 @@ class ListServer(command.Lister): # able to grep for boot-from-volume servers when using the CLI. s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV - if 'id' in s.flavor: + + if compute_client.api_version < api_versions.APIVersion('2.47'): flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: - # TODO(mriedem): Fix this for microversion >= 2.47 where the - # flavor is embedded in the server response without the id. - # We likely need to drop the Flavor ID column in that case if - # --long is specified. - s.flavor_name = '' - s.flavor_id = '' + s.flavor_name = s.flavor['original_name'] table = ( column_headers, ( utils.get_item_properties( s, columns, - mixed_case_fields=mixed_case_fields, + mixed_case_fields=( + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + ), formatters={ 'OS-EXT-STS:power_state': PowerStateColumn, - 'Networks': format_columns.DictListColumn, - 'Metadata': format_columns.DictColumn, + 'networks': format_columns.DictListColumn, + 'metadata': format_columns.DictColumn, }, ) for s in data ), @@ -2638,7 +2691,7 @@ revert to release the new server and restart the old one.""") disk_group.add_argument( '--disk-overcommit', action='store_true', - default=False, + default=None, help=_( 'Allow disk over-commit on the destination host' '(supported with --os-compute-api-version 2.24 or below)' @@ -2648,7 +2701,6 @@ revert to release the new server and restart the old one.""") '--no-disk-overcommit', dest='disk_overcommit', action='store_false', - default=False, help=_( 'Do not over-commit disk on the destination host (default)' '(supported with --os-compute-api-version 2.24 or below)' @@ -2710,6 +2762,11 @@ revert to release the new server and restart the old one.""") if compute_client.api_version < api_versions.APIVersion('2.25'): kwargs['disk_over_commit'] = parsed_args.disk_overcommit + # We can't use an argparse default value because then we can't + # distinguish between explicit 'False' and unset for the below + # case (microversion >= 2.25) + if kwargs['disk_over_commit'] is None: + kwargs['disk_over_commit'] = False elif parsed_args.disk_overcommit is not None: # TODO(stephenfin): Raise an error here in OSC 7.0 msg = _( @@ -3134,12 +3191,13 @@ class PauseServer(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, - server - ).pause() + server_id = compute_client.find_server( + server, + ignore_missing=False, + ).id + compute_client.pause_server(server_id) class RebootServer(command.Command): @@ -3694,10 +3752,10 @@ class RemovePort(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -3706,7 +3764,11 @@ class RemovePort(command.Command): else: port_id = parsed_args.port - server.interface_detach(port_id) + compute_client.delete_server_interface( + port_id, + server=server, + ignore_missing=False, + ) class RemoveNetwork(command.Command): @@ -3727,10 +3789,10 @@ class RemoveNetwork(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute - server = utils.find_resource( - compute_client.servers, parsed_args.server) + server = compute_client.find_server( + parsed_args.server, ignore_missing=False) if self.app.client_manager.is_network_endpoint_enabled(): network_client = self.app.client_manager.network @@ -3739,9 +3801,12 @@ class RemoveNetwork(command.Command): else: net_id = parsed_args.network - for inf in server.interface_list(): + for inf in compute_client.server_interfaces(server): if inf.net_id == net_id: - server.interface_detach(inf.port_id) + compute_client.delete_server_interface( + inf.port_id, + server=server, + ) class RemoveServerSecurityGroup(command.Command): @@ -3797,21 +3862,22 @@ class RemoveServerVolume(command.Command): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.sdk_connection.compute + volume_client = self.app.client_manager.sdk_connection.volume - server = utils.find_resource( - compute_client.servers, + server = compute_client.find_server( parsed_args.server, + ignore_missing=False, ) - volume = utils.find_resource( - volume_client.volumes, + volume = volume_client.find_volume( parsed_args.volume, + ignore_missing=False, ) - compute_client.volumes.delete_server_volume( - server.id, - volume.id, + compute_client.delete_volume_attachment( + volume, + server, + ignore_missing=False, ) @@ -4077,13 +4143,13 @@ class ResumeServer(command.Command): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).resume() + ignore_missing=False, + ).id + compute_client.resume_server(server_id) class SetServer(command.Command): @@ -4436,50 +4502,29 @@ class SshServer(command.Command): metavar='<server>', help=_('Server (name or ID)'), ) + # Deprecated during the Yoga cycle parser.add_argument( - '--login', - metavar='<login-name>', - help=_('Login name (ssh -l option)'), - ) - parser.add_argument( - '-l', - dest='login', + '--login', '-l', metavar='<login-name>', help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( - '--port', + '--port', '-p', metavar='<port>', type=int, - help=_('Destination port (ssh -p option)'), - ) - parser.add_argument( - '-p', - metavar='<port>', - dest='port', - type=int, help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( - '--identity', + '--identity', '-i', metavar='<keyfile>', - help=_('Private key file (ssh -i option)'), - ) - parser.add_argument( - '-i', - metavar='<filename>', - dest='identity', help=argparse.SUPPRESS, ) + # Deprecated during the Yoga cycle parser.add_argument( - '--option', + '--option', '-o', metavar='<config-options>', - help=_('Options in ssh_config(5) format (ssh -o option)'), - ) - parser.add_argument( - '-o', - metavar='<option>', - dest='option', help=argparse.SUPPRESS, ) ip_group = parser.add_mutually_exclusive_group() @@ -4521,6 +4566,7 @@ class SshServer(command.Command): default='public', help=_('Use other IP address (public, private, etc)'), ) + # Deprecated during the Yoga cycle parser.add_argument( '-v', dest='verbose', @@ -4528,46 +4574,77 @@ class SshServer(command.Command): default=False, help=argparse.SUPPRESS, ) + parser.add_argument( + 'ssh_args', + nargs='*', + metavar='-- <standard ssh args>', + help=( + 'Any argument or option that ssh allows. ' + 'Use -- once between openstackclient args and SSH args.' + ), + ) 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, ) - # Build the command - cmd = "ssh" + # first, handle the deprecated options + if any(( + parsed_args.port, + parsed_args.identity, + parsed_args.option, + parsed_args.login, + parsed_args.verbose, + )): + msg = _( + 'The ssh options have been deprecated. The ssh equivalent ' + 'options can be used instead as arguments after "--" on ' + 'the command line.' + ) + self.log.warning(msg) ip_address_family = [4, 6] if parsed_args.ipv4: ip_address_family = [4] - cmd += " -4" if parsed_args.ipv6: ip_address_family = [6] - cmd += " -6" + + args = parsed_args.ssh_args[:] if parsed_args.port: - cmd += " -p %d" % parsed_args.port + args.extend(['-p', str(parsed_args.port)]) + if parsed_args.identity: - cmd += " -i %s" % parsed_args.identity + args.extend(['-i', parsed_args.identity]) + if parsed_args.option: - cmd += " -o %s" % parsed_args.option + args.extend(['-o', parsed_args.option]) + if parsed_args.login: login = parsed_args.login - else: + args.extend(['-l', login]) + elif '-l' not in args: login = self.app.client_manager.auth_ref.username + args.extend(['-l', login]) + if parsed_args.verbose: - cmd += " -v" + args.append('-v') + + ip_address = _get_ip_address( + server.addresses, + parsed_args.address_type, + ip_address_family, + ) - cmd += " %s@%s" - ip_address = _get_ip_address(server.addresses, - parsed_args.address_type, - ip_address_family) - LOG.debug("ssh command: %s", (cmd % (login, ip_address))) - os.system(cmd % (login, ip_address)) + cmd = ' '.join(['ssh', ip_address] + args) + LOG.debug("ssh command: {cmd}".format(cmd=cmd)) + os.system(cmd) class StartServer(command.Command): @@ -4648,13 +4725,13 @@ class SuspendServer(command.Command): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).suspend() + ignore_missing=False, + ).id + compute_client.suspend_server(server_id) class UnlockServer(command.Command): @@ -4671,7 +4748,6 @@ class UnlockServer(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( @@ -4694,13 +4770,13 @@ class UnpauseServer(command.Command): return parser def take_action(self, parsed_args): - - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute for server in parsed_args.server: - utils.find_resource( - compute_client.servers, + server_id = compute_client.find_server( server, - ).unpause() + ignore_missing=False, + ).id + compute_client.unpause_server(server_id) class UnrescueServer(command.Command): diff --git a/openstackclient/compute/v2/server_image.py b/openstackclient/compute/v2/server_image.py index 6c0e3b22..2021fae7 100644 --- a/openstackclient/compute/v2/server_image.py +++ b/openstackclient/compute/v2/server_image.py @@ -73,25 +73,23 @@ class CreateServerImage(command.ShowOne): self.app.stdout.write('\rProgress: %s' % progress) self.app.stdout.flush() - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute + image_client = self.app.client_manager.image - server = utils.find_resource( - compute_client.servers, - parsed_args.server, + server = compute_client.find_server( + parsed_args.server, ignore_missing=False, ) + if parsed_args.name: image_name = parsed_args.name else: image_name = server.name - image_id = compute_client.servers.create_image( + image_id = compute_client.create_server_image( server.id, image_name, parsed_args.properties, - ) - - image_client = self.app.client_manager.image - image = image_client.find_image(image_id) + ).id if parsed_args.wait: if utils.wait_for_status( @@ -105,6 +103,8 @@ class CreateServerImage(command.ShowOne): _('Error creating server image: %s'), parsed_args.server) raise exceptions.CommandError + image = image_client.find_image(image_id, ignore_missing=False) + if self.app.client_manager._api_version['image'] == '1': info = {} info.update(image._info) diff --git a/openstackclient/compute/v2/service.py b/openstackclient/compute/v2/service.py index 6427e548..8605156c 100644 --- a/openstackclient/compute/v2/service.py +++ b/openstackclient/compute/v2/service.py @@ -17,7 +17,7 @@ import logging -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 @@ -40,16 +40,24 @@ class DeleteService(command.Command): help=_("Compute service(s) to delete (ID only). If using " "``--os-compute-api-version`` 2.53 or greater, the ID is " "a UUID which can be retrieved by listing compute services " - "using the same 2.53+ microversion.") + "using the same 2.53+ microversion. " + "If deleting a compute service, be sure to stop the actual " + "compute process on the physical host before deleting the " + "service with this command. Failing to do so can lead to " + "the running service re-creating orphaned compute_nodes " + "table records in the database.") ) return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute result = 0 for s in parsed_args.service: try: - compute_client.services.delete(s) + compute_client.delete_service( + s, + ignore_missing=False + ) except Exception as e: result += 1 LOG.error(_("Failed to delete compute service with " @@ -91,38 +99,40 @@ class ListService(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 + columns = ( + "id", + "binary", + "host", + "availability_zone", + "status", + "state", + "updated_at", + ) + column_headers = ( + "ID", + "Binary", + "Host", + "Zone", + "Status", + "State", + "Updated At", + ) if parsed_args.long: - columns = ( - "ID", - "Binary", - "Host", - "Zone", - "Status", - "State", - "Updated At", - "Disabled Reason" - ) - has_forced_down = ( - compute_client.api_version >= api_versions.APIVersion('2.11')) - if has_forced_down: - columns += ("Forced Down",) - else: - columns = ( - "ID", - "Binary", - "Host", - "Zone", - "Status", - "State", - "Updated At" - ) - data = compute_client.services.list(parsed_args.host, - parsed_args.service) - return (columns, - (utils.get_item_properties( - s, columns, - ) for s in data)) + columns += ("disabled_reason",) + column_headers += ("Disabled Reason",) + if sdk_utils.supports_microversion(compute_client, '2.11'): + columns += ("is_forced_down",) + column_headers += ("Forced Down",) + + data = compute_client.services( + host=parsed_args.host, + binary=parsed_args.service + ) + return ( + column_headers, + (utils.get_item_properties(s, columns) for s in data) + ) class SetService(command.Command): @@ -175,7 +185,7 @@ class SetService(command.Command): return parser @staticmethod - def _find_service_by_host_and_binary(cs, host, binary): + def _find_service_by_host_and_binary(compute_client, host, binary): """Utility method to find a compute service by host and binary :param host: the name of the compute service host @@ -183,7 +193,7 @@ class SetService(command.Command): :returns: novaclient.v2.services.Service dict-like object :raises: CommandError if no or multiple results were found """ - services = cs.list(host=host, binary=binary) + services = list(compute_client.services(host=host, binary=binary)) # Did we find anything? if not len(services): msg = _('Compute service for host "%(host)s" and binary ' @@ -202,8 +212,7 @@ class SetService(command.Command): return services[0] def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute - cs = compute_client.services + compute_client = self.app.client_manager.sdk_connection.compute if (parsed_args.enable or not parsed_args.disable) and \ parsed_args.disable_reason: @@ -216,14 +225,17 @@ class SetService(command.Command): # services. If 2.53+ is used we need to find the nova-compute # service using the --host and --service (binary) values. requires_service_id = ( - compute_client.api_version >= api_versions.APIVersion('2.53')) + sdk_utils.supports_microversion(compute_client, '2.53')) service_id = None if requires_service_id: # TODO(mriedem): Add an --id option so users can pass the service # id (as a uuid) directly rather than make us look it up using # host/binary. service_id = SetService._find_service_by_host_and_binary( - cs, parsed_args.host, parsed_args.service).id + compute_client, + parsed_args.host, + parsed_args.service + ).id result = 0 enabled = None @@ -235,21 +247,18 @@ class SetService(command.Command): if enabled is not None: if enabled: - args = (service_id,) if requires_service_id else ( - parsed_args.host, parsed_args.service) - cs.enable(*args) + compute_client.enable_service( + service_id, + parsed_args.host, + parsed_args.service + ) else: - if parsed_args.disable_reason: - args = (service_id, parsed_args.disable_reason) if \ - requires_service_id else ( - parsed_args.host, - parsed_args.service, - parsed_args.disable_reason) - cs.disable_log_reason(*args) - else: - args = (service_id,) if requires_service_id else ( - parsed_args.host, parsed_args.service) - cs.disable(*args) + compute_client.disable_service( + service_id, + parsed_args.host, + parsed_args.service, + parsed_args.disable_reason + ) except Exception: status = "enabled" if enabled else "disabled" LOG.error("Failed to set service status to %s", status) @@ -261,15 +270,17 @@ class SetService(command.Command): if parsed_args.up: force_down = False if force_down is not None: - if compute_client.api_version < api_versions.APIVersion( - '2.11'): + if not sdk_utils.supports_microversion(compute_client, '2.11'): msg = _('--os-compute-api-version 2.11 or later is ' 'required') raise exceptions.CommandError(msg) try: - args = (service_id, force_down) if requires_service_id else ( - parsed_args.host, parsed_args.service, force_down) - cs.force_down(*args) + compute_client.update_service_forced_down( + service_id, + parsed_args.host, + parsed_args.service, + force_down + ) except Exception: state = "down" if force_down else "up" LOG.error("Failed to set service state to %s", state) diff --git a/openstackclient/network/v2/address_group.py b/openstackclient/network/v2/address_group.py index 9017047f..b08fa872 100644 --- a/openstackclient/network/v2/address_group.py +++ b/openstackclient/network/v2/address_group.py @@ -28,10 +28,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _format_addresses(addresses): @@ -51,7 +54,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/address_scope.py b/openstackclient/network/v2/address_scope.py index 5748793a..488e1600 100644 --- a/openstackclient/network/v2/address_scope.py +++ b/openstackclient/network/v2/address_scope.py @@ -29,9 +29,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'is_shared': 'shared', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -49,7 +53,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs @@ -215,7 +219,6 @@ class ListAddressScope(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id attrs['project_id'] = project_id data = client.address_scopes(**attrs) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 0951565c..4c03074d 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -27,17 +27,17 @@ _formatters = { def _get_network_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_columns(item): columns = list(item.keys()) - if 'tenant_id' in columns: - columns.remove('tenant_id') - columns.append('project_id') return tuple(sorted(columns)) @@ -81,7 +81,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id if parsed_args.dns_domain: attrs['dns_domain'] = parsed_args.dns_domain @@ -349,7 +349,6 @@ class ListFloatingIP(common.NetworkAndComputeLister): parsed_args.project, parsed_args.project_domain, ) - query['tenant_id'] = project.id query['project_id'] = project.id if parsed_args.router is not None: router = network_client.find_router(parsed_args.router, diff --git a/openstackclient/network/v2/floating_ip_port_forwarding.py b/openstackclient/network/v2/floating_ip_port_forwarding.py index f137174c..b33633d3 100644 --- a/openstackclient/network/v2/floating_ip_port_forwarding.py +++ b/openstackclient/network/v2/floating_ip_port_forwarding.py @@ -26,10 +26,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) class CreateFloatingIPPortForwarding(command.ShowOne, diff --git a/openstackclient/network/v2/ip_availability.py b/openstackclient/network/v2/ip_availability.py index 6a3c67e2..9e56c5bd 100644 --- a/openstackclient/network/v2/ip_availability.py +++ b/openstackclient/network/v2/ip_availability.py @@ -26,10 +26,13 @@ _formatters = { def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) # TODO(ankur-gupta-f): Use the SDK resource mapped attribute names once @@ -84,7 +87,6 @@ class ListIPAvailability(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - filters['tenant_id'] = project_id filters['project_id'] = project_id data = client.network_ip_availabilities(**filters) return (column_headers, diff --git a/openstackclient/network/v2/l3_conntrack_helper.py b/openstackclient/network/v2/l3_conntrack_helper.py index 9fc33d8f..1de5b785 100644 --- a/openstackclient/network/v2/l3_conntrack_helper.py +++ b/openstackclient/network/v2/l3_conntrack_helper.py @@ -26,7 +26,12 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = {} - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client, parsed_args): diff --git a/openstackclient/network/v2/local_ip.py b/openstackclient/network/v2/local_ip.py new file mode 100644 index 00000000..109f64cf --- /dev/null +++ b/openstackclient/network/v2/local_ip.py @@ -0,0 +1,310 @@ +# Copyright 2021 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Node Local IP action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + +def _get_attrs(client_manager, parsed_args): + attrs = {} + network_client = client_manager.network + + if parsed_args.name: + attrs['name'] = parsed_args.name + if parsed_args.description: + attrs['description'] = parsed_args.description + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['project_id'] = project_id + if parsed_args.network: + network = network_client.find_network(parsed_args.network, + ignore_missing=False) + attrs['network_id'] = network.id + if parsed_args.local_ip_address: + attrs['local_ip_address'] = parsed_args.local_ip_address + if parsed_args.local_port: + port = network_client.find_port(parsed_args.local_port, + ignore_missing=False) + attrs['local_port_id'] = port.id + if parsed_args.ip_mode: + attrs['ip_mode'] = parsed_args.ip_mode + return attrs + + +class CreateLocalIP(command.ShowOne): + _description = _("Create Local IP") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + '--name', + metavar="<name>", + help=_("New local IP name") + ) + parser.add_argument( + '--description', + metavar="<description>", + help=_("New local IP description") + ) + parser.add_argument( + '--network', + metavar='<network>', + help=_("Network to allocate Local IP (name or ID)") + ) + parser.add_argument( + '--local-port', + metavar='<local_port>', + help=_("Port to allocate Local IP (name or ID)") + ) + parser.add_argument( + "--local-ip-address", + metavar="<local_ip_address>", + help=_("IP address or CIDR "), + ) + parser.add_argument( + '--ip-mode', + metavar='<ip_mode>', + help=_("local IP ip mode") + ) + + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = _get_attrs(self.app.client_manager, parsed_args) + + obj = client.create_local_ip(**attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteLocalIP(command.Command): + _description = _("Delete local IP(s)") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'local_ip', + metavar="<local-ip>", + nargs='+', + help=_("Local IP(s) to delete (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + result = 0 + + for lip in parsed_args.local_ip: + try: + obj = client.find_local_ip(lip, ignore_missing=False) + client.delete_local_ip(obj) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete Local IP with " + "name or ID '%(lip)s': %(e)s"), + {'lip': lip, 'e': e}) + + if result > 0: + total = len(parsed_args.local_ip) + msg = (_("%(result)s of %(total)s local IPs failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class SetLocalIP(command.Command): + _description = _("Set local ip properties") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'local_ip', + metavar="<local-ip>", + help=_("Local IP to modify (name or ID)") + ) + parser.add_argument( + '--name', + metavar="<name>", + help=_('Set local IP name') + ) + parser.add_argument( + '--description', + metavar="<description>", + help=_('Set local IP description') + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_local_ip( + parsed_args.local_ip, + ignore_missing=False) + attrs = {} + if parsed_args.name is not None: + attrs['name'] = parsed_args.name + if parsed_args.description is not None: + attrs['description'] = parsed_args.description + if attrs: + client.update_local_ip(obj, **attrs) + + +class ListLocalIP(command.Lister): + _description = _("List local IPs") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + '--name', + metavar='<name>', + help=_("List only local IPs of given name in output") + ) + parser.add_argument( + '--project', + metavar="<project>", + help=_("List Local IPs according to their project " + "(name or ID)") + ) + parser.add_argument( + '--network', + metavar='<network>', + help=_("List Local IP(s) according to " + "given network (name or ID)") + ) + parser.add_argument( + '--local-port', + metavar='<local_port>', + help=_("List Local IP(s) according to " + "given port (name or ID)") + ) + parser.add_argument( + '--local-ip-address', + metavar='<local_ip_address>', + help=_("List Local IP(s) according to " + "given Local IP Address") + ) + parser.add_argument( + '--ip-mode', + metavar='<ip_mode>', + help=_("List Local IP(s) according to " + "given IP mode") + ) + + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'id', + 'name', + 'description', + 'project_id', + 'local_port_id', + 'network_id', + 'local_ip_address', + 'ip_mode', + ) + column_headers = ( + 'ID', + 'Name', + 'Description', + 'Project', + 'Local Port ID', + 'Network', + 'Local IP address', + 'IP mode', + ) + attrs = {} + if parsed_args.name: + attrs['name'] = parsed_args.name + if 'project' in parsed_args and parsed_args.project is not None: + identity_client = self.app.client_manager.identity + project_id = identity_common.find_project( + identity_client, + parsed_args.project, + parsed_args.project_domain, + ).id + attrs['project_id'] = project_id + if parsed_args.network is not None: + network = client.find_network(parsed_args.network, + ignore_missing=False) + attrs['network_id'] = network.id + if parsed_args.local_port: + port = client.find_port(parsed_args.local_port, + ignore_missing=False) + attrs['local_port_id'] = port.id + if parsed_args.local_ip_address: + attrs['local_ip_address'] = parsed_args.local_ip_address + if parsed_args.ip_mode: + attrs['ip_mode'] = parsed_args.ip_mode + data = client.local_ips(**attrs) + + return (column_headers, + (utils.get_item_properties(s, + columns, + formatters={},) for s in data)) + + +class ShowLocalIP(command.ShowOne): + _description = _("Display local IP details") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'local_ip', + metavar="<local-ip>", + help=_("Local IP to display (name or ID)") + ) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + obj = client.find_local_ip( + parsed_args.local_ip, + ignore_missing=False) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) diff --git a/openstackclient/network/v2/local_ip_association.py b/openstackclient/network/v2/local_ip_association.py new file mode 100644 index 00000000..aa0747c5 --- /dev/null +++ b/openstackclient/network/v2/local_ip_association.py @@ -0,0 +1,197 @@ +# Copyright 2021 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +"""Node Local IP action implementations""" + +import logging + +from osc_lib.command import command +from osc_lib import exceptions +from osc_lib import utils + +from openstackclient.i18n import _ +from openstackclient.identity import common as identity_common + +LOG = logging.getLogger(__name__) + + +def _get_columns(item): + column_map = {} + hidden_columns = ['location', 'name', 'id'] + return utils.get_osc_show_columns_for_sdk_resource( + item, column_map, hidden_columns) + + +class CreateLocalIPAssociation(command.ShowOne): + _description = _("Create Local IP Association") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'local_ip', + metavar='<local-ip>', + help=_("Local IP that the port association belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'fixed_port', + metavar='<fixed_port>', + help=_("The ID or Name of Port to allocate Local IP Association") + ) + parser.add_argument( + '--fixed-ip', + metavar='<fixed_ip>', + help=_("Fixed IP for Local IP Association") + ) + + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + + attrs = {} + port = client.find_port(parsed_args.fixed_port, + ignore_missing=False) + attrs['fixed_port_id'] = port.id + if parsed_args.fixed_ip: + attrs['fixed_ip'] = parsed_args.fixed_ip + local_ip = client.find_local_ip( + parsed_args.local_ip, + ignore_missing=False, + ) + obj = client.create_local_ip_association(local_ip.id, **attrs) + display_columns, columns = _get_columns(obj) + data = utils.get_item_properties(obj, columns, formatters={}) + + return (display_columns, data) + + +class DeleteLocalIPAssociation(command.Command): + _description = _("Delete Local IP association(s)") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + parser.add_argument( + 'local_ip', + metavar="<local_ip>", + help=_("Local IP that the port association belongs to " + "(IP address or ID)") + ) + parser.add_argument( + 'fixed_port_id', + nargs="+", + metavar="<fixed_port_id>", + help=_("The fixed port ID of Local IP Association") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + local_ip = client.find_local_ip( + parsed_args.local_ip, + ignore_missing=False, + ) + result = 0 + + for fixed_port_id in parsed_args.fixed_port_id: + try: + client.delete_local_ip_association( + local_ip.id, + fixed_port_id, + ignore_missing=False, + ) + except Exception as e: + result += 1 + LOG.error(_("Failed to delete Local IP Association with " + "fixed port " + "name or ID '%(fixed_port_id)s': %(e)s"), + {'fixed port ID': fixed_port_id, 'e': e}) + + if result > 0: + total = len(parsed_args.fixed_port_id) + msg = (_("%(result)s of %(total)s Local IP Associations failed " + "to delete.") % {'result': result, 'total': total}) + raise exceptions.CommandError(msg) + + +class ListLocalIPAssociation(command.Lister): + _description = _("List Local IP Associations") + + def get_parser(self, prog_name): + parser = super().get_parser(prog_name) + + parser.add_argument( + 'local_ip', + metavar='<local_ip>', + help=_("Local IP that port associations belongs to") + ) + parser.add_argument( + '--fixed-port', + metavar='<fixed_port>', + help=_("Filter the list result by the ID or name of " + "the fixed port") + ) + parser.add_argument( + '--fixed-ip', + metavar='<fixed_ip>', + help=_("Filter the list result by fixed ip") + ) + parser.add_argument( + '--host', + metavar='<host>', + help=_("Filter the list result by given host") + ) + identity_common.add_project_domain_option_to_parser(parser) + + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + columns = ( + 'local_ip_id', + 'local_ip_address', + 'fixed_port_id', + 'fixed_ip', + 'host', + ) + column_headers = ( + 'Local IP ID', + 'Local IP Address', + 'Fixed port ID', + 'Fixed IP', + 'Host' + ) + attrs = {} + obj = client.find_local_ip( + parsed_args.local_ip, + ignore_missing=False, + ) + if parsed_args.fixed_port: + port = client.find_port(parsed_args.fixed_port, + ignore_missing=False) + attrs['fixed_port_id'] = port.id + if parsed_args.fixed_ip: + attrs['fixed_ip'] = parsed_args.fixed_ip + if parsed_args.host: + attrs['host'] = parsed_args.host + + data = client.local_ip_associations(obj, **attrs) + + return (column_headers, + (utils.get_item_properties(s, + columns, + formatters={}) for s in data)) diff --git a/openstackclient/network/v2/network.py b/openstackclient/network/v2/network.py index 191e4aa8..a239e0fe 100644 --- a/openstackclient/network/v2/network.py +++ b/openstackclient/network/v2/network.py @@ -58,16 +58,18 @@ def _get_columns_network(item): 'is_shared': 'shared', 'ipv4_address_scope_id': 'ipv4_address_scope', 'ipv6_address_scope_id': 'ipv6_address_scope', - 'tenant_id': 'project_id', 'tags': 'tags', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_columns_compute(item): - column_map = { - 'tenant_id': 'project_id', - } + column_map = {} return utils.get_osc_show_columns_for_sdk_resource(item, column_map) @@ -96,8 +98,6 @@ def _get_attrs_network(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor - attrs['tenant_id'] = project_id attrs['project_id'] = project_id # "network set" command doesn't support setting availability zone hints. @@ -568,7 +568,6 @@ class ListNetwork(common.NetworkAndComputeLister): parsed_args.project, parsed_args.project_domain, ) - args['tenant_id'] = project.id args['project_id'] = project.id if parsed_args.share: diff --git a/openstackclient/network/v2/network_agent.py b/openstackclient/network/v2/network_agent.py index c995e36c..3024d026 100644 --- a/openstackclient/network/v2/network_agent.py +++ b/openstackclient/network/v2/network_agent.py @@ -50,7 +50,12 @@ def _get_network_columns(item): 'is_admin_state_up': 'admin_state_up', 'is_alive': 'alive', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) class AddNetworkToAgent(command.Command): diff --git a/openstackclient/network/v2/network_auto_allocated_topology.py b/openstackclient/network/v2/network_auto_allocated_topology.py index 7b7df4d7..496606ba 100644 --- a/openstackclient/network/v2/network_auto_allocated_topology.py +++ b/openstackclient/network/v2/network_auto_allocated_topology.py @@ -25,10 +25,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _format_check_resource_columns(): @@ -51,7 +54,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id if parsed_args.check_resources: attrs['check_resources'] = True diff --git a/openstackclient/network/v2/network_flavor.py b/openstackclient/network/v2/network_flavor.py index 6e3a5a04..862155ce 100644 --- a/openstackclient/network/v2/network_flavor.py +++ b/openstackclient/network/v2/network_flavor.py @@ -29,10 +29,14 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'is_enabled': 'enabled', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -52,7 +56,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/network_flavor_profile.py b/openstackclient/network/v2/network_flavor_profile.py index df7cfb74..719f955c 100644 --- a/openstackclient/network/v2/network_flavor_profile.py +++ b/openstackclient/network/v2/network_flavor_profile.py @@ -27,10 +27,14 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'is_enabled': 'enabled', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -52,7 +56,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/network_meter.py b/openstackclient/network/v2/network_meter.py index 8b63de2c..b7b77fb1 100644 --- a/openstackclient/network/v2/network_meter.py +++ b/openstackclient/network/v2/network_meter.py @@ -29,9 +29,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'is_shared': 'shared', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -46,7 +50,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id if parsed_args.share: attrs['shared'] = True if parsed_args.no_share: diff --git a/openstackclient/network/v2/network_meter_rule.py b/openstackclient/network/v2/network_meter_rule.py index 4117d043..0f427275 100644 --- a/openstackclient/network/v2/network_meter_rule.py +++ b/openstackclient/network/v2/network_meter_rule.py @@ -27,10 +27,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -59,7 +62,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/network_qos_policy.py b/openstackclient/network/v2/network_qos_policy.py index 8d431248..bc257901 100644 --- a/openstackclient/network/v2/network_qos_policy.py +++ b/openstackclient/network/v2/network_qos_policy.py @@ -29,9 +29,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'is_shared': 'shared', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -59,7 +63,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/network_qos_rule.py b/openstackclient/network/v2/network_qos_rule.py index 4bf72d26..a4129b83 100644 --- a/openstackclient/network/v2/network_qos_rule.py +++ b/openstackclient/network/v2/network_qos_rule.py @@ -46,10 +46,13 @@ ACTION_SHOW = 'get' def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _check_type_parameters(attrs, type, is_create): diff --git a/openstackclient/network/v2/network_qos_rule_type.py b/openstackclient/network/v2/network_qos_rule_type.py index 036b682f..1bcfda82 100644 --- a/openstackclient/network/v2/network_qos_rule_type.py +++ b/openstackclient/network/v2/network_qos_rule_type.py @@ -24,7 +24,7 @@ def _get_columns(item): "type": "rule_type_name", "drivers": "drivers", } - invisible_columns = ["id", "name"] + invisible_columns = ["id", "location", "name"] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, invisible_columns) diff --git a/openstackclient/network/v2/network_rbac.py b/openstackclient/network/v2/network_rbac.py index edca872c..00667395 100644 --- a/openstackclient/network/v2/network_rbac.py +++ b/openstackclient/network/v2/network_rbac.py @@ -29,9 +29,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): column_map = { 'target_tenant': 'target_project_id', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_attrs(client_manager, parsed_args): @@ -82,7 +86,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id return attrs diff --git a/openstackclient/network/v2/network_segment.py b/openstackclient/network/v2/network_segment.py index e18ac475..0f64bd86 100644 --- a/openstackclient/network/v2/network_segment.py +++ b/openstackclient/network/v2/network_segment.py @@ -26,7 +26,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - return utils.get_osc_show_columns_for_sdk_resource(item, {}) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) class CreateNetworkSegment(command.ShowOne, diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index e105111d..a95adb0a 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -32,7 +32,13 @@ LOG = logging.getLogger(__name__) def _get_columns(item): - return utils.get_osc_show_columns_for_sdk_resource(item, {}) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _get_ranges(item): diff --git a/openstackclient/network/v2/port.py b/openstackclient/network/v2/port.py index 132c384a..b55e729f 100644 --- a/openstackclient/network/v2/port.py +++ b/openstackclient/network/v2/port.py @@ -63,9 +63,13 @@ def _get_columns(item): 'binding:vnic_type': 'binding_vnic_type', 'is_admin_state_up': 'admin_state_up', 'is_port_security_enabled': 'port_security_enabled', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) class JSONKeyValueAction(argparse.Action): @@ -134,7 +138,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id if parsed_args.disable_port_security: attrs['port_security_enabled'] = False @@ -610,6 +614,13 @@ class ListPort(command.Lister): metavar='<name>', help=_("List ports according to their name") ) + parser.add_argument( + '--security-group', + action='append', + dest='security_groups', + metavar='<security-group>', + help=_("List only ports associated with this security group") + ) identity_common.add_project_domain_option_to_parser(parser) parser.add_argument( '--fixed-ip', @@ -675,13 +686,14 @@ class ListPort(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - filters['tenant_id'] = project_id filters['project_id'] = project_id if parsed_args.name: filters['name'] = parsed_args.name if parsed_args.fixed_ip: filters['fixed_ips'] = _prepare_filter_fixed_ips( self.app.client_manager, parsed_args) + if parsed_args.security_groups: + filters['security_groups'] = parsed_args.security_groups _tag.get_tag_filtering_args(parsed_args, filters) @@ -969,6 +981,12 @@ class UnsetPort(common.NeutronUnsetCommandWithExtraArgs): action='store_true', help=_("Clear existing NUMA affinity policy") ) + parser.add_argument( + '--host', + action='store_true', + default=False, + help=_("Clear host binding for the port.") + ) _tag.add_tag_option_to_parser_for_unset(parser, _('port')) @@ -1026,6 +1044,8 @@ class UnsetPort(common.NeutronUnsetCommandWithExtraArgs): attrs['data_plane_status'] = None if parsed_args.numa_policy: attrs['numa_affinity_policy'] = None + if parsed_args.host: + attrs['binding:host_id'] = None attrs.update( self._parse_extra_properties(parsed_args.extra_properties)) diff --git a/openstackclient/network/v2/router.py b/openstackclient/network/v2/router.py index dde4eda9..f1fce298 100644 --- a/openstackclient/network/v2/router.py +++ b/openstackclient/network/v2/router.py @@ -67,14 +67,13 @@ _formatters = { def _get_columns(item): column_map = { - 'tenant_id': 'project_id', 'is_ha': 'ha', 'is_distributed': 'distributed', 'is_admin_state_up': 'admin_state_up', } if hasattr(item, 'interfaces_info'): column_map['interfaces_info'] = 'interfaces_info' - invisible_columns = [] + invisible_columns = ['location'] if item.is_ha is None: invisible_columns.append('is_ha') column_map.pop('is_ha') @@ -110,7 +109,31 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id + if parsed_args.external_gateway: + gateway_info = {} + n_client = client_manager.network + network = n_client.find_network( + parsed_args.external_gateway, ignore_missing=False) + gateway_info['network_id'] = network.id + if parsed_args.disable_snat: + gateway_info['enable_snat'] = False + if parsed_args.enable_snat: + gateway_info['enable_snat'] = True + if parsed_args.fixed_ip: + ips = [] + for ip_spec in parsed_args.fixed_ip: + if ip_spec.get('subnet', False): + subnet_name_id = ip_spec.pop('subnet') + if subnet_name_id: + subnet = n_client.find_subnet(subnet_name_id, + ignore_missing=False) + ip_spec['subnet_id'] = subnet.id + if ip_spec.get('ip-address', False): + ip_spec['ip_address'] = ip_spec.pop('ip-address') + ips.append(ip_spec) + gateway_info['external_fixed_ips'] = ips + attrs['external_gateway_info'] = gateway_info return attrs @@ -320,6 +343,32 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs): "repeat option to set multiple availability zones)") ) _tag.add_tag_option_to_parser_for_create(parser, _('router')) + parser.add_argument( + '--external-gateway', + metavar="<network>", + help=_("External Network used as router's gateway (name or ID)") + ) + parser.add_argument( + '--fixed-ip', + metavar='subnet=<subnet>,ip-address=<ip-address>', + action=parseractions.MultiKeyValueAction, + optional_keys=['subnet', 'ip-address'], + help=_("Desired IP and/or subnet (name or ID) " + "on external gateway: " + "subnet=<subnet>,ip-address=<ip-address> " + "(repeat option to set multiple fixed IP addresses)") + ) + snat_group = parser.add_mutually_exclusive_group() + snat_group.add_argument( + '--enable-snat', + action='store_true', + help=_("Enable Source NAT on external gateway") + ) + snat_group.add_argument( + '--disable-snat', + action='store_true', + help=_("Disable Source NAT on external gateway") + ) return parser @@ -338,6 +387,12 @@ class CreateRouter(command.ShowOne, common.NeutronCommandWithExtraArgs): # tags cannot be set when created, so tags need to be set later. _tag.update_tags_for_set(client, obj, parsed_args) + if (parsed_args.disable_snat or parsed_args.enable_snat or + parsed_args.fixed_ip) and not parsed_args.external_gateway: + msg = (_("You must specify '--external-gateway' in order " + "to specify SNAT or fixed-ip values")) + raise exceptions.CommandError(msg) + display_columns, columns = _get_columns(obj) data = utils.get_item_properties(obj, columns, formatters=_formatters) @@ -459,7 +514,6 @@ class ListRouter(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - args['tenant_id'] = project_id args['project_id'] = project_id _tag.get_tag_filtering_args(parsed_args, args) @@ -725,29 +779,6 @@ class SetRouter(common.NeutronCommandWithExtraArgs): msg = (_("You must specify '--external-gateway' in order " "to update the SNAT or fixed-ip values")) raise exceptions.CommandError(msg) - if parsed_args.external_gateway: - gateway_info = {} - network = client.find_network( - parsed_args.external_gateway, ignore_missing=False) - gateway_info['network_id'] = network.id - if parsed_args.disable_snat: - gateway_info['enable_snat'] = False - if parsed_args.enable_snat: - gateway_info['enable_snat'] = True - if parsed_args.fixed_ip: - ips = [] - for ip_spec in parsed_args.fixed_ip: - if ip_spec.get('subnet', False): - subnet_name_id = ip_spec.pop('subnet') - if subnet_name_id: - subnet = client.find_subnet(subnet_name_id, - ignore_missing=False) - ip_spec['subnet_id'] = subnet.id - if ip_spec.get('ip-address', False): - ip_spec['ip_address'] = ip_spec.pop('ip-address') - ips.append(ip_spec) - gateway_info['external_fixed_ips'] = ips - attrs['external_gateway_info'] = gateway_info if ((parsed_args.qos_policy or parsed_args.no_qos_policy) and not parsed_args.external_gateway): diff --git a/openstackclient/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 37d2dc5b..d8c38f45 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -35,7 +35,6 @@ def _format_network_security_group_rules(sg_rules): for key in empty_keys: sg_rule.pop(key) sg_rule.pop('security_group_id', None) - sg_rule.pop('tenant_id', None) sg_rule.pop('project_id', None) return utils.format_list_of_dicts(sg_rules) @@ -85,11 +84,17 @@ _formatters_compute = { def _get_columns(item): + # We still support Nova managed security groups, where we have tenant_id. column_map = { 'security_group_rules': 'rules', 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) # TODO(abhiraut): Use the SDK resource mapped attribute names once the @@ -159,7 +164,7 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne, parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id attrs.update( self._parse_extra_properties(parsed_args.extra_properties)) @@ -264,7 +269,6 @@ class ListSecurityGroup(common.NetworkAndComputeLister): parsed_args.project, parsed_args.project_domain, ).id - filters['tenant_id'] = project_id filters['project_id'] = project_id _tag.get_tag_filtering_args(parsed_args, filters) diff --git a/openstackclient/network/v2/security_group_rule.py b/openstackclient/network/v2/security_group_rule.py index 252dcb05..a1122616 100644 --- a/openstackclient/network/v2/security_group_rule.py +++ b/openstackclient/network/v2/security_group_rule.py @@ -71,10 +71,13 @@ def _format_remote_ip_prefix(rule): def _get_columns(item): - column_map = { - 'tenant_id': 'project_id', - } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + column_map = {} + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) def _convert_to_lowercase(string): @@ -352,7 +355,7 @@ class CreateSecurityGroupRule(common.NetworkAndComputeShowOne, parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id attrs.update( self._parse_extra_properties(parsed_args.extra_properties)) diff --git a/openstackclient/network/v2/subnet.py b/openstackclient/network/v2/subnet.py index c07fab41..bf6a46d4 100644 --- a/openstackclient/network/v2/subnet.py +++ b/openstackclient/network/v2/subnet.py @@ -137,14 +137,17 @@ def _get_columns(item): column_map = { 'is_dhcp_enabled': 'enable_dhcp', 'subnet_pool_id': 'subnetpool_id', - 'tenant_id': 'project_id', } # Do not show this column when displaying a subnet - invisible_columns = ['use_default_subnet_pool', 'prefix_length'] + invisible_columns = [ + 'location', + 'use_default_subnet_pool', + 'prefix_length' + ] return utils.get_osc_show_columns_for_sdk_resource( item, column_map, - invisible_columns=invisible_columns + invisible_columns ) @@ -184,7 +187,7 @@ def _get_attrs(client_manager, parsed_args, is_create=True): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id attrs['network_id'] = client.find_network(parsed_args.network, ignore_missing=False).id if parsed_args.subnet_pool is not None: @@ -489,6 +492,12 @@ class ListSubnet(command.Lister): "(in CIDR notation) in output " "e.g.: --subnet-range 10.10.0.0/16") ) + parser.add_argument( + '--subnet-pool', + metavar='<subnet-pool>', + help=_("List only subnets which belong to a given subnet pool " + "in output (Name or ID)") + ) _tag.add_tag_filtering_option_to_parser(parser, _('subnets')) return parser @@ -512,7 +521,6 @@ class ListSubnet(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - filters['tenant_id'] = project_id filters['project_id'] = project_id if parsed_args.network: network_id = network_client.find_network(parsed_args.network, @@ -524,6 +532,10 @@ class ListSubnet(command.Lister): filters['name'] = parsed_args.name if parsed_args.subnet_range: filters['cidr'] = parsed_args.subnet_range + if parsed_args.subnet_pool: + subnetpool_id = network_client.find_subnet_pool( + parsed_args.subnet_pool, ignore_missing=False).id + filters['subnetpool_id'] = subnetpool_id _tag.get_tag_filtering_args(parsed_args, filters) data = network_client.subnets(**filters) diff --git a/openstackclient/network/v2/subnet_pool.py b/openstackclient/network/v2/subnet_pool.py index 6b88888c..b4142f37 100644 --- a/openstackclient/network/v2/subnet_pool.py +++ b/openstackclient/network/v2/subnet_pool.py @@ -36,9 +36,13 @@ def _get_columns(item): 'is_shared': 'shared', 'maximum_prefix_length': 'max_prefixlen', 'minimum_prefix_length': 'min_prefixlen', - 'tenant_id': 'project_id', } - return utils.get_osc_show_columns_for_sdk_resource(item, column_map) + hidden_columns = ['location'] + return utils.get_osc_show_columns_for_sdk_resource( + item, + column_map, + hidden_columns + ) _formatters = { @@ -86,7 +90,7 @@ def _get_attrs(client_manager, parsed_args): parsed_args.project, parsed_args.project_domain, ).id - attrs['tenant_id'] = project_id + attrs['project_id'] = project_id if parsed_args.description is not None: attrs['description'] = parsed_args.description @@ -324,7 +328,6 @@ class ListSubnetPool(command.Lister): parsed_args.project, parsed_args.project_domain, ).id - filters['tenant_id'] = project_id filters['project_id'] = project_id if parsed_args.name is not None: filters['name'] = parsed_args.name diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 9c057460..bf67101a 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -11,6 +11,9 @@ # under the License. import json +import uuid + +from tempest.lib import exceptions from openstackclient.tests.functional import base @@ -165,3 +168,25 @@ class QuotaTests(base.TestCase): # returned attributes self.assertTrue(cmd_output["key-pairs"] >= 0) self.assertTrue(cmd_output["snapshots"] >= 0) + + def test_quota_network_set_with_check_limit(self): + if not self.haz_network: + self.skipTest('No Network service present') + if not self.is_extension_enabled('quota-check-limit'): + self.skipTest('No "quota-check-limit" extension present') + + self.openstack('quota set --networks 40 ' + self.PROJECT_NAME) + cmd_output = json.loads(self.openstack( + 'quota list -f json --network' + )) + self.assertIsNotNone(cmd_output) + self.assertEqual(40, cmd_output[0]['Networks']) + + # That will ensure we have at least two networks in the system. + for _ in range(2): + self.openstack('network create --project %s %s' % + (self.PROJECT_NAME, uuid.uuid4().hex)) + + self.assertRaises(exceptions.CommandFailed, self.openstack, + 'quota set --networks 1 --check-limit ' + + self.PROJECT_NAME) diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 59b1fad5..cf4bcbc2 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1072,7 +1072,7 @@ class ServerTests(common.ComputeTestCase): self.assertNotIn('nics are required after microversion 2.36', e.stderr) - def test_server_add_remove_network_port(self): + def test_server_add_remove_network(self): name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'server create -f json ' + @@ -1085,18 +1085,63 @@ class ServerTests(common.ComputeTestCase): self.assertIsNotNone(cmd_output['id']) self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) + # add network and check 'public' is in server show self.openstack( 'server add network ' + name + ' public') + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if 'public' not in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying add network check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses'] + self.assertIn('public', addresses) + + # remove network and check 'public' is not in server show + self.openstack('server remove network ' + name + ' public') + + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if 'public' in cmd_output['addresses']: + # Hang out for a bit and try again + print('retrying remove network check') + wait_time += 10 + time.sleep(10) + else: + break + + addresses = cmd_output['addresses'] + self.assertNotIn('public', addresses) + + def test_server_add_remove_port(self): + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'server show -f json ' + name + 'server create -f json ' + + '--network private ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--wait ' + + name )) - addresses = cmd_output['addresses'] - self.assertIn('public', addresses) + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) - port_name = 'test-port' + # create port, record one of its ip address + port_name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( 'port list -f json' @@ -1108,14 +1153,100 @@ class ServerTests(common.ComputeTestCase): '--network private ' + port_name )) self.assertIsNotNone(cmd_output['id']) + ip_address = cmd_output['fixed_ips'][0]['ip_address'] + self.addCleanup(self.openstack, 'port delete ' + port_name) + # add port to server, assert the ip address of the port appears self.openstack('server add port ' + name + ' ' + port_name) + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if ip_address not in cmd_output['addresses']['private']: + # Hang out for a bit and try again + print('retrying add port check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses']['private'] + self.assertIn(ip_address, addresses) + + # remove port, assert the ip address of the port doesn't appear + self.openstack('server remove port ' + name + ' ' + port_name) + + wait_time = 0 + while wait_time < 60: + cmd_output = json.loads(self.openstack( + 'server show -f json ' + name + )) + if ip_address in cmd_output['addresses']['private']: + # Hang out for a bit and try again + print('retrying add port check') + wait_time += 10 + time.sleep(10) + else: + break + addresses = cmd_output['addresses']['private'] + self.assertNotIn(ip_address, addresses) + + def test_server_add_remove_volume(self): + volume_wait_for = volume_common.BaseVolumeTests.wait_for_status + + name = uuid.uuid4().hex cmd_output = json.loads(self.openstack( - 'server show -f json ' + name + 'server create -f json ' + + '--network private ' + + '--flavor ' + self.flavor_name + ' ' + + '--image ' + self.image_name + ' ' + + '--wait ' + + name )) - # TODO(diwei): test remove network/port after the commands are switched + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) + server_id = cmd_output['id'] + + volume_name = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'volume create -f json ' + + '--size 1 ' + + volume_name + )) + + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(volume_name, cmd_output['name']) + volume_wait_for('volume', volume_name, 'available') + self.addCleanup(self.openstack, 'volume delete ' + volume_name) + volume_id = cmd_output['id'] + + cmd_output = json.loads(self.openstack( + 'server add volume -f json ' + + name + ' ' + + volume_name + ' ' + + '--tag bar' + )) + + self.assertIsNotNone(cmd_output['ID']) + self.assertEqual(server_id, cmd_output['Server ID']) + self.assertEqual(volume_id, cmd_output['Volume ID']) + volume_attachment_id = cmd_output['ID'] + + cmd_output = json.loads(self.openstack( + 'server volume list -f json ' + + name + )) + + self.assertEqual(volume_attachment_id, cmd_output[0]['ID']) + self.assertEqual(server_id, cmd_output[0]['Server ID']) + self.assertEqual(volume_id, cmd_output[0]['Volume ID']) + + volume_wait_for('volume', volume_name, 'in-use') + self.openstack('server remove volume ' + name + ' ' + volume_name) + volume_wait_for('volume', volume_name, 'available') - self.openstack('server delete ' + name) - self.openstack('port delete ' + port_name) + raw_output = self.openstack('server volume list ' + name) + self.assertEqual('\n', raw_output) diff --git a/openstackclient/tests/functional/network/v2/test_local_ip.py b/openstackclient/tests/functional/network/v2/test_local_ip.py new file mode 100644 index 00000000..dd278e38 --- /dev/null +++ b/openstackclient/tests/functional/network/v2/test_local_ip.py @@ -0,0 +1,161 @@ +# Copyright 2021 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +import json +import uuid + +from openstackclient.tests.functional.network.v2 import common + + +class LocalIPTests(common.NetworkTests): + """Functional tests for local IP""" + + def setUp(self): + super(LocalIPTests, self).setUp() + # Nothing in this class works with Nova Network + if not self.haz_network: + self.skipTest("No Network service present") + if not self.is_extension_enabled('local-ip'): + self.skipTest("No local-ip extension present") + + def test_local_ip_create_and_delete(self): + """Test create, delete multiple""" + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'local ip create -f json ' + + name1 + )) + self.assertEqual( + name1, + cmd_output['name'], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'local ip create -f json ' + + name2 + )) + self.assertEqual( + name2, + cmd_output['name'], + ) + + raw_output = self.openstack( + 'local ip delete ' + name1 + ' ' + name2, + ) + self.assertOutput('', raw_output) + + def test_local_ip_list(self): + """Test create, list filters, delete""" + # Get project IDs + cmd_output = json.loads(self.openstack('token issue -f json ')) + auth_project_id = cmd_output['project_id'] + + cmd_output = json.loads(self.openstack('project list -f json ')) + admin_project_id = None + demo_project_id = None + for p in cmd_output: + if p['Name'] == 'admin': + admin_project_id = p['ID'] + if p['Name'] == 'demo': + demo_project_id = p['ID'] + + # Verify assumptions: + # * admin and demo projects are present + # * demo and admin are distinct projects + # * tests run as admin + self.assertIsNotNone(admin_project_id) + self.assertIsNotNone(demo_project_id) + self.assertNotEqual(admin_project_id, demo_project_id) + self.assertEqual(admin_project_id, auth_project_id) + + name1 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'local ip create -f json ' + + name1 + )) + self.addCleanup(self.openstack, 'local ip delete ' + name1) + self.assertEqual( + admin_project_id, + cmd_output["project_id"], + ) + + name2 = uuid.uuid4().hex + cmd_output = json.loads(self.openstack( + 'local ip create -f json ' + + '--project ' + demo_project_id + + ' ' + name2 + )) + self.addCleanup(self.openstack, 'local ip delete ' + name2) + self.assertEqual( + demo_project_id, + cmd_output["project_id"], + ) + + # Test list + cmd_output = json.loads(self.openstack( + 'local ip list -f json ', + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertIn(name2, names) + + # Test list --project + cmd_output = json.loads(self.openstack( + 'local ip list -f json ' + + '--project ' + demo_project_id + )) + names = [x["Name"] for x in cmd_output] + self.assertNotIn(name1, names) + self.assertIn(name2, names) + + # Test list --name + cmd_output = json.loads(self.openstack( + 'local ip list -f json ' + + '--name ' + name1 + )) + names = [x["Name"] for x in cmd_output] + self.assertIn(name1, names) + self.assertNotIn(name2, names) + + def test_local_ip_set_unset_and_show(self): + """Tests create options, set, and show""" + name = uuid.uuid4().hex + newname = name + "_" + cmd_output = json.loads(self.openstack( + 'local ip create -f json ' + + '--description aaaa ' + + name + )) + self.addCleanup(self.openstack, 'local ip delete ' + newname) + self.assertEqual(name, cmd_output['name']) + self.assertEqual('aaaa', cmd_output['description']) + + # Test set name and description + raw_output = self.openstack( + 'local ip set ' + + '--name ' + newname + ' ' + + '--description bbbb ' + + name, + ) + self.assertOutput('', raw_output) + + # Show the updated local ip + cmd_output = json.loads(self.openstack( + 'local ip show -f json ' + + newname, + )) + self.assertEqual(newname, cmd_output['name']) + self.assertEqual('bbbb', cmd_output['description']) diff --git a/openstackclient/tests/functional/volume/v3/test_volume.py b/openstackclient/tests/functional/volume/v3/test_volume.py index 6635167d..c1b45e2f 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume.py +++ b/openstackclient/tests/functional/volume/v3/test_volume.py @@ -152,6 +152,7 @@ class VolumeTests(common.BaseVolumeTests): name, ) self.assertOutput('', raw_output) + self.wait_for_status("volume", new_name, "available") cmd_output = json.loads(self.openstack( 'volume show -f json ' + diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 8771359c..896a63a7 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -950,6 +950,49 @@ class TestQuotaSet(TestQuota): ) self.assertIsNone(result) + def test_quota_set_with_check_limit(self): + arglist = [ + '--subnets', str(network_fakes.QUOTA['subnet']), + '--volumes', str(volume_fakes.QUOTA['volumes']), + '--cores', str(compute_fakes.core_num), + '--check-limit', + self.projects[0].name, + ] + verifylist = [ + ('subnet', network_fakes.QUOTA['subnet']), + ('volumes', volume_fakes.QUOTA['volumes']), + ('cores', compute_fakes.core_num), + ('check_limit', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + kwargs_compute = { + 'cores': compute_fakes.core_num, + } + kwargs_volume = { + 'volumes': volume_fakes.QUOTA['volumes'], + } + kwargs_network = { + 'subnet': network_fakes.QUOTA['subnet'], + 'check_limit': True, + } + self.compute_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_compute + ) + self.volume_quotas_mock.update.assert_called_once_with( + self.projects[0].id, + **kwargs_volume + ) + self.network_mock.update_quota.assert_called_once_with( + self.projects[0].id, + **kwargs_network + ) + self.assertIsNone(result) + class TestQuotaShow(TestQuota): diff --git a/openstackclient/tests/unit/compute/v2/fakes.py b/openstackclient/tests/unit/compute/v2/fakes.py index 23468ebc..55572cd8 100644 --- a/openstackclient/tests/unit/compute/v2/fakes.py +++ b/openstackclient/tests/unit/compute/v2/fakes.py @@ -21,6 +21,9 @@ import uuid from novaclient import api_versions from openstack.compute.v2 import flavor as _flavor from openstack.compute.v2 import server +from openstack.compute.v2 import server_interface as _server_interface +from openstack.compute.v2 import service +from openstack.compute.v2 import volume_attachment from openstackclient.api import compute_v2 from openstackclient.tests.unit import fakes @@ -762,7 +765,7 @@ class FakeService(object): :param dict attrs: A dictionary with all attributes :return: - A FakeResource object, with id, host, binary, and so on + A fake Service object, with id, host, binary, and so on """ attrs = attrs or {} @@ -772,21 +775,18 @@ class FakeService(object): 'host': 'host-' + uuid.uuid4().hex, 'binary': 'binary-' + uuid.uuid4().hex, 'status': 'enabled', - 'zone': 'zone-' + uuid.uuid4().hex, + 'availability_zone': 'zone-' + uuid.uuid4().hex, 'state': 'state-' + uuid.uuid4().hex, 'updated_at': 'time-' + uuid.uuid4().hex, 'disabled_reason': 'earthquake', # Introduced in API microversion 2.11 - 'forced_down': False, + 'is_forced_down': False, } # Overwrite default attributes. service_info.update(attrs) - service = fakes.FakeResource(info=copy.deepcopy(service_info), - loaded=True) - - return service + return service.Service(**service_info) @staticmethod def create_services(attrs=None, count=2): @@ -1803,3 +1803,85 @@ class FakeVolumeAttachment(object): attrs, methods)) return volume_attachments + + @staticmethod + def create_one_sdk_volume_attachment(attrs=None, methods=None): + """Create a fake sdk VolumeAttachment. + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :return: + A fake VolumeAttachment object, with id, device, and so on + """ + attrs = attrs or {} + methods = methods or {} + + # Set default attributes. + volume_attachment_info = { + "id": uuid.uuid4().hex, + "device": "/dev/sdb", + "server_id": uuid.uuid4().hex, + "volume_id": uuid.uuid4().hex, + # introduced in API microversion 2.70 + "tag": "foo", + # introduced in API microversion 2.79 + "delete_on_termination": True, + # introduced in API microversion 2.89 + "attachment_id": uuid.uuid4().hex, + "bdm_uuid": uuid.uuid4().hex + } + + # Overwrite default attributes. + volume_attachment_info.update(attrs) + + return volume_attachment.VolumeAttachment(**volume_attachment_info) + + @staticmethod + def create_sdk_volume_attachments(attrs=None, methods=None, count=2): + """Create multiple fake VolumeAttachment objects (BDMs). + + :param dict attrs: + A dictionary with all attributes + :param dict methods: + A dictionary with all methods + :param int count: + The number of volume attachments to fake + :return: + A list of VolumeAttachment objects faking the volume attachments. + """ + volume_attachments = [] + for i in range(0, count): + volume_attachments.append( + FakeVolumeAttachment.create_one_sdk_volume_attachment( + attrs, methods)) + + return volume_attachments + + +def create_one_server_interface(attrs=None): + """Create a fake SDK ServerInterface. + + :param dict attrs: A dictionary with all attributes + :param dict methods: A dictionary with all methods + :return: A fake ServerInterface object with various attributes set + """ + attrs = attrs or {} + + # Set default attributes. + server_interface_info = { + "fixed_ips": uuid.uuid4().hex, + "mac_addr": "aa:aa:aa:aa:aa:aa", + "net_id": uuid.uuid4().hex, + "port_id": uuid.uuid4().hex, + "port_state": "ACTIVE", + "server_id": uuid.uuid4().hex, + # introduced in API microversion 2.70 + "tag": "foo", + } + + # Overwrite default attributes. + server_interface_info.update(attrs) + + return _server_interface.ServerInterface(**server_interface_info) diff --git a/openstackclient/tests/unit/compute/v2/test_aggregate.py b/openstackclient/tests/unit/compute/v2/test_aggregate.py index 071f2a30..3a7a81cb 100644 --- a/openstackclient/tests/unit/compute/v2/test_aggregate.py +++ b/openstackclient/tests/unit/compute/v2/test_aggregate.py @@ -234,6 +234,7 @@ class TestAggregateList(TestAggregate): "Name", "Availability Zone", "Properties", + "Hosts", ) list_data = (( @@ -251,6 +252,7 @@ class TestAggregateList(TestAggregate): for key, value in TestAggregate.fake_ag.metadata.items() if key != 'availability_zone' }), + format_columns.ListColumn(TestAggregate.fake_ag.hosts), ), ) def setUp(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index f7ed4b16..480dcfe1 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -105,6 +105,9 @@ class TestServer(compute_fakes.TestComputev2): self.volumes_mock = self.app.client_manager.volume.volumes self.volumes_mock.reset_mock() + self.app.client_manager.sdk_connection.volume = mock.Mock() + self.sdk_volume_client = self.app.client_manager.sdk_connection.volume + # Get a shortcut to the volume client VolumeManager Mock self.snapshots_mock = self.app.client_manager.volume.volume_snapshots self.snapshots_mock.reset_mock() @@ -146,13 +149,18 @@ class TestServer(compute_fakes.TestComputev2): ) # This is the return value for compute_client.find_server() - self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( - servers, - 0, - ) + self.sdk_client.find_server.side_effect = servers return servers + def setup_sdk_volumes_mock(self, count): + volumes = volume_fakes.FakeVolume.create_sdk_volumes(count=count) + + # This is the return value for volume_client.find_volume() + self.sdk_volume_client.find_volume.side_effect = volumes + + return volumes + def run_method_with_servers(self, method_name, server_count): servers = self.setup_servers_mock(server_count) @@ -184,113 +192,160 @@ class TestServer(compute_fakes.TestComputev2): method.assert_called_with() self.assertIsNone(result) + def run_method_with_sdk_servers(self, method_name, server_count): + servers = self.setup_sdk_servers_mock(count=server_count) + + arglist = [s.id for s in servers] + verifylist = [ + ('server', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [call(s.id) for s in servers] + method = getattr(self.sdk_client, method_name) + method.assert_has_calls(calls) + self.assertIsNone(result) + class TestServerAddFixedIP(TestServer): def setUp(self): - super(TestServerAddFixedIP, self).setUp() + super().setUp() # Get the command object to test self.cmd = server.AddFixedIP(self.app, None) - # Set add_fixed_ip method to be tested. - self.methods = { - 'interface_attach': None, - } + # Mock network methods + self.find_network = mock.Mock() + self.app.client_manager.network.find_network = self.find_network - def _test_server_add_fixed_ip(self, extralist, fixed_ip_address): - servers = self.setup_servers_mock(count=1) + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_v244(self, sm_mock): + sm_mock.return_value = False + + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], - ] + extralist + ] verifylist = [ ('server', servers[0].id), ('network', network['id']), - ('fixed_ip_address', fixed_ip_address), + ('fixed_ip_address', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, - net_id=network['id'], - fixed_ip=fixed_ip_address, + self.sdk_client.add_fixed_ip_to_server.assert_called_once_with( + servers[0].id, + network['id'] ) - self.assertIsNone(result) + # the legacy API operates asynchronously + self.assertEqual(((), ()), result) - def test_server_add_fixed_ip(self): - self._test_server_add_fixed_ip([], None) + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_v244_with_fixed_ip(self, sm_mock): + sm_mock.return_value = False - def test_server_add_specific_fixed_ip(self): - extralist = ['--fixed-ip-address', '5.6.7.8'] - self._test_server_add_fixed_ip(extralist, '5.6.7.8') + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() - def test_server_add_fixed_ip_with_tag(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.49') + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) - servers = self.setup_servers_mock(count=1) + result = self.cmd.take_action(parsed_args) + + self.sdk_client.add_fixed_ip_to_server.assert_called_once_with( + servers[0].id, + network['id'] + ) + # the legacy API operates asynchronously + self.assertEqual(((), ()), result) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_v244_with_tag(self, sm_mock): + sm_mock.return_value = False + + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], '--fixed-ip-address', '5.6.7.8', - '--tag', 'tag1', + '--tag', 'tag1' ] verifylist = [ ('server', servers[0].id), ('network', network['id']), ('fixed_ip_address', '5.6.7.8'), - ('tag', 'tag1'), + ('tag', 'tag1') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - servers[0].interface_attach.assert_called_once_with( - port_id=None, - net_id=network['id'], - fixed_ip='5.6.7.8', - tag='tag1' - ) - self.assertIsNone(result) + ex = self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.49 or greater is required', + str(ex)) - def test_server_add_fixed_ip_with_tag_pre_v249(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.48') + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_pre_v249_with_tag(self, sm_mock): + sm_mock.side_effect = [False, True] - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = compute_fakes.FakeNetwork.create_one_network() - with mock.patch( - 'openstackclient.api.compute_v2.APIv2.network_find' - ) as net_mock: - net_mock.return_value = network + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): arglist = [ servers[0].id, network['id'], '--fixed-ip-address', '5.6.7.8', - '--tag', 'tag1', + '--tag', 'tag1' ] verifylist = [ ('server', servers[0].id), ('network', network['id']), ('fixed_ip_address', '5.6.7.8'), - ('tag', 'tag1'), + ('tag', 'tag1') ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises( exceptions.CommandError, self.cmd.take_action, @@ -299,6 +354,170 @@ class TestServerAddFixedIP(TestServer): '--os-compute-api-version 2.49 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip(self, sm_mock): + sm_mock.side_effect = [True, False] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'] + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + ) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip=None + ) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_with_fixed_ip(self, sm_mock): + sm_mock.side_effect = [True, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + 'Tag', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + interface.tag, + ) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip='5.6.7.8' + ) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_fixed_ip_with_tag(self, sm_mock): + sm_mock.side_effect = [True, True, True] + + servers = self.setup_sdk_servers_mock(count=1) + network = compute_fakes.FakeNetwork.create_one_network() + interface = compute_fakes.create_one_server_interface() + self.sdk_client.create_server_interface.return_value = interface + + with mock.patch.object( + self.app.client_manager, + 'is_network_endpoint_enabled', + return_value=False + ): + arglist = [ + servers[0].id, + network['id'], + '--fixed-ip-address', '5.6.7.8', + '--tag', 'tag1' + ] + verifylist = [ + ('server', servers[0].id), + ('network', network['id']), + ('fixed_ip_address', '5.6.7.8'), + ('tag', 'tag1') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + expected_columns = ( + 'Port ID', + 'Server ID', + 'Network ID', + 'MAC Address', + 'Port State', + 'Fixed IPs', + 'Tag', + ) + expected_data = ( + interface.port_id, + interface.server_id, + interface.net_id, + interface.mac_addr, + interface.port_state, + format_columns.ListDictColumn(interface.fixed_ips), + interface.tag, + ) + + columns, data = self.cmd.take_action(parsed_args) + + self.assertEqual(expected_columns, columns) + self.assertEqual(expected_data, tuple(data)) + self.sdk_client.create_server_interface.assert_called_once_with( + servers[0].id, + net_id=network['id'], + fixed_ip='5.6.7.8', + tag='tag1', + ) + @mock.patch( 'openstackclient.api.compute_v2.APIv2.floating_ip_add' @@ -680,31 +899,44 @@ class TestServerVolume(TestServer): def setUp(self): super(TestServerVolume, self).setUp() - self.volume = volume_fakes.FakeVolume.create_one_volume() - self.volumes_mock.get.return_value = self.volume - self.methods = { - 'create_server_volume': None, + 'create_volume_attachment': None, + } + + self.servers = self.setup_sdk_servers_mock(count=1) + self.volumes = self.setup_sdk_volumes_mock(count=1) + + attrs = { + 'server_id': self.servers[0].id, + 'volume_id': self.volumes[0].id, } + self.volume_attachment = \ + compute_fakes.FakeVolumeAttachment.\ + create_one_sdk_volume_attachment(attrs=attrs) + + self.sdk_client.create_volume_attachment.return_value = \ + self.volume_attachment + + +class TestServerAddVolume(TestServerVolume): + + def setUp(self): + super(TestServerAddVolume, self).setUp() # Get the command object to test self.cmd = server.AddServerVolume(self.app, None) - def test_server_add_volume(self): - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_volume(self, sm_mock): arglist = [ '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ] @@ -712,39 +944,36 @@ class TestServerVolume(TestServer): expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device') expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + '/dev/sdb', ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, device='/dev/sdb') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], volumeId=self.volumes[0].id, device='/dev/sdb') - def test_server_add_volume_with_tag(self): - # requires API 2.49 or later - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.49') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_server_add_volume_with_tag(self, sm_mock): + def side_effect(compute_client, version): + if version == '2.49': + return True + return False + sm_mock.side_effect = side_effect arglist = [ '--device', '/dev/sdb', '--tag', 'foo', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('tag', 'foo'), ] @@ -753,33 +982,33 @@ class TestServerVolume(TestServer): expected_columns = ('ID', 'Server ID', 'Volume ID', 'Device', 'Tag') expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, device='/dev/sdb', tag='foo') self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + tag='foo') - def test_server_add_volume_with_tag_pre_v249(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.48') - - servers = self.setup_servers_mock(count=1) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=False) + def test_server_add_volume_with_tag_pre_v249(self, sm_mock): arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--tag', 'foo', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('tag', 'foo'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -792,26 +1021,22 @@ class TestServerVolume(TestServer): '--os-compute-api-version 2.49 or greater is required', str(ex)) - def test_server_add_volume_with_enable_delete_on_termination(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment - + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_volume_with_enable_delete_on_termination( + self, + sm_mock, + ): + self.volume_attachment.delete_on_termination = True arglist = [ '--enable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('enable_delete_on_termination', True), ] @@ -826,42 +1051,40 @@ class TestServerVolume(TestServer): 'Delete On Termination', ) expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, - volume_attachment.delete_on_termination, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, + self.volume_attachment.delete_on_termination, ) columns, data = self.cmd.take_action(parsed_args) - - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, - device='/dev/sdb', delete_on_termination=True) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + delete_on_termination=True) - def test_server_add_volume_with_disable_delete_on_termination(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) - volume_attachment = \ - compute_fakes.FakeVolumeAttachment.create_one_volume_attachment() - self.servers_volumes_mock.create_server_volume.return_value = \ - volume_attachment + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) + def test_server_add_volume_with_disable_delete_on_termination( + self, + sm_mock, + ): + self.volume_attachment.delete_on_termination = False arglist = [ '--disable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('disable_delete_on_termination', True), ] @@ -876,37 +1099,43 @@ class TestServerVolume(TestServer): 'Delete On Termination', ) expected_data = ( - volume_attachment.id, - volume_attachment.serverId, - volume_attachment.volumeId, - volume_attachment.device, - volume_attachment.tag, - volume_attachment.delete_on_termination, + self.volume_attachment.id, + self.volume_attachment.server_id, + self.volume_attachment.volume_id, + self.volume_attachment.device, + self.volume_attachment.tag, + self.volume_attachment.delete_on_termination, ) columns, data = self.cmd.take_action(parsed_args) - self.servers_volumes_mock.create_server_volume.assert_called_once_with( - servers[0].id, self.volume.id, - device='/dev/sdb', delete_on_termination=False) self.assertEqual(expected_columns, columns) self.assertEqual(expected_data, data) + self.sdk_client.create_volume_attachment.assert_called_once_with( + self.servers[0], + volumeId=self.volumes[0].id, + device='/dev/sdb', + delete_on_termination=False) + @mock.patch.object(sdk_utils, 'supports_microversion') def test_server_add_volume_with_enable_delete_on_termination_pre_v279( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.78') + def side_effect(compute_client, version): + if version == '2.79': + return False + return True + sm_mock.side_effect = side_effect - servers = self.setup_servers_mock(count=1) arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--enable-delete-on-termination', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('enable_delete_on_termination', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -917,21 +1146,25 @@ class TestServerVolume(TestServer): self.assertIn('--os-compute-api-version 2.79 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion') def test_server_add_volume_with_disable_delete_on_termination_pre_v279( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.78') + def side_effect(compute_client, version): + if version == '2.79': + return False + return True + sm_mock.side_effect = side_effect - servers = self.setup_servers_mock(count=1) arglist = [ - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, '--disable-delete-on-termination', ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('disable_delete_on_termination', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -942,24 +1175,22 @@ class TestServerVolume(TestServer): self.assertIn('--os-compute-api-version 2.79 or greater is required', str(ex)) + @mock.patch.object(sdk_utils, 'supports_microversion', return_value=True) def test_server_add_volume_with_disable_and_enable_delete_on_termination( self, + sm_mock, ): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.79') - - servers = self.setup_servers_mock(count=1) arglist = [ '--enable-delete-on-termination', '--disable-delete-on-termination', '--device', '/dev/sdb', - servers[0].id, - self.volume.id, + self.servers[0].id, + self.volumes[0].id, ] verifylist = [ - ('server', servers[0].id), - ('volume', self.volume.id), + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), ('device', '/dev/sdb'), ('enable_delete_on_termination', True), ('disable_delete_on_termination', True), @@ -971,6 +1202,37 @@ class TestServerVolume(TestServer): 'with argument --enable-delete-on-termination', str(ex)) +class TestServerRemoveVolume(TestServerVolume): + + def setUp(self): + super(TestServerRemoveVolume, self).setUp() + + # Get the command object to test + self.cmd = server.RemoveServerVolume(self.app, None) + + def test_server_remove_volume(self): + arglist = [ + self.servers[0].id, + self.volumes[0].id, + ] + + verifylist = [ + ('server', self.servers[0].id), + ('volume', self.volumes[0].id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + self.sdk_client.delete_volume_attachment.assert_called_once_with( + self.volumes[0], + self.servers[0], + ignore_missing=False, + ) + + class TestServerAddNetwork(TestServer): def setUp(self): @@ -4028,7 +4290,7 @@ class TestServerDumpCreate(TestServer): self.run_method_with_servers('trigger_crash_dump', 3) -class TestServerList(TestServer): +class _TestServerList(TestServer): # Columns to be listed up. columns = ( @@ -4056,7 +4318,7 @@ class TestServerList(TestServer): ) def setUp(self): - super(TestServerList, self).setUp() + super(_TestServerList, self).setUp() self.search_opts = { 'reservation_id': None, @@ -4095,7 +4357,7 @@ class TestServerList(TestServer): }, 'OS-EXT-AZ:availability_zone': 'availability-zone-xxx', 'OS-EXT-SRV-ATTR:host': 'host-name-xxx', - 'Metadata': '', + 'Metadata': format_columns.DictColumn({}), } # The servers to be listed. @@ -4114,10 +4376,11 @@ class TestServerList(TestServer): # Get the command object to test self.cmd = server.ListServer(self.app, None) - # Prepare data returned by fake Nova API. - self.data = [] - self.data_long = [] - self.data_no_name_lookup = [] + +class TestServerList(_TestServerList): + + def setUp(self): + super(TestServerList, self).setUp() Image = collections.namedtuple('Image', 'id name') self.images_mock.return_value = [ @@ -4132,8 +4395,8 @@ class TestServerList(TestServer): for s in self.servers ] - for s in self.servers: - self.data.append(( + self.data = tuple( + ( s.id, s.name, s.status, @@ -4141,34 +4404,8 @@ class TestServerList(TestServer): # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, - )) - self.data_long.append(( - s.id, - s.name, - s.status, - getattr(s, 'OS-EXT-STS:task_state'), - server.PowerStateColumn( - getattr(s, 'OS-EXT-STS:power_state') - ), - format_columns.DictListColumn(s.networks), - # Image will be an empty string if boot-from-volume - self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, - s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, - self.flavor.name, - s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - format_columns.DictColumn({}), - )) - self.data_no_name_lookup.append(( - s.id, - s.name, - s.status, - format_columns.DictListColumn(s.networks), - # Image will be an empty string if boot-from-volume - s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, - s.flavor['id'] - )) + ) for s in self.servers + ) def test_server_list_no_option(self): arglist = [] @@ -4189,7 +4426,7 @@ class TestServerList(TestServer): self.assertFalse(self.flavors_mock.get.call_count) self.assertFalse(self.get_image_mock.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_no_servers(self): arglist = [] @@ -4208,9 +4445,28 @@ class TestServerList(TestServer): self.assertEqual(0, self.images_mock.list.call_count) self.assertEqual(0, self.flavors_mock.list.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server.PowerStateColumn( + getattr(s, 'OS-EXT-STS:power_state') + ), + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + s.flavor['id'], + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + ) for s in self.servers) arglist = [ '--long', ] @@ -4221,10 +4477,9 @@ class TestServerList(TestServer): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns_long, columns) - self.assertCountEqual(tuple(self.data_long), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_column_option(self): arglist = [ @@ -4246,6 +4501,18 @@ class TestServerList(TestServer): self.assertIn('Created At', columns) def test_server_list_no_name_lookup_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '--no-name-lookup', ] @@ -4259,9 +4526,21 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_n_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '-n', ] @@ -4275,7 +4554,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_name_lookup_one_by_one(self): arglist = [ @@ -4297,7 +4576,7 @@ class TestServerList(TestServer): self.flavors_mock.get.assert_called() self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_image(self): @@ -4318,81 +4597,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_locked_pre_v273(self): - - arglist = [ - '--locked' - ] - verifylist = [ - ('locked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.73 or greater is required', str(ex)) - - def test_server_list_with_locked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--locked' - ] - verifylist = [ - ('locked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['locked'] = True - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_unlocked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--unlocked' - ] - verifylist = [ - ('unlocked', True) - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['locked'] = False - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - def test_server_list_with_locked_and_unlocked_v273(self): - - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') - arglist = [ - '--locked', - '--unlocked' - ] - verifylist = [ - ('locked', True), - ('unlocked', True) - ] - - ex = self.assertRaises( - utils.ParserException, - self.check_parser, self.cmd, arglist, verifylist) - self.assertIn('Argument parse failed', str(ex)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_flavor(self): @@ -4412,7 +4617,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_changes_since(self): @@ -4433,7 +4638,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): @@ -4456,123 +4661,6 @@ class TestServerList(TestServer): 'Invalid time value' ) - def test_server_list_v266_with_changes_before(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) - arglist = [ - '--changes-before', '2016-03-05T06:27:59Z', - '--deleted' - ] - verifylist = [ - ('changes_before', '2016-03-05T06:27:59Z'), - ('deleted', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) - - self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' - self.search_opts['deleted'] = True - - self.servers_mock.list.assert_called_with(**self.kwargs) - - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) - - @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) - def test_server_list_v266_with_invalid_changes_before( - self, mock_parse_isotime): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) - - arglist = [ - '--changes-before', 'Invalid time value', - ] - verifylist = [ - ('changes_before', 'Invalid time value'), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('Invalid changes-before value: Invalid time ' - 'value', str(e)) - mock_parse_isotime.assert_called_once_with( - 'Invalid time value' - ) - - def test_server_with_changes_before_pre_v266(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.65')) - - arglist = [ - '--changes-before', '2016-03-05T06:27:59Z', - '--deleted' - ] - verifylist = [ - ('changes_before', '2016-03-05T06:27:59Z'), - ('deleted', True), - ] - - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - - def test_server_list_v269_with_partial_constructs(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.69') - - arglist = [] - verifylist = [] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - # include "partial results" from non-responsive part of - # infrastructure. - server_dict = { - "id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a", - "status": "UNKNOWN", - "tenant_id": "6f70656e737461636b20342065766572", - "created": "2018-12-03T21:06:18Z", - "links": [ - { - "href": "http://fake/v2.1/", - "rel": "self" - }, - { - "href": "http://fake", - "rel": "bookmark" - } - ], - # We need to pass networks as {} because its defined as a property - # of the novaclient Server class which gives {} by default. If not - # it will fail at formatting the networks info later on. - "networks": {} - } - _server = compute_fakes.fakes.FakeResource( - info=server_dict, - ) - self.servers.append(_server) - columns, data = self.cmd.take_action(parsed_args) - # get the first three servers out since our interest is in the partial - # server. - next(data) - next(data) - next(data) - partial_server = next(data) - expected_row = ( - 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', - '', - 'UNKNOWN', - format_columns.DictListColumn({}), - '', - '', - ) - self.assertEqual(expected_row, partial_server) - def test_server_list_with_tag(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( '2.26') @@ -4593,7 +4681,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_tag_pre_v225(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -4636,7 +4724,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_not_tag_pre_v226(self): self.app.client_manager.compute.api_version = api_versions.APIVersion( @@ -4797,6 +4885,258 @@ class TestServerList(TestServer): self.assertEqual(tuple(self.data), tuple(data)) +class TestServerListV273(_TestServerList): + + # Columns to be listed up. + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image', + 'Flavor', + ) + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Image Name', + 'Image ID', + 'Flavor', + 'Availability Zone', + 'Host', + 'Properties', + ) + + def setUp(self): + super(TestServerListV273, self).setUp() + + # The fake servers' attributes. Use the original attributes names in + # nova, not the ones printed by "server list" command. + self.attrs['flavor'] = { + 'vcpus': self.flavor.vcpus, + 'ram': self.flavor.ram, + 'disk': self.flavor.disk, + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'original_name': self.flavor.name, + 'extra_specs': self.flavor.extra_specs, + } + + # The servers to be listed. + self.servers = self.setup_servers_mock(3) + self.servers_mock.list.return_value = self.servers + + Image = collections.namedtuple('Image', 'id name') + self.images_mock.return_value = [ + Image(id=s.image['id'], name=self.image.name) + # Image will be an empty string if boot-from-volume + for s in self.servers if s.image + ] + + # The flavor information is embedded, so now reason for this to be + # called + self.flavors_mock.list = mock.NonCallableMock() + + self.data = tuple( + ( + s.id, + s.name, + s.status, + format_columns.DictListColumn(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, + self.flavor.name, + ) for s in self.servers) + + def test_server_list_with_locked_pre_v273(self): + + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) + + def test_server_list_with_locked(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked' + ] + verifylist = [ + ('locked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + def test_server_list_with_unlocked_v273(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--unlocked' + ] + verifylist = [ + ('unlocked', True) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['locked'] = False + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + def test_server_list_with_locked_and_unlocked(self): + + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') + arglist = [ + '--locked', + '--unlocked' + ] + verifylist = [ + ('locked', True), + ('unlocked', True) + ] + + ex = self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) + + def test_server_list_with_changes_before(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' + self.search_opts['deleted'] = True + + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) + + @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) + def test_server_list_with_invalid_changes_before( + self, mock_parse_isotime): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.66')) + + arglist = [ + '--changes-before', 'Invalid time value', + ] + verifylist = [ + ('changes_before', 'Invalid time value'), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('Invalid changes-before value: Invalid time ' + 'value', str(e)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) + + def test_server_with_changes_before_pre_v266(self): + self.app.client_manager.compute.api_version = ( + api_versions.APIVersion('2.65')) + + arglist = [ + '--changes-before', '2016-03-05T06:27:59Z', + '--deleted' + ] + verifylist = [ + ('changes_before', '2016-03-05T06:27:59Z'), + ('deleted', True), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + + def test_server_list_v269_with_partial_constructs(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.69') + arglist = [] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + # include "partial results" from non-responsive part of + # infrastructure. + server_dict = { + "id": "server-id-95a56bfc4xxxxxx28d7e418bfd97813a", + "status": "UNKNOWN", + "tenant_id": "6f70656e737461636b20342065766572", + "created": "2018-12-03T21:06:18Z", + "links": [ + { + "href": "http://fake/v2.1/", + "rel": "self" + }, + { + "href": "http://fake", + "rel": "bookmark" + } + ], + # We need to pass networks as {} because its defined as a property + # of the novaclient Server class which gives {} by default. If not + # it will fail at formatting the networks info later on. + "networks": {} + } + server = compute_fakes.fakes.FakeResource( + info=server_dict, + ) + self.servers.append(server) + columns, data = self.cmd.take_action(parsed_args) + # get the first three servers out since our interest is in the partial + # server. + next(data) + next(data) + next(data) + partial_server = next(data) + expected_row = ( + 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', '', + 'UNKNOWN', format_columns.DictListColumn({}), '', '') + self.assertEqual(expected_row, partial_server) + + class TestServerLock(TestServer): def setUp(self): @@ -4911,7 +5251,7 @@ class TestServerMigrate(TestServer): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4933,7 +5273,7 @@ class TestServerMigrate(TestServer): ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4955,7 +5295,7 @@ class TestServerMigrate(TestServer): verifylist = [ ('live_migration', False), ('block_migration', True), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -4986,7 +5326,7 @@ class TestServerMigrate(TestServer): self.assertNotCalled(self.servers_mock.live_migrate) self.assertNotCalled(self.servers_mock.migrate) - def test_server_migrate_with_host_pre_2_56(self): + def test_server_migrate_with_host_pre_v256(self): # Tests that --host is not allowed for a cold migration # before microversion 2.56 (the test defaults to 2.1). arglist = [ @@ -4996,7 +5336,7 @@ class TestServerMigrate(TestServer): ('live_migration', False), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5022,7 +5362,7 @@ class TestServerMigrate(TestServer): ('live_migration', True), ('host', None), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5046,7 +5386,7 @@ class TestServerMigrate(TestServer): ('live_migration', True), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5074,7 +5414,7 @@ class TestServerMigrate(TestServer): ('live_migration', True), ('host', 'fakehost'), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5101,7 +5441,7 @@ class TestServerMigrate(TestServer): verifylist = [ ('live_migration', True), ('block_migration', True), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5187,7 +5527,7 @@ class TestServerMigrate(TestServer): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5207,7 +5547,7 @@ class TestServerMigrate(TestServer): verifylist = [ ('live_migration', False), ('block_migration', None), - ('disk_overcommit', False), + ('disk_overcommit', None), ('wait', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -6009,10 +6349,10 @@ class TestServerPause(TestServer): } def test_server_pause_one_server(self): - self.run_method_with_servers('pause', 1) + self.run_method_with_sdk_servers('pause_server', 1) def test_server_pause_multi_servers(self): - self.run_method_with_servers('pause', 3) + self.run_method_with_sdk_servers('pause_server', 3) class TestServerRebuild(TestServer): @@ -6403,7 +6743,7 @@ class TestServerRebuild(TestServer): self.image, None, userdata=mock_file,) - def test_rebuild_with_user_data_pre_257(self): + def test_rebuild_with_user_data_pre_v257(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.56') @@ -6443,7 +6783,7 @@ class TestServerRebuild(TestServer): self.server.rebuild.assert_called_with( self.image, None, userdata=None) - def test_rebuild_with_no_user_data_pre_254(self): + def test_rebuild_with_no_user_data_pre_v254(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.53') @@ -6535,7 +6875,7 @@ class TestServerRebuild(TestServer): self.server.rebuild.assert_called_with( self.image, None, trusted_image_certificates=None) - def test_rebuild_with_no_trusted_image_cert_pre_257(self): + def test_rebuild_with_no_trusted_image_cert_pre_v263(self): self.app.client_manager.compute.api_version = \ api_versions.APIVersion('2.62') @@ -6959,14 +7299,14 @@ class TestServerRemovePort(TestServer): # Set method to be tested. self.methods = { - 'interface_detach': None, + 'delete_server_interface': None, } self.find_port = mock.Mock() self.app.client_manager.network.find_port = self.find_port def _test_server_remove_port(self, port_id): - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) port = 'fake-port' arglist = [ @@ -6981,7 +7321,8 @@ class TestServerRemovePort(TestServer): result = self.cmd.take_action(parsed_args) - servers[0].interface_detach.assert_called_once_with(port_id) + self.sdk_client.delete_server_interface.assert_called_with( + port_id, server=servers[0], ignore_missing=False) self.assertIsNone(result) def test_server_remove_port(self): @@ -7006,17 +7347,18 @@ class TestServerRemoveNetwork(TestServer): # Set method to be tested. self.fake_inf = mock.Mock() self.methods = { - 'interface_list': [self.fake_inf], - 'interface_detach': None, + 'server_interfaces': [self.fake_inf], + 'delete_server_interface': None, } self.find_network = mock.Mock() self.app.client_manager.network.find_network = self.find_network + self.sdk_client.server_interfaces.return_value = [self.fake_inf] def _test_server_remove_network(self, network_id): self.fake_inf.net_id = network_id self.fake_inf.port_id = 'fake-port' - servers = self.setup_servers_mock(count=1) + servers = self.setup_sdk_servers_mock(count=1) network = 'fake-network' arglist = [ @@ -7031,8 +7373,9 @@ class TestServerRemoveNetwork(TestServer): result = self.cmd.take_action(parsed_args) - servers[0].interface_list.assert_called_once_with() - servers[0].interface_detach.assert_called_once_with('fake-port') + self.sdk_client.server_interfaces.assert_called_once_with(servers[0]) + self.sdk_client.delete_server_interface.assert_called_once_with( + 'fake-port', server=servers[0]) self.assertIsNone(result) def test_server_remove_network(self): @@ -7548,10 +7891,10 @@ class TestServerResume(TestServer): } def test_server_resume_one_server(self): - self.run_method_with_servers('resume', 1) + self.run_method_with_sdk_servers('resume_server', 1) def test_server_resume_multi_servers(self): - self.run_method_with_servers('resume', 3) + self.run_method_with_sdk_servers('resume_server', 3) class TestServerSet(TestServer): @@ -8117,6 +8460,123 @@ class TestServerShow(TestServer): exceptions.CommandError, self.cmd.take_action, parsed_args) +@mock.patch('openstackclient.compute.v2.server.os.system') +class TestServerSsh(TestServer): + + def setUp(self): + super().setUp() + + self.cmd = server.SshServer(self.app, None) + + self.app.client_manager.auth_ref = mock.Mock() + self.app.client_manager.auth_ref.username = 'cloud' + + self.attrs = { + 'addresses': { + 'public': [ + { + 'addr': '192.168.1.30', + 'OS-EXT-IPS-MAC:mac_addr': '00:0c:29:0d:11:74', + 'OS-EXT-IPS:type': 'fixed', + 'version': 4, + }, + ], + }, + } + self.server = compute_fakes.FakeServer.create_one_server( + attrs=self.attrs, methods=self.methods, + ) + self.servers_mock.get.return_value = self.server + + def test_server_ssh_no_opts(self, mock_exec): + arglist = [ + self.server.name, + ] + verifylist = [ + ('server', self.server.name), + ('login', None), + ('port', None), + ('identity', None), + ('option', None), + ('ipv4', False), + ('ipv6', False), + ('address_type', 'public'), + ('verbose', False), + ('ssh_args', []), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + mock_exec.assert_called_once_with('ssh 192.168.1.30 -l cloud') + mock_warning.assert_not_called() + + def test_server_ssh_passthrough_opts(self, mock_exec): + arglist = [ + self.server.name, + '--', + '-l', 'username', + '-p', '2222', + ] + verifylist = [ + ('server', self.server.name), + ('login', None), + ('port', None), + ('identity', None), + ('option', None), + ('ipv4', False), + ('ipv6', False), + ('address_type', 'public'), + ('verbose', False), + ('ssh_args', ['-l', 'username', '-p', '2222']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + mock_exec.assert_called_once_with( + 'ssh 192.168.1.30 -l username -p 2222' + ) + mock_warning.assert_not_called() + + def test_server_ssh_deprecated_opts(self, mock_exec): + arglist = [ + self.server.name, + '-l', 'username', + '-p', '2222', + ] + verifylist = [ + ('server', self.server.name), + ('login', 'username'), + ('port', 2222), + ('identity', None), + ('option', None), + ('ipv4', False), + ('ipv6', False), + ('address_type', 'public'), + ('verbose', False), + ('ssh_args', []), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + with mock.patch.object(self.cmd.log, 'warning') as mock_warning: + result = self.cmd.take_action(parsed_args) + + self.assertIsNone(result) + mock_exec.assert_called_once_with( + 'ssh 192.168.1.30 -p 2222 -l username' + ) + mock_warning.assert_called_once() + self.assertIn( + 'The ssh options have been deprecated.', + mock_warning.call_args[0][0], + ) + + class TestServerStart(TestServer): def setUp(self): @@ -8215,10 +8675,10 @@ class TestServerSuspend(TestServer): } def test_server_suspend_one_server(self): - self.run_method_with_servers('suspend', 1) + self.run_method_with_sdk_servers('suspend_server', 1) def test_server_suspend_multi_servers(self): - self.run_method_with_servers('suspend', 3) + self.run_method_with_sdk_servers('suspend_server', 3) class TestServerUnlock(TestServer): @@ -8255,10 +8715,10 @@ class TestServerUnpause(TestServer): } def test_server_unpause_one_server(self): - self.run_method_with_servers('unpause', 1) + self.run_method_with_sdk_servers('unpause_server', 1) def test_server_unpause_multi_servers(self): - self.run_method_with_servers('unpause', 3) + self.run_method_with_sdk_servers('unpause_server', 3) class TestServerUnset(TestServer): diff --git a/openstackclient/tests/unit/compute/v2/test_server_image.py b/openstackclient/tests/unit/compute/v2/test_server_image.py index e1740169..db0e1d9e 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_image.py +++ b/openstackclient/tests/unit/compute/v2/test_server_image.py @@ -27,8 +27,9 @@ class TestServerImage(compute_fakes.TestComputev2): super(TestServerImage, self).setUp() # Get a shortcut to the compute client ServerManager Mock - self.servers_mock = self.app.client_manager.compute.servers - self.servers_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute # Get a shortcut to the image client ImageManager Mock self.images_mock = self.app.client_manager.image @@ -41,14 +42,14 @@ class TestServerImage(compute_fakes.TestComputev2): self.methods = {} def setup_servers_mock(self, count): - servers = compute_fakes.FakeServer.create_servers( + servers = compute_fakes.FakeServer.create_sdk_servers( attrs=self.attrs, methods=self.methods, count=count, ) - # This is the return value for utils.find_resource() - self.servers_mock.get = compute_fakes.FakeServer.get_servers( + # This is the return value for compute_client.find_server() + self.sdk_client.find_server = compute_fakes.FakeServer.get_servers( servers, 0, ) @@ -104,8 +105,8 @@ class TestServerImageCreate(TestServerImage): ) self.images_mock.find_image = mock.Mock(side_effect=images) - self.servers_mock.create_image = mock.Mock( - return_value=images[0].id, + self.sdk_client.create_server_image = mock.Mock( + return_value=images[0], ) return images @@ -126,8 +127,7 @@ class TestServerImageCreate(TestServerImage): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, @@ -157,8 +157,7 @@ class TestServerImageCreate(TestServerImage): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, 'img-nam', {'key': 'value'}, @@ -188,8 +187,7 @@ class TestServerImageCreate(TestServerImage): parsed_args, ) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, @@ -221,8 +219,7 @@ class TestServerImageCreate(TestServerImage): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # ServerManager.create_image(server, image_name, metadata=) - self.servers_mock.create_image.assert_called_with( + self.sdk_client.create_server_image.assert_called_with( servers[0].id, servers[0].name, None, diff --git a/openstackclient/tests/unit/compute/v2/test_service.py b/openstackclient/tests/unit/compute/v2/test_service.py index c547c3a6..5b58431a 100644 --- a/openstackclient/tests/unit/compute/v2/test_service.py +++ b/openstackclient/tests/unit/compute/v2/test_service.py @@ -17,6 +17,7 @@ from unittest import mock from unittest.mock import call from novaclient import api_versions +from openstack import utils as sdk_utils from osc_lib import exceptions from openstackclient.compute.v2 import service @@ -28,9 +29,9 @@ class TestService(compute_fakes.TestComputev2): def setUp(self): super(TestService, self).setUp() - # Get a shortcut to the ServiceManager Mock - self.service_mock = self.app.client_manager.compute.services - self.service_mock.reset_mock() + self.app.client_manager.sdk_connection = mock.Mock() + self.app.client_manager.sdk_connection.compute = mock.Mock() + self.sdk_client = self.app.client_manager.sdk_connection.compute class TestServiceDelete(TestService): @@ -40,7 +41,7 @@ class TestServiceDelete(TestService): def setUp(self): super(TestServiceDelete, self).setUp() - self.service_mock.delete.return_value = None + self.sdk_client.delete_service.return_value = None # Get the command object to test self.cmd = service.DeleteService(self.app, None) @@ -56,8 +57,9 @@ class TestServiceDelete(TestService): result = self.cmd.take_action(parsed_args) - self.service_mock.delete.assert_called_with( + self.sdk_client.delete_service.assert_called_with( self.services[0].binary, + ignore_missing=False ) self.assertIsNone(result) @@ -74,8 +76,8 @@ class TestServiceDelete(TestService): calls = [] for s in self.services: - calls.append(call(s.binary)) - self.service_mock.delete.assert_has_calls(calls) + calls.append(call(s.binary, ignore_missing=False)) + self.sdk_client.delete_service.assert_has_calls(calls) self.assertIsNone(result) def test_multi_services_delete_with_exception(self): @@ -89,7 +91,7 @@ class TestServiceDelete(TestService): parsed_args = self.check_parser(self.cmd, arglist, verifylist) delete_mock_result = [None, exceptions.CommandError] - self.service_mock.delete = ( + self.sdk_client.delete_service = ( mock.Mock(side_effect=delete_mock_result) ) @@ -100,8 +102,14 @@ class TestServiceDelete(TestService): self.assertEqual( '1 of 2 compute services failed to delete.', str(e)) - self.service_mock.delete.assert_any_call(self.services[0].binary) - self.service_mock.delete.assert_any_call('unexist_service') + self.sdk_client.delete_service.assert_any_call( + self.services[0].binary, + ignore_missing=False + ) + self.sdk_client.delete_service.assert_any_call( + 'unexist_service', + ignore_missing=False + ) class TestServiceList(TestService): @@ -125,7 +133,7 @@ class TestServiceList(TestService): service.id, service.binary, service.host, - service.zone, + service.availability_zone, service.status, service.state, service.updated_at, @@ -135,7 +143,7 @@ class TestServiceList(TestService): def setUp(self): super(TestServiceList, self).setUp() - self.service_mock.list.return_value = [self.service] + self.sdk_client.services.return_value = [self.service] # Get the command object to test self.cmd = service.ListService(self.app, None) @@ -156,15 +164,18 @@ class TestServiceList(TestService): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.service_mock.list.assert_called_with( - self.service.host, - self.service.binary, + self.sdk_client.services.assert_called_with( + host=self.service.host, + binary=self.service.binary, ) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) - def test_service_list_with_long_option(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_list_with_long_option(self, sm_mock): + sm_mock.return_value = False + arglist = [ '--host', self.service.host, '--service', self.service.binary, @@ -182,15 +193,18 @@ class TestServiceList(TestService): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.service_mock.list.assert_called_with( - self.service.host, - self.service.binary, + self.sdk_client.services.assert_called_with( + host=self.service.host, + binary=self.service.binary, ) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data_long, list(data)) - def test_service_list_with_long_option_2_11(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_list_with_long_option_2_11(self, sm_mock): + sm_mock.return_value = True + arglist = [ '--host', self.service.host, '--service', self.service.binary, @@ -210,14 +224,14 @@ class TestServiceList(TestService): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.service_mock.list.assert_called_with( - self.service.host, - self.service.binary, + self.sdk_client.services.assert_called_with( + host=self.service.host, + binary=self.service.binary, ) # In 2.11 there is also a forced_down column. columns_long = self.columns_long + ('Forced Down',) - data_long = [self.data_long[0] + (self.service.forced_down,)] + data_long = [self.data_long[0] + (self.service.is_forced_down,)] self.assertEqual(columns_long, columns) self.assertEqual(data_long, list(data)) @@ -230,12 +244,14 @@ class TestServiceSet(TestService): self.service = compute_fakes.FakeService.create_one_service() - self.service_mock.enable.return_value = self.service - self.service_mock.disable.return_value = self.service + self.sdk_client.enable_service.return_value = self.service + self.sdk_client.disable_service.return_value = self.service self.cmd = service.SetService(self.app, None) - def test_set_nothing(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_set_nothing(self, sm_mock): + sm_mock.return_value = False arglist = [ self.service.host, self.service.binary, @@ -247,12 +263,13 @@ class TestServiceSet(TestService): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.service_mock.enable.assert_not_called() - self.service_mock.disable.assert_not_called() - self.service_mock.disable_log_reason.assert_not_called() + self.sdk_client.enable_service.assert_not_called() + self.sdk_client.disable_service.assert_not_called() self.assertIsNone(result) - def test_service_set_enable(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_enable(self, sm_mock): + sm_mock.return_value = False arglist = [ '--enable', self.service.host, @@ -267,13 +284,16 @@ class TestServiceSet(TestService): result = self.cmd.take_action(parsed_args) - self.service_mock.enable.assert_called_with( + self.sdk_client.enable_service.assert_called_with( + None, self.service.host, self.service.binary ) self.assertIsNone(result) - def test_service_set_disable(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_disable(self, sm_mock): + sm_mock.return_value = False arglist = [ '--disable', self.service.host, @@ -288,13 +308,17 @@ class TestServiceSet(TestService): result = self.cmd.take_action(parsed_args) - self.service_mock.disable.assert_called_with( + self.sdk_client.disable_service.assert_called_with( + None, self.service.host, - self.service.binary + self.service.binary, + None ) self.assertIsNone(result) - def test_service_set_disable_with_reason(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_disable_with_reason(self, sm_mock): + sm_mock.return_value = False reason = 'earthquake' arglist = [ '--disable', @@ -312,14 +336,17 @@ class TestServiceSet(TestService): result = self.cmd.take_action(parsed_args) - self.service_mock.disable_log_reason.assert_called_with( + self.sdk_client.disable_service.assert_called_with( + None, self.service.host, self.service.binary, reason ) self.assertIsNone(result) - def test_service_set_only_with_disable_reason(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_only_with_disable_reason(self, sm_mock): + sm_mock.return_value = False reason = 'earthquake' arglist = [ '--disable-reason', reason, @@ -339,7 +366,9 @@ class TestServiceSet(TestService): self.assertEqual("Cannot specify option --disable-reason without " "--disable specified.", str(e)) - def test_service_set_enable_with_disable_reason(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_enable_with_disable_reason(self, sm_mock): + sm_mock.return_value = False reason = 'earthquake' arglist = [ '--enable', @@ -361,7 +390,9 @@ class TestServiceSet(TestService): self.assertEqual("Cannot specify option --disable-reason without " "--disable specified.", str(e)) - def test_service_set_state_up(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_state_up(self, sm_mock): + sm_mock.side_effect = [False, True] arglist = [ '--up', self.service.host, @@ -373,16 +404,20 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.11') result = self.cmd.take_action(parsed_args) - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, False) - self.assertNotCalled(self.service_mock.enable) - self.assertNotCalled(self.service_mock.disable) + self.sdk_client.update_service_forced_down.assert_called_once_with( + None, + self.service.host, + self.service.binary, + False + ) + self.assertNotCalled(self.sdk_client.enable_service) + self.assertNotCalled(self.sdk_client.disable_service) self.assertIsNone(result) - def test_service_set_state_down(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_state_down(self, sm_mock): + sm_mock.side_effect = [False, True] arglist = [ '--down', self.service.host, @@ -394,16 +429,20 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.11') result = self.cmd.take_action(parsed_args) - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, True) - self.assertNotCalled(self.service_mock.enable) - self.assertNotCalled(self.service_mock.disable) + self.sdk_client.update_service_forced_down.assert_called_once_with( + None, + self.service.host, + self.service.binary, + True + ) + self.assertNotCalled(self.sdk_client.enable_service) + self.assertNotCalled(self.sdk_client.disable_service) self.assertIsNone(result) - def test_service_set_enable_and_state_down(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_enable_and_state_down(self, sm_mock): + sm_mock.side_effect = [False, True] arglist = [ '--enable', '--down', @@ -417,16 +456,23 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.11') result = self.cmd.take_action(parsed_args) - self.service_mock.enable.assert_called_once_with( - self.service.host, self.service.binary) - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, True) + self.sdk_client.enable_service.assert_called_once_with( + None, + self.service.host, + self.service.binary + ) + self.sdk_client.update_service_forced_down.assert_called_once_with( + None, + self.service.host, + self.service.binary, + True + ) self.assertIsNone(result) - def test_service_set_enable_and_state_down_with_exception(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_enable_and_state_down_with_exception(self, sm_mock): + sm_mock.side_effect = [False, True] arglist = [ '--enable', '--down', @@ -441,18 +487,22 @@ class TestServiceSet(TestService): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.11') - with mock.patch.object(self.service_mock, 'enable', + with mock.patch.object(self.sdk_client, 'enable_service', side_effect=Exception()): self.assertRaises(exceptions.CommandError, self.cmd.take_action, parsed_args) - self.service_mock.force_down.assert_called_once_with( - self.service.host, self.service.binary, True) - - def test_service_set_2_53_disable_down(self): + self.sdk_client.update_service_forced_down.assert_called_once_with( + None, + self.service.host, + self.service.binary, + True + ) + + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_2_53_disable_down(self, sm_mock): # Tests disabling and forcing down a compute service with microversion # 2.53 which requires looking up the service by host and binary. + sm_mock.return_value = True arglist = [ '--disable', '--down', @@ -466,18 +516,27 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.53') service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' - self.service_mock.list.return_value = [mock.Mock(id=service_id)] + self.sdk_client.services.return_value = [mock.Mock(id=service_id)] result = self.cmd.take_action(parsed_args) - self.service_mock.disable.assert_called_once_with(service_id) - self.service_mock.force_down.assert_called_once_with(service_id, True) + self.sdk_client.disable_service.assert_called_once_with( + service_id, + self.service.host, + self.service.binary, + None + ) + self.sdk_client.update_service_forced_down.assert_called_once_with( + service_id, + self.service.host, + self.service.binary, + True) self.assertIsNone(result) - def test_service_set_2_53_disable_reason(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_2_53_disable_reason(self, sm_mock): # Tests disabling with reason a compute service with microversion # 2.53 which requires looking up the service by host and binary. + sm_mock.return_value = True reason = 'earthquake' arglist = [ '--disable', @@ -492,18 +551,22 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.53') service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' - self.service_mock.list.return_value = [mock.Mock(id=service_id)] + self.sdk_client.services.return_value = [mock.Mock(id=service_id)] result = self.cmd.take_action(parsed_args) - self.service_mock.disable_log_reason.assert_called_once_with( - service_id, reason) + self.sdk_client.disable_service.assert_called_once_with( + service_id, + self.service.host, + self.service.binary, + reason + ) self.assertIsNone(result) - def test_service_set_2_53_enable_up(self): + @mock.patch.object(sdk_utils, 'supports_microversion') + def test_service_set_2_53_enable_up(self, sm_mock): # Tests enabling and bringing up a compute service with microversion # 2.53 which requires looking up the service by host and binary. + sm_mock.return_value = True arglist = [ '--enable', '--up', @@ -517,30 +580,37 @@ class TestServiceSet(TestService): ('service', self.service.binary), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.53') service_id = '339478d0-0b95-4a94-be63-d5be05dfeb1c' - self.service_mock.list.return_value = [mock.Mock(id=service_id)] + self.sdk_client.services.return_value = [mock.Mock(id=service_id)] result = self.cmd.take_action(parsed_args) - self.service_mock.enable.assert_called_once_with(service_id) - self.service_mock.force_down.assert_called_once_with(service_id, False) + self.sdk_client.enable_service.assert_called_once_with( + service_id, + self.service.host, + self.service.binary + ) + self.sdk_client.update_service_forced_down.assert_called_once_with( + service_id, + self.service.host, + self.service.binary, + False + ) self.assertIsNone(result) def test_service_set_find_service_by_host_and_binary_no_results(self): # Tests that no compute services are found by host and binary. - self.service_mock.list.return_value = [] + self.sdk_client.services.return_value = [] ex = self.assertRaises(exceptions.CommandError, self.cmd._find_service_by_host_and_binary, - self.service_mock, 'fake-host', 'nova-compute') + self.sdk_client, 'fake-host', 'nova-compute') self.assertIn('Compute service for host "fake-host" and binary ' '"nova-compute" not found.', str(ex)) def test_service_set_find_service_by_host_and_binary_many_results(self): # Tests that more than one compute service is found by host and binary. - self.service_mock.list.return_value = [mock.Mock(), mock.Mock()] + self.sdk_client.services.return_value = [mock.Mock(), mock.Mock()] ex = self.assertRaises(exceptions.CommandError, self.cmd._find_service_by_host_and_binary, - self.service_mock, 'fake-host', 'nova-compute') + self.sdk_client, 'fake-host', 'nova-compute') self.assertIn('Multiple compute services found for host "fake-host" ' 'and binary "nova-compute". Unable to proceed.', str(ex)) diff --git a/openstackclient/tests/unit/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index ab77d719..2f839d16 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -18,6 +18,9 @@ from random import randint from unittest import mock import uuid +from openstack.network.v2 import local_ip as _local_ip +from openstack.network.v2 import local_ip_association as _local_ip_association + from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit import utils @@ -102,8 +105,9 @@ class FakeAddressGroup(object): 'name': 'address-group-name-' + uuid.uuid4().hex, 'description': 'address-group-description-' + uuid.uuid4().hex, 'id': 'address-group-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'addresses': ['10.0.0.1/32'], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -113,9 +117,6 @@ class FakeAddressGroup(object): info=copy.deepcopy(address_group_attrs), loaded=True) - # Set attributes with special mapping in OpenStack SDK. - address_group.project_id = address_group_attrs['tenant_id'] - return address_group @staticmethod @@ -174,9 +175,10 @@ class FakeAddressScope(object): address_scope_attrs = { 'name': 'address-scope-name-' + uuid.uuid4().hex, 'id': 'address-scope-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'shared': False, 'ip_version': 4, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -188,7 +190,6 @@ class FakeAddressScope(object): # Set attributes with special mapping in OpenStack SDK. address_scope.is_shared = address_scope_attrs['shared'] - address_scope.project_id = address_scope_attrs['tenant_id'] return address_scope @@ -239,7 +240,7 @@ class FakeAutoAllocatedTopology(object): auto_allocated_topology_attrs = { 'id': 'network-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, } auto_allocated_topology_attrs.update(attrs) @@ -248,10 +249,6 @@ class FakeAutoAllocatedTopology(object): info=copy.deepcopy(auto_allocated_topology_attrs), loaded=True) - auto_allocated_topology.project_id = auto_allocated_topology_attrs[ - 'tenant_id' - ] - return auto_allocated_topology @@ -322,17 +319,17 @@ class FakeIPAvailability(object): network_ip_attrs = { 'network_id': 'network-id-' + uuid.uuid4().hex, 'network_name': 'network-name-' + uuid.uuid4().hex, - 'tenant_id': '', + 'project_id': '', 'subnet_ip_availability': [], 'total_ips': 254, 'used_ips': 6, + 'location': 'MUNCHMUNCHMUNCH', } network_ip_attrs.update(attrs) network_ip_availability = fakes.FakeResource( info=copy.deepcopy(network_ip_attrs), loaded=True) - network_ip_availability.project_id = network_ip_attrs['tenant_id'] return network_ip_availability @@ -409,7 +406,7 @@ class FakeNetwork(object): 'description': 'network-description-' + uuid.uuid4().hex, 'dns_domain': 'example.org.', 'mtu': '1350', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'admin_state_up': True, 'shared': False, 'subnets': ['a', 'b'], @@ -425,6 +422,7 @@ class FakeNetwork(object): 'ipv4_address_scope': 'ipv4' + uuid.uuid4().hex, 'ipv6_address_scope': 'ipv6' + uuid.uuid4().hex, 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -434,7 +432,6 @@ class FakeNetwork(object): loaded=True) # Set attributes with special mapping in OpenStack SDK. - network.project_id = network_attrs['tenant_id'] network.is_router_external = network_attrs['router:external'] network.is_admin_state_up = network_attrs['admin_state_up'] network.is_port_security_enabled = \ @@ -513,7 +510,8 @@ class FakeNetworkFlavor(object): 'id': 'network-flavor-id-' + fake_uuid, 'name': 'network-flavor-name-' + fake_uuid, 'service_type': 'vpn', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -524,7 +522,6 @@ class FakeNetworkFlavor(object): loaded=True ) - network_flavor.project_id = network_flavor_attrs['tenant_id'] network_flavor.is_enabled = network_flavor_attrs['enabled'] return network_flavor @@ -579,6 +576,7 @@ class FakeNetworkSegment(object): 'network_type': 'vlan', 'physical_network': 'physical-network-name-' + fake_uuid, 'segmentation_id': 1024, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -639,6 +637,7 @@ class FakeNetworkSegmentRange(object): 'used': {104: '3312e4ba67864b2eb53f3f41432f8efc', 106: '3312e4ba67864b2eb53f3f41432f8efc'}, 'available': [100, 101, 102, 103, 105], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -712,11 +711,12 @@ class FakePort(object): 'port_security_enabled': True, 'security_group_ids': [], 'status': 'ACTIVE', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'qos_network_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], 'propagate_uplink_status': False, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -733,11 +733,6 @@ class FakePort(object): port.binding_vnic_type = port_attrs['binding:vnic_type'] port.is_admin_state_up = port_attrs['admin_state_up'] port.is_port_security_enabled = port_attrs['port_security_enabled'] - port.project_id = port_attrs['tenant_id'] - port.security_group_ids = port_attrs['security_group_ids'] - port.qos_policy_id = port_attrs['qos_policy_id'] - port.propagate_uplink_status = port_attrs[ - 'propagate_uplink_status'] return port @@ -802,6 +797,7 @@ class FakeNetworkAgent(object): 'admin_state_up': True, 'binary': 'binary-' + uuid.uuid4().hex, 'configurations': {'subnet': 2, 'networks': 1}, + 'location': 'MUNCHMUNCHMUNCH', } agent_attrs.update(attrs) agent = fakes.FakeResource(info=copy.deepcopy(agent_attrs), @@ -858,7 +854,7 @@ class FakeNetworkRBAC(object): A dictionary with all attributes :return: A FakeResource object, with id, action, target_tenant, - tenant_id, type + project_id, type """ attrs = attrs or {} @@ -869,13 +865,13 @@ class FakeNetworkRBAC(object): 'object_id': 'object-id-' + uuid.uuid4().hex, 'action': 'access_as_shared', 'target_tenant': 'target-tenant-' + uuid.uuid4().hex, - 'tenant_id': 'tenant-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } rbac_attrs.update(attrs) rbac = fakes.FakeResource(info=copy.deepcopy(rbac_attrs), loaded=True) # Set attributes with special mapping in OpenStack SDK. - rbac.project_id = rbac_attrs['tenant_id'] rbac.target_project_id = rbac_attrs['target_tenant'] return rbac @@ -928,10 +924,11 @@ class FakeNetworkFlavorProfile(object): flavor_profile_attrs = { 'id': 'flavor-profile-id' + uuid.uuid4().hex, 'description': 'flavor-profile-description-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'driver': 'driver-' + uuid.uuid4().hex, 'metainfo': 'metainfo-' + uuid.uuid4().hex, - 'enabled': True + 'enabled': True, + 'location': 'MUNCHMUNCHMUNCH', } flavor_profile_attrs.update(attrs) @@ -940,7 +937,6 @@ class FakeNetworkFlavorProfile(object): info=copy.deepcopy(flavor_profile_attrs), loaded=True) - flavor_profile.project_id = flavor_profile_attrs['tenant_id'] flavor_profile.is_enabled = flavor_profile_attrs['enabled'] return flavor_profile @@ -986,10 +982,11 @@ class FakeNetworkQosPolicy(object): 'name': 'qos-policy-name-' + uuid.uuid4().hex, 'id': qos_id, 'is_default': False, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'shared': False, 'description': 'qos-policy-description-' + uuid.uuid4().hex, 'rules': rules, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1001,7 +998,6 @@ class FakeNetworkQosPolicy(object): # Set attributes with special mapping in OpenStack SDK. qos_policy.is_shared = qos_policy_attrs['shared'] - qos_policy.project_id = qos_policy_attrs['tenant_id'] return qos_policy @@ -1062,17 +1058,15 @@ class FakeNetworkSecGroup(object): security_group_attrs = { 'name': 'security-group-name-' + uuid.uuid4().hex, 'id': sg_id, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, - 'description': 'security-group-description-' + uuid.uuid4().hex + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'description': 'security-group-description-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } security_group = fakes.FakeResource( info=copy.deepcopy(security_group_attrs), loaded=True) - # Set attributes with special mapping in OpenStack SDK. - security_group.project_id = security_group_attrs['tenant_id'] - return security_group @@ -1095,8 +1089,9 @@ class FakeNetworkQosRule(object): qos_rule_attrs = { 'id': 'qos-rule-id-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'type': type, + 'location': 'MUNCHMUNCHMUNCH', } if type == RULE_TYPE_BANDWIDTH_LIMIT: qos_rule_attrs['max_kbps'] = randint(1, 10000) @@ -1114,9 +1109,6 @@ class FakeNetworkQosRule(object): qos_rule = fakes.FakeResource(info=copy.deepcopy(qos_rule_attrs), loaded=True) - # Set attributes with special mapping in OpenStack SDK. - qos_rule.project_id = qos_rule['tenant_id'] - return qos_rule @staticmethod @@ -1172,6 +1164,7 @@ class FakeNetworkQosRuleType(object): # Set default attributes. qos_rule_type_attrs = { 'type': 'rule-type-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1211,7 +1204,7 @@ class FakeRouter(object): A dictionary with all attributes :return: A FakeResource object, with id, name, admin_state_up, - status, tenant_id + status, project_id """ attrs = attrs or {} @@ -1224,12 +1217,13 @@ class FakeRouter(object): 'description': 'router-description-' + uuid.uuid4().hex, 'distributed': False, 'ha': False, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'routes': [], 'external_gateway_info': {}, 'availability_zone_hints': [], 'availability_zones': [], 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1239,7 +1233,6 @@ class FakeRouter(object): loaded=True) # Set attributes with special mapping in OpenStack SDK. - router.project_id = router_attrs['tenant_id'] router.is_admin_state_up = router_attrs['admin_state_up'] router.is_distributed = router_attrs['distributed'] router.is_ha = router_attrs['ha'] @@ -1305,7 +1298,8 @@ class FakeSecurityGroup(object): 'stateful': True, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], - 'tags': [] + 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1315,9 +1309,6 @@ class FakeSecurityGroup(object): info=copy.deepcopy(security_group_attrs), loaded=True) - # Set attributes with special mapping in OpenStack SDK. - security_group.project_id = security_group_attrs['project_id'] - return security_group @staticmethod @@ -1386,7 +1377,8 @@ class FakeSecurityGroupRule(object): 'remote_address_group_id': None, 'remote_ip_prefix': '0.0.0.0/0', 'security_group_id': 'security-group-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1396,9 +1388,6 @@ class FakeSecurityGroupRule(object): info=copy.deepcopy(security_group_rule_attrs), loaded=True) - # Set attributes with special mapping in OpenStack SDK. - security_group_rule.project_id = security_group_rule_attrs['tenant_id'] - return security_group_rule @staticmethod @@ -1461,7 +1450,7 @@ class FakeSubnet(object): 'name': 'subnet-name-' + uuid.uuid4().hex, 'network_id': 'network-id-' + uuid.uuid4().hex, 'cidr': '10.10.10.0/24', - 'tenant_id': project_id, + 'project_id': project_id, 'enable_dhcp': True, 'dns_nameservers': [], 'allocation_pools': [], @@ -1475,6 +1464,7 @@ class FakeSubnet(object): 'subnetpool_id': None, 'description': 'subnet-description-' + uuid.uuid4().hex, 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1486,7 +1476,6 @@ class FakeSubnet(object): # Set attributes with special mappings in OpenStack SDK. subnet.is_dhcp_enabled = subnet_attrs['enable_dhcp'] subnet.subnet_pool_id = subnet_attrs['subnetpool_id'] - subnet.project_id = subnet_attrs['tenant_id'] return subnet @@ -1552,10 +1541,11 @@ class FakeFloatingIP(object): 'floating_network_id': 'network-id-' + uuid.uuid4().hex, 'router_id': 'router-id-' + uuid.uuid4().hex, 'port_id': 'port-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'description': 'floating-ip-description-' + uuid.uuid4().hex, 'qos_policy_id': 'qos-policy-id-' + uuid.uuid4().hex, 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1566,9 +1556,6 @@ class FakeFloatingIP(object): loaded=True ) - # Set attributes with special mappings in OpenStack SDK. - floating_ip.project_id = floating_ip_attrs['tenant_id'] - return floating_ip @staticmethod @@ -1619,8 +1606,9 @@ class FakeNetworkMeter(object): 'id': 'meter-id-' + uuid.uuid4().hex, 'name': 'meter-name-' + uuid.uuid4().hex, 'description': 'meter-description-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, - 'shared': False + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'shared': False, + 'location': 'MUNCHMUNCHMUNCH', } meter_attrs.update(attrs) @@ -1629,8 +1617,6 @@ class FakeNetworkMeter(object): info=copy.deepcopy(meter_attrs), loaded=True) - meter.project_id = meter_attrs['tenant_id'] - return meter @staticmethod @@ -1668,7 +1654,8 @@ class FakeNetworkMeterRule(object): 'remote_ip_prefix': '10.0.0.0/24', 'source_ip_prefix': '8.8.8.8/32', 'destination_ip_prefix': '10.0.0.0/24', - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'location': 'MUNCHMUNCHMUNCH', } meter_rule_attrs.update(attrs) @@ -1677,8 +1664,6 @@ class FakeNetworkMeterRule(object): info=copy.deepcopy(meter_rule_attrs), loaded=True) - meter_rule.project_id = meter_rule_attrs['tenant_id'] - return meter_rule @staticmethod @@ -1721,7 +1706,7 @@ class FakeSubnetPool(object): 'prefixes': ['10.0.0.0/24', '10.1.0.0/24'], 'default_prefixlen': '8', 'address_scope_id': 'address-scope-id-' + uuid.uuid4().hex, - 'tenant_id': 'project-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, 'is_default': False, 'shared': False, 'max_prefixlen': '32', @@ -1730,6 +1715,7 @@ class FakeSubnetPool(object): 'ip_version': '4', 'description': 'subnet-pool-description-' + uuid.uuid4().hex, 'tags': [], + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1746,7 +1732,6 @@ class FakeSubnetPool(object): subnet_pool.is_shared = subnet_pool_attrs['shared'] subnet_pool.maximum_prefix_length = subnet_pool_attrs['max_prefixlen'] subnet_pool.minimum_prefix_length = subnet_pool_attrs['min_prefixlen'] - subnet_pool.project_id = subnet_pool_attrs['tenant_id'] return subnet_pool @@ -1801,6 +1786,7 @@ class FakeNetworkServiceProvider(object): 'name': 'provider-name-' + uuid.uuid4().hex, 'service_type': 'service-type-' + uuid.uuid4().hex, 'default': False, + 'location': 'MUNCHMUNCHMUNCH', } service_provider.update(attrs) @@ -1923,6 +1909,7 @@ class FakeFloatingIPPortForwarding(object): 'external_port': randint(1, 65535), 'protocol': 'tcp', 'description': 'some description', + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -1998,6 +1985,7 @@ class FakeL3ConntrackHelper(object): 'helper': 'tftp', 'protocol': 'tcp', 'port': randint(1, 65535), + 'location': 'MUNCHMUNCHMUNCH', } # Overwrite default attributes. @@ -2048,3 +2036,138 @@ class FakeL3ConntrackHelper(object): ) return mock.Mock(side_effect=ct_helpers) + + +def create_one_local_ip(attrs=None): + """Create a fake local ip. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with name, id, etc. + """ + attrs = attrs or {} + + # Set default attributes. + local_ip_attrs = { + 'created_at': '2021-11-29T10:10:23.000000', + 'name': 'local-ip-name-' + uuid.uuid4().hex, + 'description': 'local-ip-description-' + uuid.uuid4().hex, + 'id': 'local-ip-id-' + uuid.uuid4().hex, + 'project_id': 'project-id-' + uuid.uuid4().hex, + 'local_port_id': 'local_port_id-' + uuid.uuid4().hex, + 'network_id': 'network_id-' + uuid.uuid4().hex, + 'local_ip_address': '10.0.0.1', + 'ip_mode': 'translate', + 'revision_number': 'local-ip-revision-number-' + uuid.uuid4().hex, + 'updated_at': '2021-11-29T10:10:25.000000', + } + + # Overwrite default attributes. + local_ip_attrs.update(attrs) + + local_ip = _local_ip.LocalIP(**local_ip_attrs) + + return local_ip + + +def create_local_ips(attrs=None, count=2): + """Create multiple fake local ips. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of local ips to fake + :return: + A list of FakeResource objects faking the local ips + """ + local_ips = [] + for i in range(0, count): + local_ips.append(create_one_local_ip(attrs)) + + return local_ips + + +def get_local_ips(local_ips=None, count=2): + """Get an iterable Mock object with a list of faked local ips. + + If local ip list is provided, then initialize the Mock object + with the list. Otherwise create one. + + :param List local_ips: + A list of FakeResource objects faking local ips + :param int count: + The number of local ips to fake + :return: + An iterable Mock object with side_effect set to a list of faked + local ips + """ + if local_ips is None: + local_ips = create_local_ips(count) + return mock.Mock(side_effect=local_ips) + + +def create_one_local_ip_association(attrs=None): + """Create a fake local ip association. + + :param Dictionary attrs: + A dictionary with all attributes + :return: + A FakeResource object with local_ip_id, local_ip_address, etc. + """ + attrs = attrs or {} + + # Set default attributes. + local_ip_association_attrs = { + 'local_ip_id': 'local-ip-id-' + uuid.uuid4().hex, + 'local_ip_address': '172.24.4.228', + 'fixed_port_id': 'fixed-port-id-' + uuid.uuid4().hex, + 'fixed_ip': '10.0.0.5', + 'host': 'host-' + uuid.uuid4().hex, + } + + # Overwrite default attributes. + local_ip_association_attrs.update(attrs) + + local_ip_association = ( + _local_ip_association.LocalIPAssociation( + **local_ip_association_attrs)) + + return local_ip_association + + +def create_local_ip_associations(attrs=None, count=2): + """Create multiple fake local ip associations. + + :param Dictionary attrs: + A dictionary with all attributes + :param int count: + The number of address groups to fake + :return: + A list of FakeResource objects faking the local ip associations + """ + local_ip_associations = [] + for i in range(0, count): + local_ip_associations.append(create_one_local_ip_association(attrs)) + + return local_ip_associations + + +def get_local_ip_associations(local_ip_associations=None, count=2): + """Get a list of faked local ip associations + + If local ip association list is provided, then initialize + the Mock object with the list. Otherwise create one. + + :param List local_ip_associations: + A list of FakeResource objects faking local ip associations + :param int count: + The number of local ip associations to fake + :return: + An iterable Mock object with side_effect set to a list of faked + local ip associations + """ + if local_ip_associations is None: + local_ip_associations = create_local_ip_associations(count) + + return mock.Mock(side_effect=local_ip_associations) diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index a5ee83cb..703ab74d 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -43,7 +43,7 @@ class TestCreateAddressGroup(TestAddressGroup): new_address_group = ( network_fakes.FakeAddressGroup.create_one_address_group( attrs={ - 'tenant_id': project.id, + 'project_id': project.id, } )) columns = ( @@ -122,7 +122,7 @@ class TestCreateAddressGroup(TestAddressGroup): self.network.create_address_group.assert_called_once_with(**{ 'addresses': ['10.0.0.1/32'], - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'name': self.new_address_group.name, 'description': self.new_address_group.description, }) diff --git a/openstackclient/tests/unit/network/v2/test_address_scope.py b/openstackclient/tests/unit/network/v2/test_address_scope.py index 17f13e83..7e7c4215 100644 --- a/openstackclient/tests/unit/network/v2/test_address_scope.py +++ b/openstackclient/tests/unit/network/v2/test_address_scope.py @@ -43,7 +43,7 @@ class TestCreateAddressScope(TestAddressScope): new_address_scope = ( network_fakes.FakeAddressScope.create_one_address_scope( attrs={ - 'tenant_id': project.id, + 'project_id': project.id, } )) columns = ( @@ -122,7 +122,7 @@ class TestCreateAddressScope(TestAddressScope): self.network.create_address_scope.assert_called_once_with(**{ 'ip_version': self.new_address_scope.ip_version, 'shared': True, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'name': self.new_address_scope.name, }) self.assertEqual(self.columns, columns) @@ -318,7 +318,7 @@ class TestListAddressScope(TestAddressScope): columns, data = self.cmd.take_action(parsed_args) self.network.address_scopes.assert_called_once_with( - **{'tenant_id': project.id, 'project_id': project.id}) + **{'project_id': project.id}) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -335,7 +335,7 @@ class TestListAddressScope(TestAddressScope): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.address_scopes.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py index dbcd5c97..5b5c83a5 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -179,7 +179,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.network.create_ip.assert_called_once_with(**{ 'floating_network_id': self.floating_ip.floating_network_id, - 'tenant_id': project.id, + 'project_id': project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -205,7 +205,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.network.create_ip.assert_called_once_with(**{ 'floating_network_id': self.floating_ip.floating_network_id, - 'tenant_id': project.id, + 'project_id': project.id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, data) @@ -414,7 +414,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.fixed_ip_address, ip.port_id, ip.floating_network_id, - ip.tenant_id, + ip.project_id, )) data_long.append(( ip.id, @@ -422,7 +422,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): ip.fixed_ip_address, ip.port_id, ip.floating_network_id, - ip.tenant_id, + ip.project_id, ip.router_id, ip.status, ip.description, @@ -563,8 +563,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, - 'project_id': project.id, } + filters = {'project_id': project.id} self.network.ips.assert_called_once_with(**filters) @@ -584,8 +583,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, - 'project_id': project.id, } + filters = {'project_id': project.id} self.network.ips.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py index 1028c18a..7b9e3aa6 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_port_forwarding.py @@ -39,7 +39,6 @@ class TestFloatingIPPortForwarding(network_fakes.TestNetworkV2): class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): def setUp(self): - project_id = '' super(TestCreateFloatingIPPortForwarding, self).setUp() self.new_port_forwarding = ( network_fakes.FakeFloatingIPPortForwarding. @@ -69,7 +68,6 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): 'internal_ip_address', 'internal_port', 'internal_port_id', - 'project_id', 'protocol' ) @@ -81,7 +79,6 @@ class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): self.new_port_forwarding.internal_ip_address, self.new_port_forwarding.internal_port, self.new_port_forwarding.internal_port_id, - project_id, self.new_port_forwarding.protocol, ) @@ -446,12 +443,10 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): 'internal_ip_address', 'internal_port', 'internal_port_id', - 'project_id', 'protocol', ) def setUp(self): - project_id = '' super(TestShowFloatingIPPortForwarding, self).setUp() self._port_forwarding = ( network_fakes.FakeFloatingIPPortForwarding. @@ -469,7 +464,6 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): self._port_forwarding.internal_ip_address, self._port_forwarding.internal_port, self._port_forwarding.internal_port_id, - project_id, self._port_forwarding.protocol, ) self.network.find_floating_ip_port_forwarding = mock.Mock( diff --git a/openstackclient/tests/unit/network/v2/test_ip_availability.py b/openstackclient/tests/unit/network/v2/test_ip_availability.py index a722a023..880cf581 100644 --- a/openstackclient/tests/unit/network/v2/test_ip_availability.py +++ b/openstackclient/tests/unit/network/v2/test_ip_availability.py @@ -106,8 +106,7 @@ class TestListIPAvailability(TestIPAvailability): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': self.project.id, - 'project_id': self.project.id, + filters = {'project_id': self.project.id, 'ip_version': 4} self.network.network_ip_availabilities.assert_called_once_with( @@ -134,7 +133,7 @@ class TestShowIPAvailability(TestIPAvailability): data = ( _ip_availability.network_id, _ip_availability.network_name, - _ip_availability.tenant_id, + _ip_availability.project_id, format_columns.ListDictColumn( _ip_availability.subnet_ip_availability), _ip_availability.total_ips, diff --git a/openstackclient/tests/unit/network/v2/test_local_ip.py b/openstackclient/tests/unit/network/v2/test_local_ip.py new file mode 100644 index 00000000..17e8dcd1 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_local_ip.py @@ -0,0 +1,480 @@ +# Copyright 2021 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock +from unittest.mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import local_ip +from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes +from openstackclient.tests.unit import utils as tests_utils + + +class TestLocalIP(network_fakes.TestNetworkV2): + + def setUp(self): + super().setUp() + + # Get a shortcut to the network client + self.network = self.app.client_manager.network + # Get a shortcut to the ProjectManager Mock + self.projects_mock = self.app.client_manager.identity.projects + # Get a shortcut to the DomainManager Mock + self.domains_mock = self.app.client_manager.identity.domains + + +class TestCreateLocalIP(TestLocalIP): + project = identity_fakes_v3.FakeProject.create_one_project() + domain = identity_fakes_v3.FakeDomain.create_one_domain() + local_ip_network = network_fakes.FakeNetwork.create_one_network() + port = network_fakes.FakePort.create_one_port() + # The new local ip created. + new_local_ip = network_fakes.create_one_local_ip( + attrs={'project_id': project.id, + 'network_id': local_ip_network.id, + 'local_port_id': port.id}) + + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'project_id', + 'local_port_id', + 'network_id', + 'local_ip_address', + 'ip_mode', + 'revision_number', + 'updated_at', + ) + data = ( + new_local_ip.created_at, + new_local_ip.description, + new_local_ip.id, + new_local_ip.name, + new_local_ip.project_id, + new_local_ip.local_port_id, + new_local_ip.network_id, + new_local_ip.local_ip_address, + new_local_ip.ip_mode, + new_local_ip.revision_number, + new_local_ip.updated_at, + ) + + def setUp(self): + super().setUp() + self.network.create_local_ip = mock.Mock( + return_value=self.new_local_ip) + self.network.find_network = mock.Mock( + return_value=self.local_ip_network) + self.network.find_port = mock.Mock( + return_value=self.port) + + # Get the command object to test + self.cmd = local_ip.CreateLocalIP(self.app, self.namespace) + + self.projects_mock.get.return_value = self.project + self.domains_mock.get.return_value = self.domain + + def test_create_no_options(self): + parsed_args = self.check_parser(self.cmd, [], []) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_local_ip.assert_called_once_with(**{}) + self.assertEqual(set(self.columns), set(columns)) + self.assertItemsEqual(self.data, data) + + def test_create_all_options(self): + arglist = [ + '--project-domain', self.domain.name, + '--description', self.new_local_ip.description, + '--name', self.new_local_ip.name, + '--network', self.new_local_ip.network_id, + '--local-port', self.new_local_ip.local_port_id, + '--local-ip-address', '10.0.0.1', + '--ip-mode', self.new_local_ip.ip_mode, + ] + verifylist = [ + ('project_domain', self.domain.name), + ('description', self.new_local_ip.description), + ('name', self.new_local_ip.name), + ('network', self.new_local_ip.network_id), + ('local_port', self.new_local_ip.local_port_id), + ('local_ip_address', '10.0.0.1'), + ('ip_mode', self.new_local_ip.ip_mode), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_local_ip.assert_called_once_with(**{ + 'name': self.new_local_ip.name, + 'description': self.new_local_ip.description, + 'network_id': self.new_local_ip.network_id, + 'local_port_id': self.new_local_ip.local_port_id, + 'local_ip_address': '10.0.0.1', + 'ip_mode': self.new_local_ip.ip_mode, + }) + self.assertEqual(set(self.columns), set(columns)) + self.assertItemsEqual(self.data, data) + + +class TestDeleteLocalIP(TestLocalIP): + # The local ip to delete. + _local_ips = network_fakes.create_local_ips(count=2) + + def setUp(self): + super().setUp() + self.network.delete_local_ip = mock.Mock(return_value=None) + self.network.find_local_ip = network_fakes.get_local_ips( + local_ips=self._local_ips) + + # Get the command object to test + self.cmd = local_ip.DeleteLocalIP(self.app, self.namespace) + + def test_local_ip_delete(self): + arglist = [ + self._local_ips[0].name, + ] + verifylist = [ + ('local_ip', [self._local_ips[0].name]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.network.find_local_ip.assert_called_once_with( + self._local_ips[0].name, ignore_missing=False) + self.network.delete_local_ip.assert_called_once_with( + self._local_ips[0]) + self.assertIsNone(result) + + def test_multi_local_ips_delete(self): + arglist = [] + + for a in self._local_ips: + arglist.append(a.name) + verifylist = [ + ('local_ip', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._local_ips: + calls.append(call(a)) + self.network.delete_local_ip.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_local_ips_delete_with_exception(self): + arglist = [ + self._local_ips[0].name, + 'unexist_local_ip', + ] + verifylist = [ + ('local_ip', + [self._local_ips[0].name, 'unexist_local_ip']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + find_mock_result = [self._local_ips[0], exceptions.CommandError] + self.network.find_local_ip = ( + mock.Mock(side_effect=find_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('1 of 2 local IPs failed to delete.', str(e)) + + self.network.find_local_ip.assert_any_call( + self._local_ips[0].name, ignore_missing=False) + self.network.find_local_ip.assert_any_call( + 'unexist_local_ip', ignore_missing=False) + self.network.delete_local_ip.assert_called_once_with( + self._local_ips[0] + ) + + +class TestListLocalIP(TestLocalIP): + # The local ip to list up. + local_ips = ( + network_fakes.create_local_ips(count=3)) + fake_network = network_fakes.FakeNetwork.create_one_network( + {'id': 'fake_network_id'} + ) + + columns = ( + 'ID', + 'Name', + 'Description', + 'Project', + 'Local Port ID', + 'Network', + 'Local IP address', + 'IP mode', + ) + data = [] + for lip in local_ips: + data.append(( + lip.id, + lip.name, + lip.description, + lip.project_id, + lip.local_port_id, + lip.network_id, + lip.local_ip_address, + lip.ip_mode, + )) + + def setUp(self): + super().setUp() + self.network.local_ips = mock.Mock( + return_value=self.local_ips) + self.network.find_network = mock.Mock( + return_value=self.fake_network + ) + + # Get the command object to test + self.cmd = local_ip.ListLocalIP(self.app, self.namespace) + + def test_local_ip_list(self): + arglist = [] + verifylist = [] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with(**{}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_local_ip_list_name(self): + arglist = [ + '--name', self.local_ips[0].name, + ] + verifylist = [ + ('name', self.local_ips[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with( + **{'name': self.local_ips[0].name}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_local_ip_list_project(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with( + **{'project_id': project.id}) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_local_ip_project_domain(self): + project = identity_fakes_v3.FakeProject.create_one_project() + self.projects_mock.get.return_value = project + arglist = [ + '--project', project.id, + '--project-domain', project.domain_id, + ] + verifylist = [ + ('project', project.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'project_id': project.id} + + self.network.local_ips.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_local_ip_list_network(self): + arglist = [ + '--network', 'fake_network_id', + ] + verifylist = [ + ('network', 'fake_network_id'), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with(**{ + 'network_id': 'fake_network_id', + }) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_local_ip_list_local_ip_address(self): + arglist = [ + '--local-ip-address', self.local_ips[0].local_ip_address, + ] + verifylist = [ + ('local_ip_address', self.local_ips[0].local_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with(**{ + 'local_ip_address': self.local_ips[0].local_ip_address, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + def test_local_ip_list_ip_mode(self): + arglist = [ + '--ip-mode', self.local_ips[0].ip_mode, + ] + verifylist = [ + ('ip_mode', self.local_ips[0].ip_mode), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ips.assert_called_once_with(**{ + 'ip_mode': self.local_ips[0].ip_mode, + }) + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, list(data)) + + +class TestSetLocalIP(TestLocalIP): + # The local ip to set. + _local_ip = network_fakes.create_one_local_ip() + + def setUp(self): + super().setUp() + self.network.update_local_ip = mock.Mock(return_value=None) + self.network.find_local_ip = mock.Mock( + return_value=self._local_ip) + + # Get the command object to test + self.cmd = local_ip.SetLocalIP(self.app, self.namespace) + + def test_set_nothing(self): + arglist = [self._local_ip.name, ] + verifylist = [ + ('local_ip', self._local_ip.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + + self.network.update_local_ip.assert_not_called() + self.assertIsNone(result) + + def test_set_name_and_description(self): + arglist = [ + '--name', 'new_local_ip_name', + '--description', 'new_local_ip_description', + self._local_ip.name, + ] + verifylist = [ + ('name', 'new_local_ip_name'), + ('description', 'new_local_ip_description'), + ('local_ip', self._local_ip.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + result = self.cmd.take_action(parsed_args) + attrs = { + 'name': "new_local_ip_name", + 'description': 'new_local_ip_description', + } + self.network.update_local_ip.assert_called_with( + self._local_ip, **attrs) + self.assertIsNone(result) + + +class TestShowLocalIP(TestLocalIP): + # The local ip to show. + _local_ip = network_fakes.create_one_local_ip() + columns = ( + 'created_at', + 'description', + 'id', + 'name', + 'project_id', + 'local_port_id', + 'network_id', + 'local_ip_address', + 'ip_mode', + 'revision_number', + 'updated_at', + ) + data = ( + _local_ip.created_at, + _local_ip.description, + _local_ip.id, + _local_ip.name, + _local_ip.project_id, + _local_ip.local_port_id, + _local_ip.network_id, + _local_ip.local_ip_address, + _local_ip.ip_mode, + _local_ip.revision_number, + _local_ip.updated_at, + ) + + def setUp(self): + super().setUp() + self.network.find_local_ip = mock.Mock( + return_value=self._local_ip) + + # Get the command object to test + self.cmd = local_ip.ShowLocalIP(self.app, self.namespace) + + def test_show_no_options(self): + arglist = [] + verifylist = [] + + # Missing required args should bail here + self.assertRaises(tests_utils.ParserException, self.check_parser, + self.cmd, arglist, verifylist) + + def test_show_all_options(self): + arglist = [ + self._local_ip.name, + ] + verifylist = [ + ('local_ip', self._local_ip.name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.find_local_ip.assert_called_once_with( + self._local_ip.name, ignore_missing=False) + self.assertEqual(set(self.columns), set(columns)) + self.assertItemsEqual(self.data, list(data)) diff --git a/openstackclient/tests/unit/network/v2/test_local_ip_association.py b/openstackclient/tests/unit/network/v2/test_local_ip_association.py new file mode 100644 index 00000000..97759302 --- /dev/null +++ b/openstackclient/tests/unit/network/v2/test_local_ip_association.py @@ -0,0 +1,328 @@ +# Copyright 2021 Huawei, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +# + +from unittest import mock +from unittest.mock import call + +from osc_lib import exceptions + +from openstackclient.network.v2 import local_ip_association +from openstackclient.tests.unit.identity.v2_0 import fakes as identity_fakes_v2 +from openstackclient.tests.unit.network.v2 import fakes as network_fakes + + +class TestLocalIPAssociation(network_fakes.TestNetworkV2): + + def setUp(self): + super().setUp() + self.network = self.app.client_manager.network + self.local_ip = network_fakes.create_one_local_ip() + self.fixed_port = network_fakes.FakePort.create_one_port() + self.project = identity_fakes_v2.FakeProject.create_one_project() + self.network.find_port = mock.Mock(return_value=self.fixed_port) + + +class TestCreateLocalIPAssociation(TestLocalIPAssociation): + + def setUp(self): + super().setUp() + self.new_local_ip_association = ( + network_fakes.create_one_local_ip_association( + attrs={ + 'fixed_port_id': self.fixed_port.id, + 'local_ip_id': self.local_ip.id, + } + ) + ) + self.network.create_local_ip_association = mock.Mock( + return_value=self.new_local_ip_association) + + self.network.find_local_ip = mock.Mock( + return_value=self.local_ip + ) + + # Get the command object to test + self.cmd = local_ip_association.CreateLocalIPAssociation( + self.app, self.namespace) + + self.columns = ( + 'local_ip_address', + 'fixed_port_id', + 'fixed_ip', + 'host', + ) + + self.data = ( + self.new_local_ip_association.local_ip_address, + self.new_local_ip_association.fixed_port_id, + self.new_local_ip_association.fixed_ip, + self.new_local_ip_association.host, + ) + + def test_create_no_options(self): + arglist = [ + self.new_local_ip_association.local_ip_id, + self.new_local_ip_association.fixed_port_id, + ] + verifylist = [ + ('local_ip', self.new_local_ip_association.local_ip_id), + ('fixed_port', self.new_local_ip_association.fixed_port_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_local_ip_association.\ + assert_called_once_with( + self.new_local_ip_association.local_ip_id, + **{ + 'fixed_port_id': + self.new_local_ip_association.fixed_port_id, + }) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) + + def test_create_all_options(self): + arglist = [ + self.new_local_ip_association.local_ip_id, + self.new_local_ip_association.fixed_port_id, + '--fixed-ip', self.new_local_ip_association.fixed_ip, + ] + verifylist = [ + ('local_ip', self.new_local_ip_association.local_ip_id), + ('fixed_port', self.new_local_ip_association.fixed_port_id), + ('fixed_ip', self.new_local_ip_association.fixed_ip), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.create_local_ip_association.\ + assert_called_once_with( + self.new_local_ip_association.local_ip_id, + **{ + 'fixed_port_id': + self.new_local_ip_association.fixed_port_id, + 'fixed_ip': + self.new_local_ip_association.fixed_ip, + }) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(data)) + + +class TestDeleteLocalIPAssociation(TestLocalIPAssociation): + + def setUp(self): + super().setUp() + self._local_ip_association = ( + network_fakes.create_local_ip_associations( + count=2, attrs={ + 'local_ip_id': self.local_ip.id, + } + ) + ) + self.network.delete_local_ip_association = mock.Mock( + return_value=None + ) + + self.network.find_local_ip = mock.Mock( + return_value=self.local_ip + ) + # Get the command object to test + self.cmd = local_ip_association.DeleteLocalIPAssociation( + self.app, self.namespace) + + def test_local_ip_association_delete(self): + arglist = [ + self.local_ip.id, + self._local_ip_association[0].fixed_port_id, + ] + verifylist = [ + ('local_ip', self.local_ip.id), + ('fixed_port_id', [self._local_ip_association[0].fixed_port_id]), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.network.delete_local_ip_association.\ + assert_called_once_with( + self.local_ip.id, + self._local_ip_association[0].fixed_port_id, + ignore_missing=False + ) + + self.assertIsNone(result) + + def test_multi_local_ip_associations_delete(self): + arglist = [] + fixed_port_id = [] + + arglist.append(str(self.local_ip)) + + for a in self._local_ip_association: + arglist.append(a.fixed_port_id) + fixed_port_id.append(a.fixed_port_id) + + verifylist = [ + ('local_ip', str(self.local_ip)), + ('fixed_port_id', fixed_port_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + calls = [] + for a in self._local_ip_association: + calls.append(call(a.local_ip_id, a.fixed_port_id, + ignore_missing=False)) + + self.network.delete_local_ip_association.assert_has_calls(calls) + self.assertIsNone(result) + + def test_multi_local_ip_association_delete_with_exception(self): + arglist = [ + self.local_ip.id, + self._local_ip_association[0].fixed_port_id, + 'unexist_fixed_port_id', + ] + verifylist = [ + ('local_ip', self.local_ip.id), + ('fixed_port_id', + [self._local_ip_association[0].fixed_port_id, + 'unexist_fixed_port_id']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + delete_mock_result = [None, exceptions.CommandError] + + self.network.delete_local_ip_association = ( + mock.MagicMock(side_effect=delete_mock_result) + ) + + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual( + '1 of 2 Local IP Associations failed to delete.', + str(e) + ) + + self.network.delete_local_ip_association.\ + assert_any_call( + self.local_ip.id, + 'unexist_fixed_port_id', + ignore_missing=False + ) + self.network.delete_local_ip_association.\ + assert_any_call( + self.local_ip.id, + self._local_ip_association[0].fixed_port_id, + ignore_missing=False + ) + + +class TestListLocalIPAssociation(TestLocalIPAssociation): + + columns = ( + 'Local IP ID', + 'Local IP Address', + 'Fixed port ID', + 'Fixed IP', + 'Host' + ) + + def setUp(self): + super().setUp() + self.local_ip_associations = ( + network_fakes.create_local_ip_associations( + count=3, attrs={ + 'local_ip_id': self.local_ip.id, + 'fixed_port_id': self.fixed_port.id, + } + ) + ) + self.data = [] + for lip_assoc in self.local_ip_associations: + self.data.append(( + lip_assoc.local_ip_id, + lip_assoc.local_ip_address, + lip_assoc.fixed_port_id, + lip_assoc.fixed_ip, + lip_assoc.host, + )) + self.network.local_ip_associations = mock.Mock( + return_value=self.local_ip_associations + ) + self.network.find_local_ip = mock.Mock( + return_value=self.local_ip + ) + self.network.find_port = mock.Mock( + return_value=self.fixed_port + ) + # Get the command object to test + self.cmd = local_ip_association.ListLocalIPAssociation( + self.app, + self.namespace + ) + + def test_local_ip_association_list(self): + arglist = [ + self.local_ip.id + ] + verifylist = [ + ('local_ip', self.local_ip.id) + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + self.network.local_ip_associations.assert_called_once_with( + self.local_ip, + **{} + ) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(list(data))) + + def test_local_ip_association_list_all_options(self): + arglist = [ + '--fixed-port', self.local_ip_associations[0].fixed_port_id, + '--fixed-ip', self.local_ip_associations[0].fixed_ip, + '--host', self.local_ip_associations[0].host, + self.local_ip_associations[0].local_ip_id + ] + + verifylist = [ + ('fixed_port', self.local_ip_associations[0].fixed_port_id), + ('fixed_ip', self.local_ip_associations[0].fixed_ip), + ('host', self.local_ip_associations[0].host), + ('local_ip', self.local_ip_associations[0].local_ip_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + columns, data = self.cmd.take_action(parsed_args) + + attrs = { + 'fixed_port_id': self.local_ip_associations[0].fixed_port_id, + 'fixed_ip': self.local_ip_associations[0].fixed_ip, + 'host': self.local_ip_associations[0].host, + } + + self.network.local_ip_associations.assert_called_once_with( + self.local_ip, + **attrs + ) + self.assertEqual(set(self.columns), set(columns)) + self.assertEqual(set(self.data), set(list(data))) diff --git a/openstackclient/tests/unit/network/v2/test_network.py b/openstackclient/tests/unit/network/v2/test_network.py index 127d82b0..2b04edf5 100644 --- a/openstackclient/tests/unit/network/v2/test_network.py +++ b/openstackclient/tests/unit/network/v2/test_network.py @@ -48,7 +48,7 @@ class TestCreateNetworkIdentityV3(TestNetwork): # The new network created. _network = network_fakes.FakeNetwork.create_one_network( attrs={ - 'tenant_id': project.id, + 'project_id': project.id, 'availability_zone_hints': ["nova"], } ) @@ -197,8 +197,6 @@ class TestCreateNetworkIdentityV3(TestNetwork): 'shared': True, 'description': self._network.description, 'mtu': self._network.mtu, - # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor - 'tenant_id': self.project.id, 'project_id': self.project.id, 'is_default': True, 'router:external': True, @@ -284,7 +282,7 @@ class TestCreateNetworkIdentityV2(TestNetwork): project = identity_fakes_v2.FakeProject.create_one_project() # The new network created. _network = network_fakes.FakeNetwork.create_one_network( - attrs={'tenant_id': project.id} + attrs={'project_id': project.id} ) columns = ( @@ -379,8 +377,6 @@ class TestCreateNetworkIdentityV2(TestNetwork): self.network.create_network.assert_called_once_with(**{ 'admin_state_up': True, 'name': self._network.name, - # TODO(dtroyer): Remove tenant_id when we clean up the SDK refactor - 'tenant_id': self.project.id, 'project_id': self.project.id, }) self.assertFalse(self.network.set_tags.called) @@ -704,7 +700,7 @@ class TestListNetwork(TestNetwork): columns, data = self.cmd.take_action(parsed_args) self.network.networks.assert_called_once_with( - **{'tenant_id': project.id, 'project_id': project.id} + **{'project_id': project.id} ) self.assertEqual(self.columns, columns) @@ -723,7 +719,7 @@ class TestListNetwork(TestNetwork): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.networks.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py index e9687a70..d77d6894 100644 --- a/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py +++ b/openstackclient/tests/unit/network/v2/test_network_auto_allocated_topology.py @@ -33,7 +33,7 @@ class TestCreateAutoAllocatedTopology(TestAutoAllocatedTopology): topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( attrs={'id': network_object.id, - 'tenant_id': project.id} + 'project_id': project.id} ) columns = ( @@ -131,7 +131,7 @@ class TestValidateAutoAllocatedTopology(TestAutoAllocatedTopology): topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( attrs={'id': network_object.id, - 'tenant_id': project.id} + 'project_id': project.id} ) columns = ( @@ -208,7 +208,7 @@ class TestDeleteAutoAllocatedTopology(TestAutoAllocatedTopology): topology = network_fakes.FakeAutoAllocatedTopology.create_one_topology( attrs={'id': network_object.id, - 'tenant_id': project.id} + 'project_id': project.id} ) def setUp(self): diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index 010f53d3..20c5b9d6 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -166,7 +166,7 @@ class TestCreateNetworkFlavor(TestNetworkFlavor): self.network.create_flavor.assert_called_once_with(**{ 'description': self.new_network_flavor.description, 'enabled': True, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'service_type': self.new_network_flavor.service_type, 'name': self.new_network_flavor.name, }) diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py index fcf24da9..1cbe30ba 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor_profile.py @@ -89,7 +89,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'enabled': self.new_flavor_profile.enabled, 'driver': self.new_flavor_profile.driver, 'metainfo': self.new_flavor_profile.metainfo} @@ -119,7 +119,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'enabled': self.new_flavor_profile.enabled, 'metainfo': self.new_flavor_profile.metainfo} ) @@ -148,7 +148,7 @@ class TestCreateFlavorProfile(TestFlavorProfile): self.network.create_service_profile.assert_called_once_with( **{'description': self.new_flavor_profile.description, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'enabled': self.new_flavor_profile.enabled, 'driver': self.new_flavor_profile.driver, } diff --git a/openstackclient/tests/unit/network/v2/test_network_meter.py b/openstackclient/tests/unit/network/v2/test_network_meter.py index 4fadcfe1..5cedf0f4 100644 --- a/openstackclient/tests/unit/network/v2/test_network_meter.py +++ b/openstackclient/tests/unit/network/v2/test_network_meter.py @@ -112,7 +112,7 @@ class TestCreateMeter(TestMeter): self.network.create_metering_label.assert_called_once_with( **{'description': self.new_meter.description, 'name': self.new_meter.name, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'shared': True, } ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py index d6a78410..af4cb3fb 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -42,7 +42,7 @@ class TestCreateNetworkQosPolicy(TestQosPolicy): new_qos_policy = ( network_fakes.FakeNetworkQosPolicy.create_one_qos_policy( attrs={ - 'tenant_id': project.id, + 'project_id': project.id, } )) columns = ( @@ -123,7 +123,7 @@ class TestCreateNetworkQosPolicy(TestQosPolicy): self.network.create_qos_policy.assert_called_once_with(**{ 'shared': True, - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'name': self.new_qos_policy.name, 'description': 'QoS policy description', 'is_default': True, @@ -325,7 +325,7 @@ class TestListNetworkQosPolicy(TestQosPolicy): columns, data = self.cmd.take_action(parsed_args) self.network.qos_policies.assert_called_once_with( - **{'tenant_id': project.id} + **{'project_id': project.id} ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index 08be64c5..e20efc8b 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -45,7 +45,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): ag_object = network_fakes.FakeAddressGroup.create_one_address_group() project = identity_fakes_v3.FakeProject.create_one_project() rbac_policy = network_fakes.FakeNetworkRBAC.create_one_network_rbac( - attrs={'tenant_id': project.id, + attrs={'project_id': project.id, 'target_tenant': project.id, 'object_id': network_object.id} ) @@ -64,7 +64,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): rbac_policy.id, rbac_policy.object_id, rbac_policy.object_type, - rbac_policy.tenant_id, + rbac_policy.project_id, rbac_policy.target_tenant, ] @@ -206,7 +206,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): '--type', self.rbac_policy.object_type, '--action', self.rbac_policy.action, '--target-project', self.rbac_policy.target_tenant, - '--project', self.rbac_policy.tenant_id, + '--project', self.rbac_policy.project_id, '--project-domain', self.project.domain_id, '--target-project-domain', self.project.domain_id, self.rbac_policy.object_id, @@ -215,7 +215,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): ('type', self.rbac_policy.object_type), ('action', self.rbac_policy.action), ('target_project', self.rbac_policy.target_tenant), - ('project', self.rbac_policy.tenant_id), + ('project', self.rbac_policy.project_id), ('project_domain', self.project.domain_id), ('target_project_domain', self.project.domain_id), ('rbac_object', self.rbac_policy.object_id), @@ -230,7 +230,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): 'object_type': self.rbac_policy.object_type, 'action': self.rbac_policy.action, 'target_tenant': self.rbac_policy.target_tenant, - 'tenant_id': self.rbac_policy.tenant_id, + 'project_id': self.rbac_policy.project_id, }) self.assertEqual(self.columns, columns) self.assertEqual(self.data, list(data)) @@ -276,7 +276,7 @@ class TestCreateNetworkRBAC(TestNetworkRBAC): self.rbac_policy.id, obj_fake.id, obj_type, - self.rbac_policy.tenant_id, + self.rbac_policy.project_id, self.rbac_policy.target_tenant, ] self.assertEqual(self.columns, columns) @@ -541,7 +541,7 @@ class TestShowNetworkRBAC(TestNetworkRBAC): rbac_policy.id, rbac_policy.object_id, rbac_policy.object_type, - rbac_policy.tenant_id, + rbac_policy.project_id, rbac_policy.target_tenant, ] diff --git a/openstackclient/tests/unit/network/v2/test_port.py b/openstackclient/tests/unit/network/v2/test_port.py index 5f2a1283..3c18f362 100644 --- a/openstackclient/tests/unit/network/v2/test_port.py +++ b/openstackclient/tests/unit/network/v2/test_port.py @@ -1217,7 +1217,6 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) filters = { - 'tenant_id': project.id, 'project_id': project.id, 'fields': LIST_FIELDS_TO_RETRIEVE, } @@ -1241,7 +1240,6 @@ class TestListPort(TestPort): columns, data = self.cmd.take_action(parsed_args) filters = { - 'tenant_id': project.id, 'project_id': project.id, 'fields': LIST_FIELDS_TO_RETRIEVE, } @@ -1296,6 +1294,26 @@ class TestListPort(TestPort): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, list(data)) + def test_port_list_security_group(self): + arglist = [ + '--security-group', 'sg-id1', + '--security-group', 'sg-id2', + ] + verifylist = [ + ('security_groups', ['sg-id1', 'sg-id2']), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = { + 'security_groups': ['sg-id1', 'sg-id2'], + 'fields': LIST_FIELDS_TO_RETRIEVE, + } + + self.network.ports.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, list(data)) + class TestSetPort(TestPort): @@ -1923,6 +1941,7 @@ class TestUnsetPort(TestPort): 'subnet=042eb10a-3a18-4658-ab-cf47c8d03152,ip-address=1.0.0.0', '--binding-profile', 'Superman', '--qos-policy', + '--host', self._testport.name, ] verifylist = [ @@ -1931,6 +1950,7 @@ class TestUnsetPort(TestPort): 'ip-address': '1.0.0.0'}]), ('binding_profile', ['Superman']), ('qos_policy', True), + ('host', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1941,7 +1961,8 @@ class TestUnsetPort(TestPort): 'subnet_id': '042eb10a-3a18-4658-ab-cf47c8d03152', 'ip_address': '0.0.0.1'}], 'binding:profile': {'batman': 'Joker'}, - 'qos_policy_id': None + 'qos_policy_id': None, + 'binding:host_id': None } self.network.update_port.assert_called_once_with( self._testport, **attrs) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 03246748..4d035077 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -141,7 +141,7 @@ class TestCreateRouter(TestRouter): new_router.ha, new_router.id, new_router.name, - new_router.tenant_id, + new_router.project_id, router.RoutesColumn(new_router.routes), new_router.status, format_columns.ListColumn(new_router.tags), @@ -186,6 +186,43 @@ class TestCreateRouter(TestRouter): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data) + def test_create_with_gateway(self): + _network = network_fakes.FakeNetwork.create_one_network() + _subnet = network_fakes.FakeSubnet.create_one_subnet() + self.network.find_network = mock.Mock(return_value=_network) + self.network.find_subnet = mock.Mock(return_value=_subnet) + arglist = [ + self.new_router.name, + '--external-gateway', _network.name, + '--enable-snat', + '--fixed-ip', 'ip-address=2001:db8::1' + ] + verifylist = [ + ('name', self.new_router.name), + ('enable', True), + ('distributed', False), + ('ha', False), + ('external_gateway', _network.name), + ('enable_snat', True), + ('fixed_ip', [{'ip-address': '2001:db8::1'}]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = (self.cmd.take_action(parsed_args)) + + self.network.create_router.assert_called_once_with(**{ + 'admin_state_up': True, + 'name': self.new_router.name, + 'external_gateway_info': { + 'network_id': _network.id, + 'enable_snat': True, + 'external_fixed_ips': [{'ip_address': '2001:db8::1'}], + }, + }) + self.assertFalse(self.network.set_tags.called) + self.assertEqual(self.columns, columns) + self.assertCountEqual(self.data, data) + def _test_create_with_ha_options(self, option, ha): arglist = [ option, @@ -423,7 +460,7 @@ class TestListRouter(TestRouter): r.name, r.status, router.AdminStateColumn(r.admin_state_up), - r.tenant_id, + r.project_id, r.distributed, r.ha, )) @@ -619,7 +656,7 @@ class TestListRouter(TestRouter): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -639,7 +676,7 @@ class TestListRouter(TestRouter): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.routers.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -1364,7 +1401,7 @@ class TestShowRouter(TestRouter): _router.id, router.RouterInfoColumn(_router.interfaces_info), _router.name, - _router.tenant_id, + _router.project_id, router.RoutesColumn(_router.routes), _router.status, format_columns.ListColumn(_router.tags), diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 569c0cd5..95262bf1 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -121,7 +121,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'description': self._security_group.description, 'stateful': self._security_group.stateful, 'name': self._security_group.name, - 'tenant_id': self.project.id, + 'project_id': self.project.id, }) self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, data) @@ -324,7 +324,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): columns, data = self.cmd.take_action(parsed_args) filters = { - 'tenant_id': project.id, 'project_id': project.id, + 'project_id': project.id, 'fields': security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE} self.network.security_groups.assert_called_once_with(**filters) @@ -346,7 +346,7 @@ class TestListSecurityGroupNetwork(TestSecurityGroupNetwork): columns, data = self.cmd.take_action(parsed_args) filters = { - 'tenant_id': project.id, 'project_id': project.id, + 'project_id': project.id, 'fields': security_group.ListSecurityGroup.FIELDS_TO_RETRIEVE} self.network.security_groups.assert_called_once_with(**filters) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py index bcdb0c26..4e765f3d 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_network.py @@ -458,7 +458,7 @@ class TestCreateSecurityGroupRuleNetwork(TestSecurityGroupRuleNetwork): 'protocol': self._security_group_rule.protocol, 'remote_ip_prefix': self._security_group_rule.remote_ip_prefix, 'security_group_id': self._security_group.id, - 'tenant_id': self.project.id, + 'project_id': self.project.id, }) self.assertEqual(self.expected_columns, columns) self.assertEqual(self.expected_data, data) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index 5147b64d..dcac2ef8 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -44,7 +44,7 @@ class TestCreateSubnet(TestSubnet): # An IPv4 subnet to be created with mostly default values self._subnet = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': self.project.id, + 'project_id': self.project.id, } ) @@ -55,7 +55,7 @@ class TestCreateSubnet(TestSubnet): # An IPv4 subnet to be created using a specific subnet pool self._subnet_from_pool = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'subnetpool_id': self._subnet_pool.id, 'dns_nameservers': ['8.8.8.8', '8.8.4.4'], @@ -71,7 +71,7 @@ class TestCreateSubnet(TestSubnet): # An IPv6 subnet to be created with most options specified self._subnet_ipv6 = network_fakes.FakeSubnet.create_one_subnet( attrs={ - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'cidr': 'fe80:0:0:a00a::/64', 'enable_dhcp': True, 'dns_nameservers': ['fe80:27ff:a00a:f00f::ffff', @@ -661,7 +661,7 @@ class TestListSubnet(TestSubnet): subnet.name, subnet.network_id, subnet.cidr, - subnet.tenant_id, + subnet.project_id, subnet.enable_dhcp, format_columns.ListColumn(subnet.dns_nameservers), subnet_v2.AllocationPoolsColumn(subnet.allocation_pools), @@ -783,7 +783,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -821,7 +821,7 @@ class TestListSubnet(TestSubnet): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.subnets.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -899,6 +899,48 @@ class TestListSubnet(TestSubnet): self.assertEqual(self.columns, columns) self.assertCountEqual(self.data, list(data)) + def test_subnet_list_subnetpool_by_name(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + subnet = network_fakes.FakeSubnet.create_one_subnet( + {'subnetpool_id': subnet_pool.id}) + self.network.find_network = mock.Mock(return_value=subnet) + self.network.find_subnet_pool = mock.Mock(return_value=subnet_pool) + arglist = [ + '--subnet-pool', subnet_pool.name, + ] + verifylist = [ + ('subnet_pool', subnet_pool.name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'subnetpool_id': subnet_pool.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + + def test_subnet_list_subnetpool_by_id(self): + subnet_pool = network_fakes.FakeSubnetPool.create_one_subnet_pool() + subnet = network_fakes.FakeSubnet.create_one_subnet( + {'subnetpool_id': subnet_pool.id}) + self.network.find_network = mock.Mock(return_value=subnet) + self.network.find_subnet_pool = mock.Mock(return_value=subnet_pool) + arglist = [ + '--subnet-pool', subnet_pool.id, + ] + verifylist = [ + ('subnet_pool', subnet_pool.id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + filters = {'subnetpool_id': subnet_pool.id} + + self.network.subnets.assert_called_once_with(**filters) + self.assertEqual(self.columns, columns) + self.assertItemsEqual(self.data, list(data)) + def test_list_with_tag_options(self): arglist = [ '--tags', 'red,blue', @@ -1205,7 +1247,7 @@ class TestShowSubnet(TestSubnet): _subnet.ipv6_ra_mode, _subnet.name, _subnet.network_id, - _subnet.tenant_id, + _subnet.project_id, _subnet.segment_id, format_columns.ListColumn(_subnet.service_types), _subnet.subnetpool_id, diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 4d18dc99..55d2cc29 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -197,7 +197,7 @@ class TestCreateSubnetPool(TestSubnetPool): self.network.create_subnet_pool.assert_called_once_with(**{ 'prefixes': ['10.0.10.0/24'], - 'tenant_id': self.project.id, + 'project_id': self.project.id, 'name': self._subnet_pool.name, }) self.assertEqual(self.columns, columns) @@ -569,7 +569,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -589,7 +589,7 @@ class TestListSubnetPool(TestSubnetPool): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - filters = {'tenant_id': project.id, 'project_id': project.id} + filters = {'project_id': project.id} self.network.subnet_pools.assert_called_once_with(**filters) self.assertEqual(self.columns, columns) @@ -970,7 +970,7 @@ class TestShowSubnetPool(TestSubnetPool): _subnet_pool.min_prefixlen, _subnet_pool.name, format_columns.ListColumn(_subnet_pool.prefixes), - _subnet_pool.tenant_id, + _subnet_pool.project_id, _subnet_pool.shared, format_columns.ListColumn(_subnet_pool.tags), ) diff --git a/openstackclient/tests/unit/volume/v2/fakes.py b/openstackclient/tests/unit/volume/v2/fakes.py index b5f66d4b..96e381d3 100644 --- a/openstackclient/tests/unit/volume/v2/fakes.py +++ b/openstackclient/tests/unit/volume/v2/fakes.py @@ -18,6 +18,7 @@ from unittest import mock import uuid from cinderclient import api_versions +from openstack.block_storage.v3 import volume from osc_lib.cli import format_columns from openstackclient.tests.unit import fakes @@ -46,7 +47,7 @@ class FakeTransfer(object): def create_one_transfer(attrs=None): """Create a fake transfer. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of Transfer Request :return: A FakeResource object with volume_id, name, id. @@ -75,7 +76,7 @@ class FakeTransfer(object): def create_transfers(attrs=None, count=2): """Create multiple fake transfers. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of transfer :param Integer count: The number of transfers to be faked @@ -116,7 +117,7 @@ class FakeTypeAccess(object): def create_one_type_access(attrs=None): """Create a fake volume type access for project. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object, with Volume_type_ID and Project_ID. @@ -148,7 +149,7 @@ class FakeService(object): def create_one_service(attrs=None): """Create a fake service. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of service :return: A FakeResource object with host, status, etc. @@ -180,7 +181,7 @@ class FakeService(object): def create_services(attrs=None, count=2): """Create multiple fake services. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of service :param Integer count: The number of services to be faked @@ -201,7 +202,7 @@ class FakeCapability(object): def create_one_capability(attrs=None): """Create a fake volume backend capability. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of the Capabilities. :return: A FakeResource object with capability name and attrs. @@ -260,7 +261,7 @@ class FakePool(object): def create_one_pool(attrs=None): """Create a fake pool. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of the pool :return: A FakeResource object with pool name and attrs. @@ -362,7 +363,7 @@ class FakeVolume(object): def create_one_volume(attrs=None): """Create a fake volume. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of volume :return: A FakeResource object with id, name, status, etc. @@ -405,7 +406,7 @@ class FakeVolume(object): def create_volumes(attrs=None, count=2): """Create multiple fake volumes. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes of volume :param Integer count: The number of volumes to be faked @@ -419,6 +420,61 @@ class FakeVolume(object): return volumes @staticmethod + def create_one_sdk_volume(attrs=None): + """Create a fake volume. + + :param dict attrs: + A dictionary with all attributes of volume + :return: + A FakeResource object with id, name, status, etc. + """ + attrs = attrs or {} + + # Set default attribute + volume_info = { + 'id': 'volume-id' + uuid.uuid4().hex, + 'name': 'volume-name' + uuid.uuid4().hex, + 'description': 'description' + uuid.uuid4().hex, + 'status': random.choice(['available', 'in_use']), + 'size': random.randint(1, 20), + 'volume_type': + random.choice(['fake_lvmdriver-1', 'fake_lvmdriver-2']), + 'bootable': + random.choice(['true', 'false']), + 'metadata': { + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex, + 'key' + uuid.uuid4().hex: 'val' + uuid.uuid4().hex}, + 'snapshot_id': random.randint(1, 5), + 'availability_zone': 'zone' + uuid.uuid4().hex, + 'attachments': [{ + 'device': '/dev/' + uuid.uuid4().hex, + 'server_id': uuid.uuid4().hex, + }, ], + } + + # Overwrite default attributes if there are some attributes set + volume_info.update(attrs) + return volume.Volume(**volume_info) + + @staticmethod + def create_sdk_volumes(attrs=None, count=2): + """Create multiple fake volumes. + + :param dict attrs: + A dictionary with all attributes of volume + :param Integer count: + The number of volumes to be faked + :return: + A list of FakeResource objects + """ + volumes = [] + for n in range(0, count): + volumes.append(FakeVolume.create_one_sdk_volume(attrs)) + + return volumes + + @staticmethod def get_volumes(volumes=None, count=2): """Get an iterable MagicMock object with a list of faked volumes. @@ -484,7 +540,7 @@ class FakeAvailabilityZone(object): def create_one_availability_zone(attrs=None): """Create a fake AZ. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with zoneName, zoneState, etc. @@ -509,7 +565,7 @@ class FakeAvailabilityZone(object): def create_availability_zones(attrs=None, count=2): """Create multiple fake AZs. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of AZs to fake @@ -532,7 +588,7 @@ class FakeBackup(object): def create_one_backup(attrs=None): """Create a fake backup. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, volume_id, etc. @@ -565,7 +621,7 @@ class FakeBackup(object): def create_backups(attrs=None, count=2): """Create multiple fake backups. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of backups to fake @@ -636,7 +692,7 @@ class FakeConsistencyGroup(object): def create_one_consistency_group(attrs=None): """Create a fake consistency group. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -666,7 +722,7 @@ class FakeConsistencyGroup(object): def create_consistency_groups(attrs=None, count=2): """Create multiple fake consistency groups. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of consistency groups to fake @@ -713,7 +769,7 @@ class FakeConsistencyGroupSnapshot(object): def create_one_consistency_group_snapshot(attrs=None): """Create a fake consistency group snapshot. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -742,7 +798,7 @@ class FakeConsistencyGroupSnapshot(object): def create_consistency_group_snapshots(attrs=None, count=2): """Create multiple fake consistency group snapshots. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of consistency group snapshots to fake @@ -789,7 +845,7 @@ class FakeExtension(object): def create_one_extension(attrs=None): """Create a fake extension. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with name, namespace, etc. @@ -825,7 +881,7 @@ class FakeQos(object): def create_one_qos(attrs=None): """Create a fake Qos specification. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, consumer, etc. @@ -852,7 +908,7 @@ class FakeQos(object): def create_one_qos_association(attrs=None): """Create a fake Qos specification association. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, association_type, etc. @@ -878,7 +934,7 @@ class FakeQos(object): def create_qoses(attrs=None, count=2): """Create multiple fake Qos specifications. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of Qos specifications to fake @@ -920,7 +976,7 @@ class FakeSnapshot(object): def create_one_snapshot(attrs=None): """Create a fake snapshot. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with id, name, description, etc. @@ -951,7 +1007,7 @@ class FakeSnapshot(object): def create_snapshots(attrs=None, count=2): """Create multiple fake snapshots. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of snapshots to fake @@ -993,9 +1049,9 @@ class FakeVolumeType(object): def create_one_volume_type(attrs=None, methods=None): """Create a fake volume type. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes - :param Dictionary methods: + :param dict methods: A dictionary with all methods :return: A FakeResource object with id, name, description, etc. @@ -1025,7 +1081,7 @@ class FakeVolumeType(object): def create_volume_types(attrs=None, count=2): """Create multiple fake volume_types. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :param int count: The number of types to fake @@ -1063,7 +1119,7 @@ class FakeVolumeType(object): def create_one_encryption_volume_type(attrs=None): """Create a fake encryption volume type. - :param Dictionary attrs: + :param dict attrs: A dictionary with all attributes :return: A FakeResource object with volume_type_id etc. diff --git a/releasenotes/notes/add-network-local-ip-df3a9ce7610d8b90.yaml b/releasenotes/notes/add-network-local-ip-df3a9ce7610d8b90.yaml new file mode 100644 index 00000000..dbbd3028 --- /dev/null +++ b/releasenotes/notes/add-network-local-ip-df3a9ce7610d8b90.yaml @@ -0,0 +1,8 @@ +--- +features: + - Add ``local ip create``, ``local ip delete``, + ``local ip list``, ``local ip set``, ``local ip show``, + ``local ip association create``, ``local ip association delete`` + and ``local ip association list`` commands to support Neutron Local IP + CRUD operations. + [`bug 1930200 <https://bugs.launchpad.net/neutron/+bug/1930200>`_] diff --git a/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml b/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml new file mode 100644 index 00000000..0aa04760 --- /dev/null +++ b/releasenotes/notes/add-option-to-unset-port-host-c76de9b1d2addf9a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add possibility to unbind Neutron's port from the host by unsetting its + host_id. diff --git a/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml b/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml new file mode 100644 index 00000000..171b4a5a --- /dev/null +++ b/releasenotes/notes/check-limit-quota-cc7f291dd1b537c1.yaml @@ -0,0 +1,5 @@ +--- +features: + - Add ``--check-limit`` option to the ``openstack quota set`` command (only + for network commands). The network quota engine will check the resource + usage before setting the new quota limit. diff --git a/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml b/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml new file mode 100644 index 00000000..fdb37bbb --- /dev/null +++ b/releasenotes/notes/fix-flavor-in-server-list-microversion-2.47-af200e9bb4747e2d.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + Add support for compute API microversion 2.47, which changes how flavor + details are included in server detail responses. In 2.46 and below, + only the flavor ID was shown in the server detail response. Starting in + 2.47, flavor information is embedded in the server response. The newer + behavior is now supported. diff --git a/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml b/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml new file mode 100644 index 00000000..d784a9aa --- /dev/null +++ b/releasenotes/notes/list-subnet-by-pool-id-a642efc13d04fa08.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--subnet-pool`` option to ``subnet list`` to filter + by subnets by subnet pool. diff --git a/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml b/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml new file mode 100644 index 00000000..79899b3e --- /dev/null +++ b/releasenotes/notes/migrate-add-fixed-ip-to-sdk-3d932d77633bc765.yaml @@ -0,0 +1,3 @@ +features: + - | + Switch the add fixed IP command from novaclient to SDK. diff --git a/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml b/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml new file mode 100644 index 00000000..20f8d550 --- /dev/null +++ b/releasenotes/notes/migrate-create-server-image-to-sdk-e3d8077ffe05bb3d.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate openstack server image create from novaclient to sdk. diff --git a/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml b/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml new file mode 100644 index 00000000..54abdacb --- /dev/null +++ b/releasenotes/notes/migrate-server-add-volume-to-sdk-685e036a88839651.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Migrate openstack server add volume to using sdk. diff --git a/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml b/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml new file mode 100644 index 00000000..e2d40034 --- /dev/null +++ b/releasenotes/notes/migrate-server-pause-unpause-to-sdk-d74ec8536b764af6.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Migrate ``server pause`` and ``server unpause`` commands from novaclient + to sdk. diff --git a/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml b/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml new file mode 100644 index 00000000..7d3781bb --- /dev/null +++ b/releasenotes/notes/migrate-server-suspend-resume-to-sdk-fd1709336607b496.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Migrate ``server suspend`` and ``server resume`` commands from novaclient + to sdk. diff --git a/releasenotes/notes/migrate-service-list-delete-set-to-sdk-920cbe0d210af565.yaml b/releasenotes/notes/migrate-service-list-delete-set-to-sdk-920cbe0d210af565.yaml new file mode 100644 index 00000000..55dcb43c --- /dev/null +++ b/releasenotes/notes/migrate-service-list-delete-set-to-sdk-920cbe0d210af565.yaml @@ -0,0 +1,3 @@ +features: + - | + Switch the compute service commands from novaclient to SDK. diff --git a/releasenotes/notes/options-create-router-97910a882b604652.yaml b/releasenotes/notes/options-create-router-97910a882b604652.yaml new file mode 100644 index 00000000..f7d90b75 --- /dev/null +++ b/releasenotes/notes/options-create-router-97910a882b604652.yaml @@ -0,0 +1,8 @@ +--- +features: + - | + It is now possible to add an external gateway to a router + immediately on creation. Previously it could only be done by + modifying the router after it had been created. This includes + the options to en- or disable SNAT and to specify a fixed-ip + on the external network. diff --git a/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml b/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml new file mode 100644 index 00000000..fe81de94 --- /dev/null +++ b/releasenotes/notes/pass_ssh_args-cf26a2ce26ccddaf.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added the ability to pass arguments through to the ``ssh`` command When + using ``openstack server ssh``. This allows the user to use any ``ssh`` + option without needing to add that option to the openstack client. + Existing openstackclient options that mirror SSH options are now + deprecated. +deprecations: + - | + ``openstack server ssh`` options that mirror ``ssh`` options are now + deprecated (``--login, -l, --port, --identity, --option, -o, -vz``). + The ``ssh`` equivalent of each deprecated option should be used instead. + For example ``openstack server ssh instance -- -l user -i key`` diff --git a/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml b/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml new file mode 100644 index 00000000..c68eeafb --- /dev/null +++ b/releasenotes/notes/port-list-security-group-4af5d2e789174ff9.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Added ``--security-group`` option to the ``os port list`` command. This + option is appendable and multiple security group IDs can be provided. diff --git a/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml b/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml new file mode 100644 index 00000000..6b47b1b3 --- /dev/null +++ b/releasenotes/notes/switch-server-remove-network-port-to-sdk-829ba711e0e198d5.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Switch server remove volume/port to using sdk. diff --git a/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml b/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml new file mode 100644 index 00000000..3e0397d7 --- /dev/null +++ b/releasenotes/notes/switch-server-remove-volume-to-sdk-47e9befd2672dcdf.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Switch command server remove volume to using sdk. diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py index 206c0ce2..179f8f23 100644 --- a/releasenotes/source/conf.py +++ b/releasenotes/source/conf.py @@ -37,6 +37,14 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. +from sphinx.util import logging + +# According to the discussion in +# https://github.com/sphinx-doc/sphinx/issues/10112 this may be applied as a +# dirty hack until the issue with replacing extlinks is resolved +linklogger = logging.getLogger('sphinx.ext.extlinks') +linklogger.setLevel(40) # Ignore messages less severe than ERROR + extensions = [ 'openstackdocstheme', 'reno.sphinxext', diff --git a/requirements.txt b/requirements.txt index 0ac991da..cb414ebb 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,7 +5,7 @@ pbr!=2.1.0,>=2.0.0 # Apache-2.0 cliff>=3.5.0 # Apache-2.0 iso8601>=0.1.11 # MIT -openstacksdk>=0.56.0 # Apache-2.0 +openstacksdk>=0.61.0 # Apache-2.0 osc-lib>=2.3.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 @@ -415,6 +415,16 @@ openstack.network.v2 = ip_availability_list = openstackclient.network.v2.ip_availability:ListIPAvailability ip_availability_show = openstackclient.network.v2.ip_availability:ShowIPAvailability + local_ip_create = openstackclient.network.v2.local_ip:CreateLocalIP + local_ip_delete = openstackclient.network.v2.local_ip:DeleteLocalIP + local_ip_list = openstackclient.network.v2.local_ip:ListLocalIP + local_ip_set = openstackclient.network.v2.local_ip:SetLocalIP + local_ip_show = openstackclient.network.v2.local_ip:ShowLocalIP + + local_ip_association_create = openstackclient.network.v2.local_ip_association:CreateLocalIPAssociation + local_ip_association_delete = openstackclient.network.v2.local_ip_association:DeleteLocalIPAssociation + local_ip_association_list = openstackclient.network.v2.local_ip_association:ListLocalIPAssociation + network_agent_add_network = openstackclient.network.v2.network_agent:AddNetworkToAgent network_agent_add_router = openstackclient.network.v2.network_agent:AddRouterToAgent network_agent_delete = openstackclient.network.v2.network_agent:DeleteNetworkAgent |