diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b277bb059b..718c80f0b0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -15,28 +15,14 @@ repos: files: .*\.(yaml|yml)$ args: ['--unsafe'] - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.0 + rev: v0.15.1 hooks: - id: ruff-check args: ['--fix', '--unsafe-fixes'] - id: ruff-format - repo: https://opendev.org/openstack/hacking - rev: 7.0.0 + rev: 8.0.0 hooks: - id: hacking additional_dependencies: [] exclude: '^(doc|releasenotes)/.*$' - - repo: https://github.com/pre-commit/mirrors-mypy - rev: v1.18.2 - hooks: - - id: mypy - additional_dependencies: - - types-requests - # keep this in-sync with '[tool.mypy] exclude' in 'pyproject.toml' - exclude: | - (?x)( - doc/.* - | examples/.* - | hacking/.* - | releasenotes/.* - ) diff --git a/examples/common.py b/examples/common.py index 650139ec27..8213f4b532 100755 --- a/examples/common.py +++ b/examples/common.py @@ -264,7 +264,7 @@ def main(opts, run): if dump_stack_trace: _logger.error(traceback.format_exc(e)) else: - _logger.error('Exception raised: ' + str(e)) + _logger.error('Exception raised: %s', e) return 1 diff --git a/hacking/checks.py b/hacking/checks.py index 0eb485e7e2..80807df87b 100644 --- a/hacking/checks.py +++ b/hacking/checks.py @@ -30,17 +30,28 @@ def assert_no_oslo(logical_line): """Check for use of oslo libraries. - O400 + Okay: import os + Okay: from os import path + O400: import oslo_messaging + O400: from oslo_log import log """ - if re.match(r'(from|import) oslo_.*', logical_line): - yield (0, "0400: oslo libraries should not be used in SDK projects") + if match := re.match(r'(from|import) (oslo_.*)', logical_line): + if match.group(2) == 'oslo_i18n': + return + yield (0, "O400: oslo libraries should not be used in SDK projects") @core.flake8ext def assert_no_duplicated_setup(logical_line, filename): """Check for use of various unnecessary test duplications. - O401 + This check only applies to files under openstackclient/tests/unit/. + + Okay: self.app = fakes.FakeShell() + Okay: self.app.client_manager.auth_ref = mock.Mock() + O401: self.app = Namespace(self.app, self.namespace) + O401: self.network_client = self.app.client_manager.network + O401: self.app.client_manager.network = mock.Mock() """ if os.path.join('openstackclient', 'tests', 'unit') not in filename: return @@ -53,7 +64,7 @@ def assert_no_duplicated_setup(logical_line, filename): if os.path.basename(filename) != 'fakes.py': if re.match( - r'self.[a-z_]+_client = self.app.client_manager.*', logical_line + r'self.[a-zA-Z0-9_]+ = self.app.client_manager.*', logical_line ): yield ( 0, @@ -75,10 +86,17 @@ def assert_no_duplicated_setup(logical_line, filename): @core.flake8ext -def assert_use_of_client_aliases(logical_line): +def assert_use_of_client_aliases(logical_line, filename): """Ensure we use $service_client instead of $sdk_connection.service. - O402 + Okay: self.compute_client.find_server(foo) + O402: self.app.client_manager.sdk_connnection.compute.find_server(foo) + + The following checks only apply to files under openstackclient/tests/unit/: + + O402: self.app.client_manager.compute.find_server.return_value = server + O402: self.app.client_manager.compute.find_server = mock.Mock() + O402: self.compute_client.find_server = mock.Mock() """ # we should expand the list of services as we drop legacy clients if match := re.match( @@ -86,7 +104,22 @@ def assert_use_of_client_aliases(logical_line): logical_line, ): service = match.group(1) - yield (0, f"0402: prefer {service}_client to sdk_connection.{service}") + yield (0, f"O402: prefer {service}_client to sdk_connection.{service}") + + # everything from here down only affects unit tests + if os.path.join('openstackclient', 'tests', 'unit') not in filename: + return + + if match := re.match( + r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+)\.(return_value|side_effect) = ', # noqa: E501 + logical_line, + ): + service = match.group(1) + yield ( + 0, + f"O402: prefer {service}_client to " + f"self.app.client_manager.{service}", + ) if match := re.match( r'(self\.app\.client_manager\.(compute|network|image)+\.[a-z_]+) = mock.Mock', # noqa: E501 @@ -137,6 +170,11 @@ def visit_Call(self, node): isinstance(node.func.value, ast.Name) and node.func.value.id.endswith('client') ) + or ( + # handle calls like 'self.compute_client.find_server' + isinstance(node.func.value, ast.Attribute) + and node.func.value.attr.endswith('_client') + ) or ( # handle calls like 'self.app.client_manager.image.find_image' isinstance(node.func.value, ast.Attribute) @@ -177,3 +215,22 @@ def assert_find_ignore_missing_kwargs(logical_line, filename): 'O403: Calls to find_* proxy methods must explicitly set ' 'ignore_missing', ) + + +@core.flake8ext +def assert_use_of_osc_command(logical_line, filename): + """Ensure we use openstackclient.command instead of osc_lib.command. + + Okay: from openstackclient.command import command + Okay: import openstackclient.command + O404: from osc_lib.command import command + """ + if filename == 'openstackclient/command.py': + return + + if re.match(r'^from osc_lib\.command import command$', logical_line): + yield ( + 0, + 'O404: Import Command classes from openstackclient.command, not ' + 'osc_lib.command.command', + ) diff --git a/openstackclient/api/api.py b/openstackclient/api/api.py index 5f78b0ee54..eae73eea6b 100644 --- a/openstackclient/api/api.py +++ b/openstackclient/api/api.py @@ -254,7 +254,7 @@ def find_bulk(self, path, **kwargs): items = self.list(path) if isinstance(items, dict): # strip off the enclosing dict - key = list(items.keys())[0] + key = next(iter(items.keys())) items = items[key] ret = [] diff --git a/openstackclient/common/module.py b/openstackclient/common/module.py index 6ca5dc2315..0985107516 100644 --- a/openstackclient/common/module.py +++ b/openstackclient/common/module.py @@ -61,7 +61,7 @@ def take_action(self, parsed_args): # TODO(bapalm): Fix this when cliff properly supports # handling the detection rather than using the hard-code below. if parsed_args.formatter == 'table': - command_names = utils.format_list(command_names, "\n") + command_names = utils.format_list(command_names, "\n") # type: ignore commands.append((group, command_names)) diff --git a/openstackclient/common/progressbar.py b/openstackclient/common/progressbar.py index 2852bb250a..49eb6d6df9 100644 --- a/openstackclient/common/progressbar.py +++ b/openstackclient/common/progressbar.py @@ -40,7 +40,7 @@ def _display_progress_bar(self, size_read): # Output something like this: [==========> ] 49% sys.stdout.write( '\r[{:<30}] {:.0%}'.format( - '=' * int(round(self._percent * 29)) + '>', self._percent + '=' * round(self._percent * 29) + '>', self._percent ) ) sys.stdout.flush() diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 6d0025a754..c0e98a8361 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -272,7 +272,10 @@ def _list_quota_compute(self, parsed_args, project_ids): sdk_exceptions.NotFoundException, ) as exc: # Project not found, move on to next one - LOG.warning(f"Project {project_id} not found: {exc}") + LOG.warning( + 'Project %(project_id)s not found: %(exc)s', + {'project_id': project_id, 'exc': exc}, + ) continue project_result = _xform_get_quota( @@ -334,7 +337,10 @@ def _list_quota_volume(self, parsed_args, project_ids): sdk_exceptions.NotFoundException, ) as exc: # Project not found, move on to next one - LOG.warning(f"Project {project_id} not found: {exc}") + LOG.warning( + 'Project %(project_id)s not found: %(exc)s', + {'project_id': project_id, 'exc': exc}, + ) continue project_result = _xform_get_quota( @@ -389,7 +395,10 @@ def _list_quota_network(self, parsed_args, project_ids): sdk_exceptions.ForbiddenException, ) as exc: # Project not found, move on to next one - LOG.warning(f"Project {project_id} not found: {exc}") + LOG.warning( + 'Project %(project_id)s not found: %(exc)s', + {'project_id': project_id, 'exc': exc}, + ) continue project_result = _xform_get_quota( @@ -818,13 +827,11 @@ def _normalize_names(section: dict) -> None: _normalize_names(info["usage"]) # Remove the 'id' field since it's not very useful - if 'id' in info: - del info['id'] + info.pop('id', None) # Remove the sdk-derived fields for field in ('location', 'name', 'force'): - if field in info: - del info[field] + info.pop(field, None) if not parsed_args.usage: result = [{'resource': k, 'limit': v} for k, v in info.items()] diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 1eee828d9b..e89a48aee7 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -940,9 +940,9 @@ def __call__(self, parser, namespace, values, option_string=None): } for kv_str in values.split(','): - k, sep, v = kv_str.partition('=') + k, _sep, v = kv_str.partition('=') - if k not in list(info) + ['tag'] or not v: + if k not in [*list(info), 'tag'] or not v: msg = _( "Invalid argument %s; argument must be of form " "'net-id=net-uuid,port-id=port-uuid,v4-fixed-ip=ip-addr," @@ -975,7 +975,7 @@ def __call__(self, parser, namespace, values, option_string=None): if getattr(namespace, self.dest, None) is None: setattr(namespace, self.dest, []) - dev_name, sep, dev_map = values.partition('=') + dev_name, _sep, dev_map = values.partition('=') dev_map = dev_map.split(':') if dev_map else dev_map if not dev_name or not dev_map or len(dev_map) > 4: msg = _( @@ -3189,13 +3189,22 @@ class MigrateServer(command.Command): _description = _( """Migrate server to different host. -A migrate operation is implemented as a resize operation using the same flavor -as the old server. This means that, like resize, migrate works by creating a -new server using the same flavor and copying the contents of the original disk -into a new one. As with resize, the migrate operation is a two-step process for -the user: the first step is to perform the migrate, and the second step is to -either confirm (verify) success and release the old server, or to declare a -revert to release the new server and restart the old one.""" +There are two types of migration operation: a cold migration and a live +migration. + +A cold migration operation is implemented as a resize operation +using the same flavor as the old server. This means that, like resize, migrate +works by shutting down the original server, creating a new server using the +same flavor and copying the contents of the original disk into a new one. +As with resize, the migrate operation is a two-step process for the user: +the first step is to perform the migrate, and the second step is to either +confirm (verify) success and release the old server, or to declare a revert +to release the new server and restart the old one. + +By comparison, a live migration operation does not involve shutting the server +down, and is a one-step process that does not require a confirmation or revert +to finish. +""" ) def get_parser(self, prog_name): @@ -4267,7 +4276,7 @@ def get_parser(self, prog_name): metavar='', help=_('Server (name or ID)'), ) - phase_group = parser.add_mutually_exclusive_group() + phase_group = parser.add_mutually_exclusive_group(required=True) phase_group.add_argument( '--flavor', metavar='', @@ -5017,8 +5026,8 @@ def take_action(self, parsed_args): ip_address_family, ) - cmd = ' '.join(['ssh', ip_address] + args) - LOG.debug(f"ssh command: {cmd}") + cmd = ' '.join(['ssh', ip_address, *args]) + LOG.debug('ssh command: %s', cmd) # we intentionally pass through user-provided arguments and run this in # the user's shell os.system(cmd) # noqa: S605 diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 0684764706..9de1c4cbb2 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -180,7 +180,7 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None): return parsed_name return obj['id'] if obj['name'] == parsed_name else parsed_name # diaper defense in case parsing the token fails - except Exception: # noqa + except Exception: return parsed_name @@ -256,6 +256,37 @@ def find_project(identity_client, name_or_id, domain_name_or_id=None): ) +def find_project_id_sdk( + identity_client, + name_or_id, + domain_name_or_id=None, + *, + validate_actor_existence=True, + validate_domain_actor_existence=None, +): + if domain_name_or_id is None: + return _find_sdk_id( + identity_client.find_project, + name_or_id=name_or_id, + validate_actor_existence=validate_actor_existence, + ) + + if validate_domain_actor_existence is None: + validate_domain_actor_existence = validate_actor_existence + + domain_id = find_domain_id_sdk( + identity_client, + name_or_id=domain_name_or_id, + validate_actor_existence=validate_domain_actor_existence, + ) + return _find_sdk_id( + identity_client.find_project, + name_or_id=name_or_id, + validate_actor_existence=validate_actor_existence, + domain_id=domain_id, + ) + + def find_user(identity_client, name_or_id, domain_name_or_id=None): if domain_name_or_id is None: return _find_identity_resource( diff --git a/openstackclient/identity/v2_0/role_assignment.py b/openstackclient/identity/v2_0/role_assignment.py index 0aa800ef84..eab7522a5f 100644 --- a/openstackclient/identity/v2_0/role_assignment.py +++ b/openstackclient/identity/v2_0/role_assignment.py @@ -17,7 +17,7 @@ from osc_lib import utils from openstackclient import command -from openstackclient.i18n import _ # noqa +from openstackclient.i18n import _ class ListRoleAssignment(command.Lister): diff --git a/openstackclient/identity/v2_0/service.py b/openstackclient/identity/v2_0/service.py index 5e8dca7354..a93a953260 100644 --- a/openstackclient/identity/v2_0/service.py +++ b/openstackclient/identity/v2_0/service.py @@ -160,7 +160,9 @@ def take_action(self, parsed_args): for service, service_endpoints in endpoints.items(): if service_endpoints: info = {"type": service} - info.update(service_endpoints[0]) + # FIXME(stephenfin): The return type for this in ksa is + # wrong + info.update(service_endpoints[0]) # type: ignore return zip(*sorted(info.items())) msg = _( diff --git a/openstackclient/identity/v3/federation_protocol.py b/openstackclient/identity/v3/federation_protocol.py index 850ec0ac7f..3e1dea14db 100644 --- a/openstackclient/identity/v3/federation_protocol.py +++ b/openstackclient/identity/v3/federation_protocol.py @@ -26,6 +26,15 @@ LOG = logging.getLogger(__name__) +def _format_protocol(protocol): + columns = ('name', 'idp_id', 'mapping_id') + column_headers = ('id', 'identity_provider', 'mapping') + return ( + column_headers, + utils.get_item_properties(protocol, columns), + ) + + class CreateProtocol(command.ShowOne): _description = _("Create new federation protocol") @@ -58,21 +67,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - protocol = identity_client.federation.protocols.create( - protocol_id=parsed_args.federation_protocol, - identity_provider=parsed_args.identity_provider, - mapping=parsed_args.mapping, + identity_client = self.app.client_manager.sdk_connection.identity + + protocol = identity_client.create_federation_protocol( + name=parsed_args.federation_protocol, + idp_id=parsed_args.identity_provider, + mapping_id=parsed_args.mapping, ) - info = dict(protocol._info) - # NOTE(marek-denis): Identity provider is not included in a response - # from Keystone, however it should be listed to the user. Add it - # manually to the output list, simply reusing value provided by the - # user. - info['identity_provider'] = parsed_args.identity_provider - info['mapping'] = info.pop('mapping_id') - info.pop('links', None) - return zip(*sorted(info.items())) + + return _format_protocol(protocol) class DeleteProtocol(command.Command): @@ -99,12 +102,15 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity + result = 0 for i in parsed_args.federation_protocol: try: - identity_client.federation.protocols.delete( - parsed_args.identity_provider, i + identity_client.delete_federation_protocol( + idp_id=parsed_args.identity_provider, + protocol=i, + ignore_missing=False, ) except Exception as e: result += 1 @@ -140,9 +146,9 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - protocols = identity_client.federation.protocols.list( + protocols = identity_client.federation_protocols( parsed_args.identity_provider ) columns = ('id', 'mapping') @@ -181,21 +187,16 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - protocol = identity_client.federation.protocols.update( - parsed_args.identity_provider, - parsed_args.federation_protocol, - parsed_args.mapping, - ) - info = dict(protocol._info) - # NOTE(marek-denis): Identity provider is not included in a response - # from Keystone, however it should be listed to the user. Add it - # manually to the output list, simply reusing value provided by the - # user. - info['identity_provider'] = parsed_args.identity_provider - info['mapping'] = info.pop('mapping_id') - return zip(*sorted(info.items())) + kwargs = {'idp_id': parsed_args.identity_provider} + if parsed_args.federation_protocol: + kwargs['name'] = parsed_args.federation_protocol + if parsed_args.mapping: + kwargs['mapping_id'] = parsed_args.mapping + + protocol = identity_client.update_federation_protocol(**kwargs) + return _format_protocol(protocol) class ShowProtocol(command.ShowOne): @@ -220,12 +221,10 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - protocol = identity_client.federation.protocols.get( - parsed_args.identity_provider, parsed_args.federation_protocol + protocol = identity_client.get_federation_protocol( + idp_id=parsed_args.identity_provider, + protocol=parsed_args.federation_protocol, ) - info = dict(protocol._info) - info['mapping'] = info.pop('mapping_id') - info.pop('links', None) - return zip(*sorted(info.items())) + return _format_protocol(protocol) diff --git a/openstackclient/identity/v3/identity_provider.py b/openstackclient/identity/v3/identity_provider.py index f1af03f05c..c085afea1e 100644 --- a/openstackclient/identity/v3/identity_provider.py +++ b/openstackclient/identity/v3/identity_provider.py @@ -188,13 +188,13 @@ def get_parser(self, prog_name): parser.add_argument( '--id', metavar='', - help=_('The Identity Providers’ ID attribute'), + help=_('Filter identity providers by ID'), ) parser.add_argument( '--enabled', dest='enabled', action='store_true', - help=_('The Identity Providers that are enabled will be returned'), + help=_('List only enabled identity providers'), ) return parser diff --git a/openstackclient/identity/v3/limit.py b/openstackclient/identity/v3/limit.py index 15da04369f..94f6bcaf03 100644 --- a/openstackclient/identity/v3/limit.py +++ b/openstackclient/identity/v3/limit.py @@ -25,6 +25,28 @@ LOG = logging.getLogger(__name__) +def _format_limit(limit): + columns = ( + "description", + "id", + "project_id", + "region_id", + "resource_limit", + "resource_name", + "service_id", + ) + column_headers = ( + "description", + "id", + "project_id", + "region_id", + "resource_limit", + "resource_name", + "service_id", + ) + return (column_headers, utils.get_item_properties(limit, columns)) + + class CreateLimit(command.ShowOne): _description = _("Create a limit") @@ -46,6 +68,7 @@ def get_parser(self, prog_name): required=True, help=_('Project to associate the resource limit to'), ) + common_utils.add_project_domain_option_to_parser(parser) parser.add_argument( '--service', metavar='', @@ -67,47 +90,33 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - - project = common_utils.find_project( - identity_client, parsed_args.project + identity_client = self.app.client_manager.sdk_connection.identity + + kwargs = { + "resource_name": parsed_args.resource_name, + "resource_limit": parsed_args.resource_limit, + } + if parsed_args.description: + kwargs["description"] = parsed_args.description + + kwargs["project_id"] = common_utils.find_project_id_sdk( + identity_client, + parsed_args.project, + domain_name_or_id=parsed_args.project_domain, ) - service = common_utils.find_service( + + kwargs["service_id"] = common_utils.find_service_sdk( identity_client, parsed_args.service - ) - region = None + ).id + if parsed_args.region: - if 'None' not in parsed_args.region: - # NOTE (vishakha): Due to bug #1799153 and for any another - # related case where GET resource API does not support the - # filter by name, osc_lib.utils.find_resource() method cannot - # be used because that method try to fall back to list all the - # resource if requested resource cannot be get via name. Which - # ends up with NoUniqueMatch error. - # So osc_lib.utils.find_resource() function cannot be used for - # 'regions', using common_utils.get_resource() instead. - region = common_utils.get_resource( - identity_client.regions, parsed_args.region - ) - else: - self.log.warning( - _( - "Passing 'None' to indicate no region is deprecated. " - "Instead, don't pass --region." - ) - ) + kwargs["region_id"] = identity_client.get_region( + parsed_args.region + ).id - limit = identity_client.limits.create( - project, - service, - parsed_args.resource_name, - parsed_args.resource_limit, - description=parsed_args.description, - region=region, - ) + limit = identity_client.create_limit(**kwargs) - limit._info.pop('links', None) - return zip(*sorted(limit._info.items())) + return _format_limit(limit) class ListLimit(command.Lister): @@ -136,50 +145,41 @@ def get_parser(self, prog_name): metavar='', help=_('List resource limits associated with project'), ) + common_utils.add_project_domain_option_to_parser(parser) + return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - service = None + kwargs = {} if parsed_args.service: - service = common_utils.find_service( + kwargs["service_id"] = common_utils.find_service_sdk( identity_client, parsed_args.service ) - region = None + if parsed_args.region: - if 'None' not in parsed_args.region: - # NOTE (vishakha): Due to bug #1799153 and for any another - # related case where GET resource API does not support the - # filter by name, osc_lib.utils.find_resource() method cannot - # be used because that method try to fall back to list all the - # resource if requested resource cannot be get via name. Which - # ends up with NoUniqueMatch error. - # So osc_lib.utils.find_resource() function cannot be used for - # 'regions', using common_utils.get_resource() instead. - region = common_utils.get_resource( - identity_client.regions, parsed_args.region - ) - else: - self.log.warning( - _( - "Passing 'None' to indicate no region is deprecated. " - "Instead, don't pass --region." - ) - ) + kwargs["region_id"] = identity_client.get_region( + parsed_args.region + ).id - project = None if parsed_args.project: - project = utils.find_resource( - identity_client.projects, parsed_args.project + project_domain_id = None + if parsed_args.project_domain: + project_domain_id = common_utils.find_domain_id_sdk( + identity_client, parsed_args.project_domain + ) + + kwargs["project_id"] = common_utils._find_sdk_id( + identity_client.find_project, + name_or_id=parsed_args.project, + domain_id=project_domain_id, ) - limits = identity_client.limits.list( - service=service, - resource_name=parsed_args.resource_name, - region=region, - project=project, - ) + if parsed_args.resource_name: + kwargs["resource_name"] = parsed_args.resource_name + + limits = identity_client.limits(**kwargs) columns = ( 'ID', @@ -209,10 +209,9 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - limit = identity_client.limits.get(parsed_args.limit_id) - limit._info.pop('links', None) - return zip(*sorted(limit._info.items())) + identity_client = self.app.client_manager.sdk_connection.identity + limit = identity_client.get_limit(parsed_args.limit_id) + return _format_limit(limit) class SetLimit(command.ShowOne): @@ -240,17 +239,16 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - - limit = identity_client.limits.update( - parsed_args.limit_id, - description=parsed_args.description, - resource_limit=parsed_args.resource_limit, - ) + identity_client = self.app.client_manager.sdk_connection.identity - limit._info.pop('links', None) + kwargs = {} + if parsed_args.description: + kwargs["description"] = parsed_args.description + if parsed_args.resource_limit: + kwargs["resource_limit"] = parsed_args.resource_limit + limit = identity_client.update_limit(parsed_args.limit_id, **kwargs) - return zip(*sorted(limit._info.items())) + return _format_limit(limit) class DeleteLimit(command.Command): @@ -262,17 +260,20 @@ def get_parser(self, prog_name): 'limit_id', metavar='', nargs="+", - help=_('Limit to delete (ID)'), + help=_( + 'Limit to delete (ID) ' + '(repeat option to remove multiple limits)' + ), ) return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity errors = 0 for limit_id in parsed_args.limit_id: try: - identity_client.limits.delete(limit_id) + identity_client.delete_limit(limit_id) except Exception as e: errors += 1 LOG.error( diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index e70a8a5011..f3d7fea237 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -17,7 +17,7 @@ import logging -from keystoneauth1 import exceptions as ks_exc +from openstack import exceptions as sdk_exc from osc_lib.cli import parseractions from osc_lib import exceptions from osc_lib import utils @@ -30,6 +30,21 @@ LOG = logging.getLogger(__name__) +def _format_project(project): + # NOTE(0weng): Projects allow unknown attributes in the body, so extract + # the column names separately. + (column_headers, columns) = utils.get_osc_show_columns_for_sdk_resource( + project, + {'is_enabled': 'enabled'}, + ['links', 'location', 'parents_as_ids', 'subtree_as_ids'], + ) + + return ( + column_headers, + utils.get_item_properties(project, columns), + ) + + class CreateProject(command.ShowOne): _description = _("Create new project") @@ -90,22 +105,13 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - - domain = None - if parsed_args.domain: - domain = common.find_domain(identity_client, parsed_args.domain).id - - parent = None - if parsed_args.parent: - parent = utils.find_resource( - identity_client.projects, - parsed_args.parent, - ).id + identity_client = self.app.client_manager.sdk_connection.identity kwargs = {} + if parsed_args.properties: kwargs = parsed_args.properties.copy() + if 'is_domain' in kwargs.keys(): if kwargs['is_domain'].lower() == "true": kwargs['is_domain'] = True @@ -114,35 +120,55 @@ def take_action(self, parsed_args): elif kwargs['is_domain'].lower() == "none": kwargs['is_domain'] = None - kwargs['tags'] = list(set(parsed_args.tags)) + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if parsed_args.name: + kwargs['name'] = parsed_args.name + + domain = None + if parsed_args.domain: + domain = common.find_domain_id_sdk( + identity_client, parsed_args.domain + ) + kwargs['domain_id'] = domain + + if parsed_args.parent: + kwargs['parent_id'] = common.find_project_id_sdk( + identity_client, + parsed_args.parent, + domain_name_or_id=domain, + ) + + kwargs['is_enabled'] = parsed_args.enabled + + if parsed_args.tags: + kwargs['tags'] = list(set(parsed_args.tags)) - options = {} if parsed_args.immutable is not None: - options['immutable'] = parsed_args.immutable + kwargs['options'] = {'immutable': parsed_args.immutable} try: - project = identity_client.projects.create( - name=parsed_args.name, - domain=domain, - parent=parent, - description=parsed_args.description, - enabled=parsed_args.enabled, - options=options, + project = identity_client.create_project( **kwargs, ) - except ks_exc.Conflict: + except sdk_exc.ConflictException: if parsed_args.or_show: - project = utils.find_resource( - identity_client.projects, - parsed_args.name, - domain_id=domain, - ) + if parsed_args.domain: + project = identity_client.find_project( + parsed_args.name, + domain_id=domain, + ignore_missing=False, + ) + else: + project = identity_client.find_project( + parsed_args.name, ignore_missing=False + ) LOG.info(_('Returning existing project %s'), project.name) else: raise - project._info.pop('links') - return zip(*sorted(project._info.items())) + return _format_project(project) class DeleteProject(command.Command): @@ -171,23 +197,19 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - domain = None - if parsed_args.domain: - domain = common.find_domain(identity_client, parsed_args.domain) errors = 0 for project in parsed_args.projects: try: - if domain is not None: - project_obj = utils.find_resource( - identity_client.projects, project, domain_id=domain.id - ) - else: - project_obj = utils.find_resource( - identity_client.projects, project - ) - identity_client.projects.delete(project_obj.id) + project = common.find_project_id_sdk( + identity_client, + project, + domain_name_or_id=parsed_args.domain, + validate_actor_existence=True, + validate_domain_actor_existence=False, + ) + identity_client.delete_project(project) except Exception as e: errors += 1 LOG.error( @@ -268,38 +290,46 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - columns: tuple[str, ...] = ('ID', 'Name') + identity_client = self.app.client_manager.sdk_connection.identity + + column_headers: tuple[str, ...] = ('ID', 'Name') + if parsed_args.long: + column_headers += ('Domain ID', 'Description', 'Enabled') + + columns: tuple[str, ...] = ('id', 'name') if parsed_args.long: - columns += ('Domain ID', 'Description', 'Enabled') + columns += ('domain_id', 'description', 'is_enabled') + kwargs = {} domain_id = None if parsed_args.domain: - domain_id = common.find_domain( + domain_id = common.find_domain_id_sdk( identity_client, parsed_args.domain - ).id - kwargs['domain'] = domain_id + ) + kwargs['domain_id'] = domain_id if parsed_args.parent: - parent_id = common.find_project( - identity_client, parsed_args.parent - ).id - kwargs['parent'] = parent_id + parent_id = common.find_project_id_sdk( + identity_client, + parsed_args.parent, + domain_name_or_id=domain_id, + ) + kwargs['parent_id'] = parent_id + user = None if parsed_args.user: if parsed_args.domain: - user_id = utils.find_resource( - identity_client.users, + user = common.find_user_id_sdk( + identity_client, parsed_args.user, - domain_id=domain_id, - ).id + domain_name_or_id=domain_id, + ) else: - user_id = utils.find_resource( - identity_client.users, parsed_args.user - ).id - - kwargs['user'] = user_id + user = common.find_user_id_sdk( + identity_client, + parsed_args.user, + ) if parsed_args.is_enabled is not None: kwargs['is_enabled'] = parsed_args.is_enabled @@ -308,32 +338,29 @@ def take_action(self, parsed_args): if parsed_args.my_projects: # NOTE(adriant): my-projects supersedes all the other filters. - kwargs = {'user': self.app.client_manager.auth_ref.user_id} + kwargs = {} + user = self.app.client_manager.auth_ref.user_id - try: - data = identity_client.projects.list(**kwargs) - except ks_exc.Forbidden: - # NOTE(adriant): if no filters, assume a forbidden is non-admin - # wanting their own project list. - if not kwargs: - user = self.app.client_manager.auth_ref.user_id - data = identity_client.projects.list(user=user) - else: - raise + if user: + data = list(identity_client.user_projects(user, **kwargs)) + else: + try: + data = list(identity_client.projects(**kwargs)) + except sdk_exc.ForbiddenException: + # NOTE(adriant): if no filters, assume a forbidden is non-admin + # wanting their own project list. + if not kwargs: + user = self.app.client_manager.auth_ref.user_id + data = list(identity_client.user_projects(user)) + else: + raise if parsed_args.sort: - data = utils.sort_items(data, parsed_args.sort) + data = list(utils.sort_items(data, parsed_args.sort)) return ( - columns, - ( - utils.get_item_properties( - s, - columns, - formatters={}, - ) - for s in data - ), + column_headers, + (utils.get_item_properties(s, columns) for s in data), ) @@ -392,11 +419,7 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - - project = common.find_project( - identity_client, parsed_args.project, parsed_args.domain - ) + identity_client = self.app.client_manager.sdk_connection.identity kwargs = {} if parsed_args.name: @@ -409,9 +432,50 @@ def take_action(self, parsed_args): kwargs['options'] = {'immutable': parsed_args.immutable} if parsed_args.properties: kwargs.update(parsed_args.properties) - tag.update_tags_in_args(parsed_args, project, kwargs) - identity_client.projects.update(project.id, **kwargs) + if parsed_args.domain: + domain = common.find_domain_id_sdk( + identity_client, + parsed_args.domain, + validate_actor_existence=False, + ) + project = identity_client.find_project( + parsed_args.project, + domain_id=domain, + ignore_missing=True, + ) + else: + project = identity_client.find_project( + parsed_args.project, + ignore_missing=True, + ) + + if ( + parsed_args.tags + or parsed_args.remove_tags + or parsed_args.clear_tags + ): + existing_tags = [] + if project: + existing_tags = project.tags + + if parsed_args.clear_tags: + kwargs['tags'] = [] + else: + existing_tags_set = set(existing_tags) + if parsed_args.remove_tags: + tags = sorted( + existing_tags_set - set(parsed_args.remove_tags) + ) + if parsed_args.tags: + tags = sorted( + existing_tags_set.union(set(parsed_args.tags)) + ) + kwargs['tags'] = tags + + project_id = project.id if project else parsed_args.project + + identity_client.update_project(project_id, **kwargs) class ShowProject(command.ShowOne): @@ -444,31 +508,36 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - project_str = common._get_token_resource( - identity_client, 'project', parsed_args.project, parsed_args.domain - ) + kwargs = {} + domain = None if parsed_args.domain: - domain = common.find_domain(identity_client, parsed_args.domain) - project = utils.find_resource( - identity_client.projects, project_str, domain_id=domain.id - ) - else: - project = utils.find_resource( - identity_client.projects, project_str + domain = common.find_domain_id_sdk( + identity_client, parsed_args.domain ) - if parsed_args.parents or parsed_args.children: - # NOTE(RuiChen): utils.find_resource() can't pass kwargs, - # if id query hit the result at first, so call - # identity manager.get() with kwargs directly. - project = identity_client.projects.get( - project.id, - parents_as_ids=parsed_args.parents, - subtree_as_ids=parsed_args.children, - ) + kwargs['domain_id'] = domain + + # Get project id first; otherwise, find_project() can't find + # parents/children if only project name was given + project = common.find_project_id_sdk( + identity_client, + parsed_args.project, + domain_name_or_id=domain, + validate_actor_existence=False, + validate_domain_actor_existence=False, + ) + + # Include these options as query parameters if they are provided + if parsed_args.parents: + kwargs['parents_as_ids'] = True + if parsed_args.children: + kwargs['subtree_as_ids'] = True + + project = identity_client.find_project( + project, **kwargs, ignore_missing=False + ) - project._info.pop('links') - return zip(*sorted(project._info.items())) + return _format_project(project) diff --git a/openstackclient/identity/v3/registered_limit.py b/openstackclient/identity/v3/registered_limit.py index e0afb4133f..4be829bc21 100644 --- a/openstackclient/identity/v3/registered_limit.py +++ b/openstackclient/identity/v3/registered_limit.py @@ -25,6 +25,29 @@ LOG = logging.getLogger(__name__) +def _format_registered_limit(registered_limit): + columns = ( + 'default_limit', + 'description', + 'id', + 'region_id', + 'resource_name', + 'service_id', + ) + column_headers = ( + 'default_limit', + 'description', + 'id', + 'region_id', + 'resource_name', + 'service_id', + ) + return ( + column_headers, + utils.get_item_properties(registered_limit, columns), + ) + + class CreateRegisteredLimit(command.ShowOne): _description = _("Create a registered limit") @@ -64,43 +87,28 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity + + kwargs = {} + + if parsed_args.description: + kwargs["description"] = parsed_args.description + + kwargs["service_id"] = common_utils.find_service_sdk( + identity_client, parsed_args.service + ).id - service = utils.find_resource( - identity_client.services, parsed_args.service - ) - region = None if parsed_args.region: - if 'None' not in parsed_args.region: - # NOTE (vishakha): Due to bug #1799153 and for any another - # related case where GET resource API does not support the - # filter by name, osc_lib.utils.find_resource() method cannot - # be used because that method try to fall back to list all the - # resource if requested resource cannot be get via name. Which - # ends up with NoUniqueMatch error. - # So osc_lib.utils.find_resource() function cannot be used for - # 'regions', using common_utils.get_resource() instead. - region = common_utils.get_resource( - identity_client.regions, parsed_args.region - ) - else: - self.log.warning( - _( - "Passing 'None' to indicate no region is deprecated. " - "Instead, don't pass --region." - ) - ) + kwargs["region_id"] = identity_client.get_region( + parsed_args.region + ).id - registered_limit = identity_client.registered_limits.create( - service, - parsed_args.resource_name, - parsed_args.default_limit, - description=parsed_args.description, - region=region, - ) + kwargs["resource_name"] = parsed_args.resource_name + kwargs["default_limit"] = parsed_args.default_limit + + registered_limit = identity_client.create_registered_limit(**kwargs) - registered_limit._info.pop('links', None) - return zip(*sorted(registered_limit._info.items())) + return _format_registered_limit(registered_limit) class DeleteRegisteredLimit(command.Command): @@ -112,17 +120,22 @@ def get_parser(self, prog_name): 'registered_limits', metavar='', nargs="+", - help=_('Registered limit(s) to delete (ID)'), + help=_( + 'Registered limit(s) to delete (ID) ' + '(repeat option to remove multiple registered limits)' + ), ) return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity errors = 0 for registered_limit_id in parsed_args.registered_limits: try: - identity_client.registered_limits.delete(registered_limit_id) + identity_client.delete_registered_limit( + registered_limit_id, ignore_missing=False + ) except Exception as e: errors += 1 from pprint import pprint @@ -170,40 +183,22 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - service = None + kwargs = {} if parsed_args.service: - service = common_utils.find_service( + kwargs["service_id"] = common_utils.find_service_sdk( identity_client, parsed_args.service - ) - region = None + ).id if parsed_args.region: - if 'None' not in parsed_args.region: - # NOTE (vishakha): Due to bug #1799153 and for any another - # related case where GET resource API does not support the - # filter by name, osc_lib.utils.find_resource() method cannot - # be used because that method try to fall back to list all the - # resource if requested resource cannot be get via name. Which - # ends up with NoUniqueMatch error. - # So osc_lib.utils.find_resource() function cannot be used for - # 'regions', using common_utils.get_resource() instead. - region = common_utils.get_resource( - identity_client.regions, parsed_args.region - ) - else: - self.log.warning( - _( - "Passing 'None' to indicate no region is deprecated. " - "Instead, don't pass --region." - ) - ) + kwargs["region_id"] = identity_client.get_region( + parsed_args.region + ).id - registered_limits = identity_client.registered_limits.list( - service=service, - resource_name=parsed_args.resource_name, - region=region, - ) + if parsed_args.resource_name: + kwargs["resource_name"] = parsed_args.resource_name + + registered_limits = identity_client.registered_limits(**kwargs) columns = ( 'ID', @@ -273,44 +268,33 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity + identity_client = self.app.client_manager.sdk_connection.identity - service = None + kwargs = {} if parsed_args.service: - service = common_utils.find_service( + kwargs["service_id"] = common_utils.find_service_sdk( identity_client, parsed_args.service - ) + ).id + + if parsed_args.resource_name: + kwargs["resource_name"] = parsed_args.resource_name + + if parsed_args.default_limit: + kwargs["default_limit"] = parsed_args.default_limit + + if parsed_args.description: + kwargs["description"] = parsed_args.description - region = None if parsed_args.region: - if 'None' not in parsed_args.region: - # NOTE (vishakha): Due to bug #1799153 and for any another - # related case where GET resource API does not support the - # filter by name, osc_lib.utils.find_resource() method cannot - # be used because that method try to fall back to list all the - # resource if requested resource cannot be get via name. Which - # ends up with NoUniqueMatch error. - # So osc_lib.utils.find_resource() function cannot be used for - # 'regions', using common_utils.get_resource() instead. - region = common_utils.get_resource( - identity_client.regions, parsed_args.region - ) - else: - self.log.warning( - _("Passing 'None' to indicate no region is deprecated.") - ) + kwargs["region_id"] = identity_client.get_region( + parsed_args.region + ).id - registered_limit = identity_client.registered_limits.update( - parsed_args.registered_limit_id, - service=service, - resource_name=parsed_args.resource_name, - default_limit=parsed_args.default_limit, - description=parsed_args.description, - region=region, + registered_limit = identity_client.update_registered_limit( + parsed_args.registered_limit_id, **kwargs ) - registered_limit._info.pop('links', None) - return zip(*sorted(registered_limit._info.items())) + return _format_registered_limit(registered_limit) class ShowRegisteredLimit(command.ShowOne): @@ -326,9 +310,8 @@ def get_parser(self, prog_name): return parser def take_action(self, parsed_args): - identity_client = self.app.client_manager.identity - registered_limit = identity_client.registered_limits.get( + identity_client = self.app.client_manager.sdk_connection.identity + registered_limit = identity_client.get_registered_limit( parsed_args.registered_limit_id ) - registered_limit._info.pop('links', None) - return zip(*sorted(registered_limit._info.items())) + return _format_registered_limit(registered_limit) diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 3c580d6f8b..d8f66e1eba 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -416,8 +416,10 @@ def take_action(self, parsed_args): return ( ('ID', 'Name', 'Domain'), ( - utils.get_item_properties(s, ('id', 'name')) - + (domain.name,) + ( + *utils.get_item_properties(s, ('id', 'name')), + domain.name, + ) for s in data ), ) diff --git a/openstackclient/identity/v3/tag.py b/openstackclient/identity/v3/tag.py index 41493c9936..f49a1ca98f 100644 --- a/openstackclient/identity/v3/tag.py +++ b/openstackclient/identity/v3/tag.py @@ -123,14 +123,3 @@ def add_tag_option_to_parser_for_set(parser, resource_name): ) % resource_name, ) - - -def update_tags_in_args(parsed_args, obj, args): - if parsed_args.clear_tags: - args['tags'] = [] - obj.tags = [] - if parsed_args.remove_tags: - args['tags'] = sorted(set(obj.tags) - set(parsed_args.remove_tags)) - return - if parsed_args.tags: - args['tags'] = sorted(set(obj.tags).union(set(parsed_args.tags))) diff --git a/openstackclient/image/v1/image.py b/openstackclient/image/v1/image.py index 0ea7eca710..cf0f689198 100644 --- a/openstackclient/image/v1/image.py +++ b/openstackclient/image/v1/image.py @@ -299,7 +299,7 @@ def take_action(self, parsed_args): volume_client.volumes, parsed_args.volume, ) - response, body = volume_client.volumes.upload_to_image( + _response, body = volume_client.volumes.upload_to_image( source_volume.id, parsed_args.force, parsed_args.name, @@ -498,7 +498,7 @@ def take_action(self, parsed_args): if parsed_args.property: # NOTE(dtroyer): coerce to a list to subscript it in py3 - attr, value = list(parsed_args.property.items())[0] + attr, value = next(iter(parsed_args.property.items())) api_utils.simple_filter( images, attr=attr, diff --git a/openstackclient/image/v2/image.py b/openstackclient/image/v2/image.py index cbb6d874de..a7db86c111 100644 --- a/openstackclient/image/v2/image.py +++ b/openstackclient/image/v2/image.py @@ -622,7 +622,7 @@ def _take_action_volume(self, parsed_args): ) # TODO(stephenfin): These should be an error in a future # version - LOG.warning(msg % opt_name) + LOG.warning(msg, opt_name) source_volume = volume_client.find_volume( parsed_args.volume, ignore_missing=False @@ -1719,6 +1719,7 @@ def get_parser(self, prog_name): ) stores_group.add_argument( '--all-stores', + action='store_true', help=_( "Make image available to all stores " "(either '--store' or '--all-stores' required with the " diff --git a/openstackclient/network/client.py b/openstackclient/network/client.py index 5165e1ecdc..82a47fb25e 100644 --- a/openstackclient/network/client.py +++ b/openstackclient/network/client.py @@ -23,6 +23,7 @@ API_VERSION_OPTION = 'os_network_api_version' API_NAME = 'network' API_VERSIONS = ('2.0', '2') +API_EXTENSIONS = ('taas',) def make_client(instance): diff --git a/openstackclient/network/utils.py b/openstackclient/network/utils.py index 30c0da0645..43ffea679d 100644 --- a/openstackclient/network/utils.py +++ b/openstackclient/network/utils.py @@ -78,7 +78,7 @@ def str2dict(strdict: str) -> dict[str, str]: else: kvlist[i - 1] = f"{kvlist[i - 1]};{kv}" for kv in kvlist: - key, sep, value = kv.partition(':') + key, value = kv.split(':', 1) result[key] = value return result @@ -173,11 +173,15 @@ def is_ipv6_protocol(protocol): # However, while the OSC CLI doesn't document the protocol, # the code must still handle it. In addition, handle both # protocol names and numbers. - if ( - protocol is not None - and protocol.startswith('ipv6-') - or protocol in ['icmpv6', '41', '43', '44', '58', '59', '60'] - ): + if (protocol is not None and protocol.startswith('ipv6-')) or protocol in [ + 'icmpv6', + '41', + '43', + '44', + '58', + '59', + '60', + ]: return True else: return False diff --git a/openstackclient/network/v2/network_segment_range.py b/openstackclient/network/v2/network_segment_range.py index c8b9a0e4c4..f0172a6850 100644 --- a/openstackclient/network/v2/network_segment_range.py +++ b/openstackclient/network/v2/network_segment_range.py @@ -406,14 +406,21 @@ def take_action(self, parsed_args): for s in data: props = utils.get_item_properties(s, columns) if ( - parsed_args.available - and _is_prop_empty(columns, props, 'available') - or parsed_args.unavailable - and not _is_prop_empty(columns, props, 'available') - or parsed_args.used - and _is_prop_empty(columns, props, 'used') - or parsed_args.unused - and not _is_prop_empty(columns, props, 'used') + ( + parsed_args.available + and _is_prop_empty(columns, props, 'available') + ) + or ( + parsed_args.unavailable + and not _is_prop_empty(columns, props, 'available') + ) + or ( + parsed_args.used and _is_prop_empty(columns, props, 'used') + ) + or ( + parsed_args.unused + and not _is_prop_empty(columns, props, 'used') + ) ): continue if parsed_args.long: diff --git a/openstackclient/shell.py b/openstackclient/shell.py index 743ed2bc82..7add82a96f 100644 --- a/openstackclient/shell.py +++ b/openstackclient/shell.py @@ -128,15 +128,42 @@ def _load_plugins(self): }, ) - # Command groups deal only with major versions - version = '.v' + version_opt.replace('.', '_').split('_')[0] - cmd_group = 'openstack.' + api.replace('-', '_') + version + # Build our command group which we expect to look like: + # + # openstack..vN + # + # Note that command groups deal only with major versions + cmd_group = '.'.join( + [ + 'openstack', + api.replace('-', '_'), + 'v' + version_opt.replace('.', '_').split('_')[0], + ] + ) self.command_manager.add_command_group(cmd_group) self.log.debug( '%(name)s API version %(version)s, cmd group %(group)s', {'name': api, 'version': version_opt, 'group': cmd_group}, ) + mod_extensions = getattr(mod, 'API_EXTENSIONS', None) + if not mod_extensions: + continue + + for extension in mod_extensions: + extension_cmd_group = '.'.join([cmd_group, extension]) + self.command_manager.add_command_group(extension_cmd_group) + self.log.debug( + '%(name)s API version %(version)s ' + '(%(extension)s extension), cmd group %(group)s', + { + 'name': api, + 'version': version_opt, + 'extension': extension, + 'group': cmd_group, + }, + ) + def _load_commands(self): """Load commands via cliff/stevedore diff --git a/openstackclient/tests/functional/base.py b/openstackclient/tests/functional/base.py index 96c9accf6b..ee30062619 100644 --- a/openstackclient/tests/functional/base.py +++ b/openstackclient/tests/functional/base.py @@ -92,7 +92,7 @@ def openstack( format_args.append('-f json') output = execute( - ' '.join(['openstack'] + auth_args + [cmd] + format_args), + ' '.join(['openstack', *auth_args, cmd, *format_args]), fail_ok=fail_ok, ) diff --git a/openstackclient/tests/functional/common/test_extension.py b/openstackclient/tests/functional/common/test_extension.py index c65f52db51..1a8af9bc89 100644 --- a/openstackclient/tests/functional/common/test_extension.py +++ b/openstackclient/tests/functional/common/test_extension.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar + from tempest.lib import exceptions as tempest_exc from openstackclient.tests.functional import base @@ -21,6 +23,8 @@ class ExtensionTests(base.TestCase): """Functional tests for extension""" + haz_network: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/common/test_quota.py b/openstackclient/tests/functional/common/test_quota.py index 373b178c15..a9bcaec742 100644 --- a/openstackclient/tests/functional/common/test_quota.py +++ b/openstackclient/tests/functional/common/test_quota.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from tempest.lib.common.utils import data_utils @@ -25,7 +26,8 @@ class QuotaTests(base.TestCase): test runs as these may run in parallel and otherwise step on each other. """ - PROJECT_NAME: str + haz_network: ClassVar[bool] + PROJECT_NAME: ClassVar[str] @classmethod def setUpClass(cls): diff --git a/openstackclient/tests/functional/compute/v2/test_flavor.py b/openstackclient/tests/functional/compute/v2/test_flavor.py index 4a0ff4883c..7f4cc43e71 100644 --- a/openstackclient/tests/functional/compute/v2/test_flavor.py +++ b/openstackclient/tests/functional/compute/v2/test_flavor.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional import base @@ -19,6 +20,7 @@ class FlavorTests(base.TestCase): """Functional tests for flavor.""" PROJECT_NAME = uuid.uuid4().hex + PROJECT_ID: ClassVar[str] @classmethod def setUpClass(cls): @@ -28,7 +30,7 @@ def setUpClass(cls): "project create --enable " + cls.PROJECT_NAME, parse_output=True, ) - cls.project_id = cmd_output["id"] + cls.PROJECT_ID = cmd_output["id"] @classmethod def tearDownClass(cls): diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 6afa2c7c0e..c00d9dc555 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -13,6 +13,7 @@ import itertools import json import time +from typing import ClassVar import uuid from tempest.lib import exceptions @@ -25,6 +26,8 @@ class ServerTests(common.ComputeTestCase): """Functional tests for openstack server commands""" + haz_network: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/identity/v2/common.py b/openstackclient/tests/functional/identity/v2/common.py index dd2e271933..c17c713eb6 100644 --- a/openstackclient/tests/functional/identity/v2/common.py +++ b/openstackclient/tests/functional/identity/v2/common.py @@ -11,6 +11,7 @@ # under the License. import os +from typing import ClassVar import unittest import fixtures @@ -57,19 +58,22 @@ class IdentityTests(base.TestCase): CATALOG_LIST_HEADERS = ['Name', 'Type', 'Endpoints'] ENDPOINT_LIST_HEADERS = ['ID', 'Region', 'Service Name', 'Service Type'] + PROJECT_NAME: ClassVar[str] + PROJECT_DESCRIPTION: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() # create dummy project - cls.project_name = data_utils.rand_name('TestProject') - cls.project_description = data_utils.rand_name('description') + cls.PROJECT_NAME = data_utils.rand_name('TestProject') + cls.PROJECT_DESCRIPTION = data_utils.rand_name('description') try: cls.openstack( '--os-identity-api-version 2 ' 'project create ' - f'--description {cls.project_description} ' + f'--description {cls.PROJECT_DESCRIPTION} ' '--enable ' - f'{cls.project_name}' + f'{cls.PROJECT_NAME}' ) except tempest_exceptions.CommandFailed: # Good chance this is due to Identity v2 admin not being enabled @@ -83,7 +87,7 @@ def tearDownClass(cls): try: cls.openstack( '--os-identity-api-version 2 ' - f'project delete {cls.project_name}' + f'project delete {cls.PROJECT_NAME}' ) finally: super().tearDownClass() @@ -125,7 +129,7 @@ def _create_dummy_user(self, add_clean_up=True): email = data_utils.rand_name() + '@example.com' raw_output = self.openstack( 'user create ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--password {password} ' f'--email {email} ' '--enable ' diff --git a/openstackclient/tests/functional/identity/v2/test_role.py b/openstackclient/tests/functional/identity/v2/test_role.py index ec6134012f..58a04f5e2a 100644 --- a/openstackclient/tests/functional/identity/v2/test_role.py +++ b/openstackclient/tests/functional/identity/v2/test_role.py @@ -39,14 +39,14 @@ def test_role_add(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}', ) @@ -58,13 +58,13 @@ def test_role_remove(self): username = self._create_dummy_user() add_raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}' ) del_raw_output = self.openstack( 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}' ) diff --git a/openstackclient/tests/functional/identity/v3/common.py b/openstackclient/tests/functional/identity/v3/common.py index 9f21374ff5..53733b9657 100644 --- a/openstackclient/tests/functional/identity/v3/common.py +++ b/openstackclient/tests/functional/identity/v3/common.py @@ -11,6 +11,7 @@ # under the License. import os +from typing import ClassVar import fixtures from tempest.lib.common.utils import data_utils @@ -147,30 +148,35 @@ class IdentityTests(base.TestCase): 'Region ID', ] + DOMAIN_NAME: ClassVar[str] + DOMAIN_DESCRIPTION: ClassVar[str] + PROJECT_NAME: ClassVar[str] + PROJECT_DESCRIPTION: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() # create dummy domain - cls.domain_name = data_utils.rand_name('TestDomain') - cls.domain_description = data_utils.rand_name('description') + cls.DOMAIN_NAME = data_utils.rand_name('TestDomain') + cls.DOMAIN_DESCRIPTION = data_utils.rand_name('description') cls.openstack( '--os-identity-api-version 3 ' 'domain create ' - f'--description {cls.domain_description} ' + f'--description {cls.DOMAIN_DESCRIPTION} ' '--enable ' - f'{cls.domain_name}' + f'{cls.DOMAIN_NAME}' ) # create dummy project - cls.project_name = data_utils.rand_name('TestProject') - cls.project_description = data_utils.rand_name('description') + cls.PROJECT_NAME = data_utils.rand_name('TestProject') + cls.PROJECT_DESCRIPTION = data_utils.rand_name('description') cls.openstack( '--os-identity-api-version 3 ' 'project create ' - f'--domain {cls.domain_name} ' - f'--description {cls.project_description} ' + f'--domain {cls.DOMAIN_NAME} ' + f'--description {cls.PROJECT_DESCRIPTION} ' '--enable ' - f'{cls.project_name}' + f'{cls.PROJECT_NAME}' ) @classmethod @@ -179,15 +185,15 @@ def tearDownClass(cls): # delete dummy project cls.openstack( '--os-identity-api-version 3 ' - f'project delete {cls.project_name}' + f'project delete {cls.PROJECT_NAME}' ) # disable and delete dummy domain cls.openstack( '--os-identity-api-version 3 ' - f'domain set --disable {cls.domain_name}' + f'domain set --disable {cls.DOMAIN_NAME}' ) cls.openstack( - f'--os-identity-api-version 3 domain delete {cls.domain_name}' + f'--os-identity-api-version 3 domain delete {cls.DOMAIN_NAME}' ) finally: super().tearDownClass() @@ -213,9 +219,9 @@ def _create_dummy_user(self, add_clean_up=True): description = data_utils.rand_name('description') raw_output = self.openstack( 'user create ' - f'--domain {self.domain_name} ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--password {password} ' f'--email {email} ' f'--description {description} ' @@ -262,14 +268,14 @@ def _create_dummy_group(self, add_clean_up=True): description = data_utils.rand_name('description') raw_output = self.openstack( 'group create ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--description {description} ' f'{group_name}' ) if add_clean_up: self.addCleanup( self.openstack, - f'group delete --domain {self.domain_name} {group_name}', + f'group delete --domain {self.DOMAIN_NAME} {group_name}', ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.GROUP_FIELDS) @@ -295,14 +301,14 @@ def _create_dummy_project(self, add_clean_up=True): project_description = data_utils.rand_name('description') self.openstack( 'project create ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--description {project_description} ' f'--enable {project_name}' ) if add_clean_up: self.addCleanup( self.openstack, - f'project delete --domain {self.domain_name} {project_name}', + f'project delete --domain {self.DOMAIN_NAME} {project_name}', ) return project_name diff --git a/openstackclient/tests/functional/identity/v3/test_group.py b/openstackclient/tests/functional/identity/v3/test_group.py index a2e41d813a..0292d866bf 100644 --- a/openstackclient/tests/functional/identity/v3/test_group.py +++ b/openstackclient/tests/functional/identity/v3/test_group.py @@ -28,7 +28,7 @@ def test_group_list(self): def test_group_list_with_domain(self): group_name = self._create_dummy_group() - raw_output = self.openstack(f'group list --domain {self.domain_name}') + raw_output = self.openstack(f'group list --domain {self.DOMAIN_NAME}') items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) self.assertIn(group_name, raw_output) @@ -36,14 +36,14 @@ def test_group_list_with_domain(self): def test_group_delete(self): group_name = self._create_dummy_group(add_clean_up=False) raw_output = self.openstack( - f'group delete --domain {self.domain_name} {group_name}' + f'group delete --domain {self.DOMAIN_NAME} {group_name}' ) self.assertEqual(0, len(raw_output)) def test_group_show(self): group_name = self._create_dummy_group() raw_output = self.openstack( - f'group show --domain {self.domain_name} {group_name}' + f'group show --domain {self.DOMAIN_NAME} {group_name}' ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.GROUP_FIELDS) @@ -53,20 +53,20 @@ def test_group_set(self): new_group_name = data_utils.rand_name('NewTestGroup') raw_output = self.openstack( 'group set ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--name {new_group_name} ' f'{group_name}' ) self.assertEqual(0, len(raw_output)) raw_output = self.openstack( - f'group show --domain {self.domain_name} {new_group_name}' + f'group show --domain {self.DOMAIN_NAME} {new_group_name}' ) group = self.parse_show_as_object(raw_output) self.assertEqual(new_group_name, group['name']) # reset group name to make sure it will be cleaned up raw_output = self.openstack( 'group set ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--name {group_name} ' f'{new_group_name}' ) @@ -77,15 +77,15 @@ def test_group_add_user(self): username = self._create_dummy_user() raw_output = self.openstack( 'group add user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}' ) self.addCleanup( self.openstack, 'group remove user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}', ) self.assertOutput('', raw_output) @@ -95,22 +95,22 @@ def test_group_contains_user(self): username = self._create_dummy_user() raw_output = self.openstack( 'group add user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}' ) self.addCleanup( self.openstack, 'group remove user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}', ) self.assertOutput('', raw_output) raw_output = self.openstack( 'group contains user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}' ) self.assertEqual( @@ -123,14 +123,14 @@ def test_group_remove_user(self): username = self._create_dummy_user() add_raw_output = self.openstack( 'group add user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}' ) remove_raw_output = self.openstack( 'group remove user ' - f'--group-domain {self.domain_name} ' - f'--user-domain {self.domain_name} ' + f'--group-domain {self.DOMAIN_NAME} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{group_name} {username}' ) self.assertOutput('', add_raw_output) diff --git a/openstackclient/tests/functional/identity/v3/test_limit.py b/openstackclient/tests/functional/identity/v3/test_limit.py index 8c0bbcd6a9..497d33a9c3 100644 --- a/openstackclient/tests/functional/identity/v3/test_limit.py +++ b/openstackclient/tests/functional/identity/v3/test_limit.py @@ -100,6 +100,53 @@ def test_limit_create_with_project_name(self): self.assert_show_fields(items, self.LIMIT_FIELDS) registered_limit_id = self._create_dummy_registered_limit() + def test_limit_create_with_project_domain(self): + registered_limit_id = self._create_dummy_registered_limit() + raw_output = self.openstack( + f'registered limit show {registered_limit_id}', + cloud=SYSTEM_CLOUD, + ) + items = self.parse_show(raw_output) + service_id = self._extract_value_from_items('service_id', items) + resource_name = self._extract_value_from_items('resource_name', items) + + raw_output = self.openstack(f'service show {service_id}') + items = self.parse_show(raw_output) + service_name = self._extract_value_from_items('name', items) + + project_name = self._create_dummy_project() + raw_output = self.openstack( + f'project show {project_name}', + cloud=SYSTEM_CLOUD, + ) + items = self.parse_show(raw_output) + domain_id = self._extract_value_from_items('domain_id', items) + + params = { + 'project_name': project_name, + 'project_domain': domain_id, + 'service_name': service_name, + 'resource_name': resource_name, + 'resource_limit': 15, + } + raw_output = self.openstack( + 'limit create' + ' --project {project_name}' + ' --project-domain {project_domain}' + ' --service {service_name}' + ' --resource-limit {resource_limit}' + ' {resource_name}'.format(**params), + cloud=SYSTEM_CLOUD, + ) + items = self.parse_show(raw_output) + limit_id = self._extract_value_from_items('id', items) + self.addCleanup( + self.openstack, f'limit delete {limit_id}', cloud=SYSTEM_CLOUD + ) + + self.assert_show_fields(items, self.LIMIT_FIELDS) + registered_limit_id = self._create_dummy_registered_limit() + def test_limit_create_with_service_id(self): self._create_dummy_limit() diff --git a/openstackclient/tests/functional/identity/v3/test_project.py b/openstackclient/tests/functional/identity/v3/test_project.py index 7a66c18518..8394377e4c 100644 --- a/openstackclient/tests/functional/identity/v3/test_project.py +++ b/openstackclient/tests/functional/identity/v3/test_project.py @@ -21,7 +21,7 @@ def test_project_create(self): description = data_utils.rand_name('description') raw_output = self.openstack( 'project create ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--description {description} ' '--enable ' '--property k1=v1 ' @@ -30,7 +30,7 @@ def test_project_create(self): ) self.addCleanup( self.openstack, - f'project delete --domain {self.domain_name} {project_name}', + f'project delete --domain {self.DOMAIN_NAME} {project_name}', ) items = self.parse_show(raw_output) show_fields = list(self.PROJECT_FIELDS) @@ -43,7 +43,7 @@ def test_project_create(self): def test_project_delete(self): project_name = self._create_dummy_project(add_clean_up=False) raw_output = self.openstack( - f'project delete --domain {self.domain_name} {project_name}' + f'project delete --domain {self.DOMAIN_NAME} {project_name}' ) self.assertEqual(0, len(raw_output)) @@ -55,7 +55,7 @@ def test_project_list(self): def test_project_list_with_domain(self): project_name = self._create_dummy_project() raw_output = self.openstack( - f'project list --domain {self.domain_name}' + f'project list --domain {self.DOMAIN_NAME}' ) items = self.parse_listing(raw_output) self.assert_table_structure(items, common.BASIC_LIST_HEADERS) @@ -75,7 +75,7 @@ def test_project_set(self): self.assertEqual(0, len(raw_output)) # check project details raw_output = self.openstack( - f'project show --domain {self.domain_name} {new_project_name}' + f'project show --domain {self.DOMAIN_NAME} {new_project_name}' ) items = self.parse_show(raw_output) fields = list(self.PROJECT_FIELDS) @@ -92,7 +92,7 @@ def test_project_set(self): def test_project_show(self): raw_output = self.openstack( - f'project show --domain {self.domain_name} {self.project_name}' + f'project show --domain {self.DOMAIN_NAME} {self.PROJECT_NAME}' ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.PROJECT_FIELDS) @@ -101,10 +101,10 @@ def test_project_show_with_parents_children(self): output = self.openstack( 'project show ' '--parents --children ' - f'--domain {self.domain_name} ' - f'{self.project_name}', + f'--domain {self.DOMAIN_NAME} ' + f'{self.PROJECT_NAME}', parse_output=True, ) - for attr_name in self.PROJECT_FIELDS + ['parents', 'subtree']: + for attr_name in [*self.PROJECT_FIELDS, 'parents', 'subtree']: self.assertIn(attr_name, output) - self.assertEqual(self.project_name, output.get('name')) + self.assertEqual(self.PROJECT_NAME, output.get('name')) diff --git a/openstackclient/tests/functional/identity/v3/test_role.py b/openstackclient/tests/functional/identity/v3/test_role.py index 3237c0bfb4..f4189aeef0 100644 --- a/openstackclient/tests/functional/identity/v3/test_role.py +++ b/openstackclient/tests/functional/identity/v3/test_role.py @@ -76,19 +76,19 @@ def test_role_add(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{role_name}', ) self.assertEqual(0, len(raw_output)) @@ -98,20 +98,20 @@ def test_role_add_inherited(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' '--inherited ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' '--inherited ' f'{role_name}', ) @@ -122,18 +122,18 @@ def test_role_remove(self): username = self._create_dummy_user() add_raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{role_name}' ) remove_raw_output = self.openstack( 'role remove ' - f'--project {self.project_name} ' - f'--project-domain {self.domain_name} ' + f'--project {self.PROJECT_NAME} ' + f'--project-domain {self.DOMAIN_NAME} ' f'--user {username} ' - f'--user-domain {self.domain_name} ' + f'--user-domain {self.DOMAIN_NAME} ' f'{role_name}' ) self.assertEqual(0, len(add_raw_output)) diff --git a/openstackclient/tests/functional/identity/v3/test_role_assignment.py b/openstackclient/tests/functional/identity/v3/test_role_assignment.py index 1255841afe..373f7a8ef9 100644 --- a/openstackclient/tests/functional/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/functional/identity/v3/test_role_assignment.py @@ -79,14 +79,14 @@ def test_role_assignment_list_group_domain(self): ) raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--group {group_name} --group-domain {domain_name_A} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--group {group_name} --group-domain {domain_name_A} ' f'{role_name}', ) @@ -108,20 +108,20 @@ def test_role_assignment_list_domain(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--user {username} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--domain {self.domain_name} ' + f'--domain {self.DOMAIN_NAME} ' f'--user {username} ' f'{role_name}', ) self.assertEqual(0, len(raw_output)) raw_output = self.openstack( - f'role assignment list --domain {self.domain_name} ' + f'role assignment list --domain {self.DOMAIN_NAME} ' ) items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) @@ -141,14 +141,14 @@ def test_role_assignment_list_user_domain(self): ) raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} --user-domain {domain_name_A} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} --user-domain {domain_name_A} ' f'{role_name}', ) @@ -214,20 +214,20 @@ def test_role_assignment_list_project(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}' ) self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' f'{role_name}', ) self.assertEqual(0, len(raw_output)) raw_output = self.openstack( - f'role assignment list --project {self.project_name} ' + f'role assignment list --project {self.PROJECT_NAME} ' ) items = self.parse_listing(raw_output) self.assert_table_structure(items, self.ROLE_ASSIGNMENT_LIST_HEADERS) @@ -302,7 +302,7 @@ def test_role_assignment_list_inherited(self): username = self._create_dummy_user() raw_output = self.openstack( 'role add ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' '--inherited ' f'{role_name}' @@ -310,7 +310,7 @@ def test_role_assignment_list_inherited(self): self.addCleanup( self.openstack, 'role remove ' - f'--project {self.project_name} ' + f'--project {self.PROJECT_NAME} ' f'--user {username} ' '--inherited ' f'{role_name}', diff --git a/openstackclient/tests/functional/identity/v3/test_user.py b/openstackclient/tests/functional/identity/v3/test_user.py index dd56293e63..a5e7f1adbe 100644 --- a/openstackclient/tests/functional/identity/v3/test_user.py +++ b/openstackclient/tests/functional/identity/v3/test_user.py @@ -22,7 +22,7 @@ def test_user_create(self): def test_user_delete(self): username = self._create_dummy_user(add_clean_up=False) raw_output = self.openstack( - f'user delete --domain {self.domain_name} {username}' + f'user delete --domain {self.DOMAIN_NAME} {username}' ) self.assertEqual(0, len(raw_output)) @@ -34,7 +34,7 @@ def test_user_list(self): def test_user_set(self): username = self._create_dummy_user() raw_output = self.openstack( - f'user show --domain {self.domain_name} {username}' + f'user show --domain {self.DOMAIN_NAME} {username}' ) user = self.parse_show_as_object(raw_output) new_username = data_utils.rand_name('NewTestUser') @@ -46,7 +46,7 @@ def test_user_set(self): ) self.assertEqual(0, len(raw_output)) raw_output = self.openstack( - f'user show --domain {self.domain_name} {new_username}' + f'user show --domain {self.DOMAIN_NAME} {new_username}' ) updated_user = self.parse_show_as_object(raw_output) self.assertEqual(user['id'], updated_user['id']) @@ -57,7 +57,7 @@ def test_user_set_default_project_id(self): project_name = self._create_dummy_project() # get original user details raw_output = self.openstack( - f'user show --domain {self.domain_name} {username}' + f'user show --domain {self.DOMAIN_NAME} {username}' ) user = self.parse_show_as_object(raw_output) # update user @@ -67,19 +67,19 @@ def test_user_set_default_project_id(self): '--project-domain {project_domain} ' '{id}'.format( project=project_name, - project_domain=self.domain_name, + project_domain=self.DOMAIN_NAME, id=user['id'], ) ) self.assertEqual(0, len(raw_output)) # get updated user details raw_output = self.openstack( - f'user show --domain {self.domain_name} {username}' + f'user show --domain {self.DOMAIN_NAME} {username}' ) updated_user = self.parse_show_as_object(raw_output) # get project details raw_output = self.openstack( - f'project show --domain {self.domain_name} {project_name}' + f'project show --domain {self.DOMAIN_NAME} {project_name}' ) project = self.parse_show_as_object(raw_output) # check updated user details @@ -89,7 +89,7 @@ def test_user_set_default_project_id(self): def test_user_show(self): username = self._create_dummy_user() raw_output = self.openstack( - f'user show --domain {self.domain_name} {username}' + f'user show --domain {self.DOMAIN_NAME} {username}' ) items = self.parse_show(raw_output) self.assert_show_fields(items, self.USER_FIELDS) diff --git a/openstackclient/tests/functional/image/base.py b/openstackclient/tests/functional/image/base.py index d948f81551..174c47c637 100644 --- a/openstackclient/tests/functional/image/base.py +++ b/openstackclient/tests/functional/image/base.py @@ -16,9 +16,4 @@ class BaseImageTests(base.TestCase): """Functional tests for Image commands""" - @classmethod - def setUpClass(cls): - super().setUpClass() - # TODO(dtroyer): maybe do image API discovery here to determine - # what is available, it isn't in the service catalog - cls.haz_v1_api = False + ... diff --git a/openstackclient/tests/functional/image/v1/__init__.py b/openstackclient/tests/functional/image/v1/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/openstackclient/tests/functional/image/v1/test_image.py b/openstackclient/tests/functional/image/v1/test_image.py deleted file mode 100644 index c4118babba..0000000000 --- a/openstackclient/tests/functional/image/v1/test_image.py +++ /dev/null @@ -1,97 +0,0 @@ -# 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 uuid - -import fixtures - -from openstackclient.tests.functional.image import base - - -class ImageTests(base.BaseImageTests): - """Functional tests for Image commands""" - - def setUp(self): - super().setUp() - - if not self.haz_v1_api: - self.skipTest('No Image v1 API present') - - ver_fixture = fixtures.EnvironmentVariable('OS_IMAGE_API_VERSION', '1') - self.useFixture(ver_fixture) - - self.name = uuid.uuid4().hex - output = self.openstack( - 'image create ' + self.name, - parse_output=True, - ) - self.image_id = output["id"] - self.assertOutput(self.name, output['name']) - - def tearDown(self): - try: - self.openstack('image delete ' + self.image_id) - finally: - super().tearDown() - - def test_image_list(self): - output = self.openstack('image list') - self.assertIn(self.name, [img['Name'] for img in output]) - - def test_image_attributes(self): - """Test set, unset, show on attributes, tags and properties""" - - # Test explicit attributes - self.openstack( - 'image set ' - + '--min-disk 4 ' - + '--min-ram 5 ' - + '--disk-format qcow2 ' - + '--public ' - + self.name - ) - output = self.openstack( - 'image show ' + self.name, - parse_output=True, - ) - self.assertEqual( - 4, - output["min_disk"], - ) - self.assertEqual( - 5, - output["min_ram"], - ) - self.assertEqual( - 'qcow2', - output['disk_format'], - ) - self.assertTrue( - output["is_public"], - ) - - # Test properties - self.openstack( - 'image set ' - + '--property a=b ' - + '--property c=d ' - + '--public ' - + self.name - ) - output = self.openstack( - 'image show ' + self.name, - parse_output=True, - ) - self.assertEqual( - {'a': 'b', 'c': 'd'}, - output["properties"], - ) diff --git a/openstackclient/tests/functional/network/v2/common.py b/openstackclient/tests/functional/network/v2/common.py index 248758a843..00c0b57e3d 100644 --- a/openstackclient/tests/functional/network/v2/common.py +++ b/openstackclient/tests/functional/network/v2/common.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional import base @@ -18,6 +19,8 @@ class NetworkTests(base.TestCase): """Functional tests for Network commands""" + haz_network: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() @@ -33,7 +36,7 @@ def setUp(self): class NetworkTagTests(NetworkTests): """Functional tests with tag operation""" - base_command: str + base_command: ClassVar[str] def test_tag_operation(self): # Get project IDs @@ -84,7 +87,7 @@ def _list_tag_check(self, project_id, expected): parse_output=True, ) for name, tags in expected: - net = [n for n in cmd_output if n['Name'] == name][0] + net = next(n for n in cmd_output if n['Name'] == name) self.assertEqual(set(tags), set(net['Tags'])) def _create_resource_for_tag_test(self, name, args): diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index a1b11a44a3..c2ce00d481 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -11,6 +11,7 @@ # under the License. import random +from typing import ClassVar import uuid from openstackclient.tests.functional.network.v2 import common @@ -19,6 +20,11 @@ class FloatingIpTests(common.NetworkTests): """Functional tests for floating ip""" + EXTERNAL_NETWORK_NAME: ClassVar[str] + EXTERNAL_NETWORK_ID: ClassVar[str] + PRIVATE_NETWORK_NAME: ClassVar[str] + PRIVATE_NETWORK_ID: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() @@ -32,14 +38,14 @@ def setUpClass(cls): 'network create ' + '--external ' + cls.EXTERNAL_NETWORK_NAME, parse_output=True, ) - cls.external_network_id = json_output["id"] + cls.EXTERNAL_NETWORK_ID = json_output["id"] # Create a private network for the port json_output = cls.openstack( 'network create ' + cls.PRIVATE_NETWORK_NAME, parse_output=True, ) - cls.private_network_id = json_output["id"] + cls.PRIVATE_NETWORK_ID = json_output["id"] @classmethod def tearDownClass(cls): @@ -59,8 +65,8 @@ def setUp(self): super().setUp() # Verify setup - self.assertIsNotNone(self.external_network_id) - self.assertIsNotNone(self.private_network_id) + self.assertIsNotNone(self.EXTERNAL_NETWORK_ID) + self.assertIsNotNone(self.PRIVATE_NETWORK_ID) def _create_subnet(self, network_name, subnet_name): subnet_id = None diff --git a/openstackclient/tests/functional/network/v2/test_ip_availability.py b/openstackclient/tests/functional/network/v2/test_ip_availability.py index 1cdbd487a5..be1df9108a 100644 --- a/openstackclient/tests/functional/network/v2/test_ip_availability.py +++ b/openstackclient/tests/functional/network/v2/test_ip_availability.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional.network.v2 import common @@ -18,6 +19,9 @@ class IPAvailabilityTests(common.NetworkTests): """Functional tests for IP availability""" + NAME: ClassVar[str] + NETWORK_NAME: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py index c80643e31d..268794298c 100644 --- a/openstackclient/tests/functional/network/v2/test_network_meter_rule.py +++ b/openstackclient/tests/functional/network/v2/test_network_meter_rule.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import unittest import uuid @@ -22,8 +23,8 @@ class TestMeterRule(common.NetworkTests): """Functional tests for meter rule""" - METER_ID: str - METER_RULE_ID: str + METER_ID: ClassVar[str] + METER_NAME: ClassVar[str] @classmethod def setUpClass(cls): diff --git a/openstackclient/tests/functional/network/v2/test_network_segment.py b/openstackclient/tests/functional/network/v2/test_network_segment.py index 03f5daf743..df26bcc8d7 100644 --- a/openstackclient/tests/functional/network/v2/test_network_segment.py +++ b/openstackclient/tests/functional/network/v2/test_network_segment.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional.network.v2 import common @@ -18,6 +19,10 @@ class NetworkSegmentTests(common.NetworkTests): """Functional tests for network segment""" + NETWORK_NAME: ClassVar[str] + NETWORK_ID: ClassVar[str] + PHYSICAL_NETWORK_NAME: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/network/v2/test_subnet.py b/openstackclient/tests/functional/network/v2/test_subnet.py index 2ec987e9b8..6af7c94541 100644 --- a/openstackclient/tests/functional/network/v2/test_subnet.py +++ b/openstackclient/tests/functional/network/v2/test_subnet.py @@ -11,6 +11,7 @@ # under the License. import random +from typing import ClassVar import uuid from openstackclient.tests.functional.network.v2 import common @@ -21,19 +22,22 @@ class SubnetTests(common.NetworkTagTests): base_command = 'subnet' + NETWORK_NAME: ClassVar[str] + NETWORK_ID: ClassVar[str] + @classmethod def setUpClass(cls): super().setUpClass() - if cls.haz_network: - cls.NETWORK_NAME = uuid.uuid4().hex - # Create a network for the all subnet tests - cmd_output = cls.openstack( - 'network create ' + cls.NETWORK_NAME, - parse_output=True, - ) - # Get network_id for assertEqual - cls.NETWORK_ID = cmd_output["id"] + cls.NETWORK_NAME = uuid.uuid4().hex + + # Create a network for the all subnet tests + cmd_output = cls.openstack( + 'network create ' + cls.NETWORK_NAME, + parse_output=True, + ) + # Get network_id for assertEqual + cls.NETWORK_ID = cmd_output["id"] @classmethod def tearDownClass(cls): diff --git a/openstackclient/tests/functional/object/v1/common.py b/openstackclient/tests/functional/object/v1/common.py index 036731da50..f3cc9aea93 100644 --- a/openstackclient/tests/functional/object/v1/common.py +++ b/openstackclient/tests/functional/object/v1/common.py @@ -10,12 +10,16 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar + from openstackclient.tests.functional import base class ObjectStoreTests(base.TestCase): """Functional tests for Object Store commands""" + haz_object_store: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/volume/v2/common.py b/openstackclient/tests/functional/volume/v2/common.py index f15d4d961f..ed55030d27 100644 --- a/openstackclient/tests/functional/volume/v2/common.py +++ b/openstackclient/tests/functional/volume/v2/common.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar + import fixtures from openstackclient.tests.functional.volume import base @@ -18,6 +20,8 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests.""" + haz_volume_v2: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py index e5daded1bd..a5302033b6 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_snapshot.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional.volume.v2 import common @@ -18,24 +19,27 @@ class VolumeSnapshotTests(common.BaseVolumeTests): """Functional tests for volume snapshot.""" - VOLLY = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + VOLUME_ID: ClassVar[str] @classmethod def setUpClass(cls): super().setUpClass() # create a volume for all tests to create snapshot cmd_output = cls.openstack( - 'volume create ' + '--size 1 ' + cls.VOLLY, + 'volume create ' + '--size 1 ' + cls.VOLUME_NAME, parse_output=True, ) - cls.wait_for_status('volume', cls.VOLLY, 'available') + cls.wait_for_status('volume', cls.VOLUME_NAME, 'available') cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): try: - cls.wait_for_status('volume', cls.VOLLY, 'available') - raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.wait_for_status('volume', cls.VOLUME_NAME, 'available') + raw_output = cls.openstack( + 'volume delete --force ' + cls.VOLUME_NAME + ) cls.assertOutput('', raw_output) finally: super().tearDownClass() @@ -44,7 +48,10 @@ def test_volume_snapshot_delete(self): """Test create, delete multiple""" name1 = uuid.uuid4().hex cmd_output = self.openstack( - 'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, + 'volume snapshot create ' + + name1 + + ' --volume ' + + self.VOLUME_NAME, parse_output=True, ) self.assertEqual( @@ -54,7 +61,10 @@ def test_volume_snapshot_delete(self): name2 = uuid.uuid4().hex cmd_output = self.openstack( - 'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, + 'volume snapshot create ' + + name2 + + ' --volume ' + + self.VOLUME_NAME, parse_output=True, ) self.assertEqual( @@ -76,7 +86,10 @@ def test_volume_snapshot_list(self): """Test create, list filter""" name1 = uuid.uuid4().hex cmd_output = self.openstack( - 'volume snapshot create ' + name1 + ' --volume ' + self.VOLLY, + 'volume snapshot create ' + + name1 + + ' --volume ' + + self.VOLUME_NAME, parse_output=True, ) self.addCleanup(self.wait_for_delete, 'volume snapshot', name1) @@ -97,7 +110,10 @@ def test_volume_snapshot_list(self): name2 = uuid.uuid4().hex cmd_output = self.openstack( - 'volume snapshot create ' + name2 + ' --volume ' + self.VOLLY, + 'volume snapshot create ' + + name2 + + ' --volume ' + + self.VOLUME_NAME, parse_output=True, ) self.addCleanup(self.wait_for_delete, 'volume snapshot', name2) @@ -146,7 +162,7 @@ def test_volume_snapshot_list(self): # Test list --volume cmd_output = self.openstack( - 'volume snapshot list ' + '--volume ' + self.VOLLY, + 'volume snapshot list ' + '--volume ' + self.VOLUME_NAME, parse_output=True, ) names = [x["Name"] for x in cmd_output] @@ -169,7 +185,7 @@ def test_volume_snapshot_set(self): cmd_output = self.openstack( 'volume snapshot create ' + '--volume ' - + self.VOLLY + + self.VOLUME_NAME + ' --description aaaa ' + '--property Alpha=a ' + name, diff --git a/openstackclient/tests/functional/volume/v2/test_volume_type.py b/openstackclient/tests/functional/volume/v2/test_volume_type.py index 80bf85a5bb..c80e6c154e 100644 --- a/openstackclient/tests/functional/volume/v2/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v2/test_volume_type.py @@ -173,9 +173,9 @@ def test_encryption_type(self): 'volume type list --encryption-type', parse_output=True, ) - encryption_output = [ + encryption_output = next( t['Encryption'] for t in cmd_output if t['Name'] == encryption_type - ][0] + ) expected = { 'provider': 'LuksEncryptor', 'cipher': 'aes-xts-plain64', diff --git a/openstackclient/tests/functional/volume/v3/common.py b/openstackclient/tests/functional/volume/v3/common.py index cbab39275c..038991313a 100644 --- a/openstackclient/tests/functional/volume/v3/common.py +++ b/openstackclient/tests/functional/volume/v3/common.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar + import fixtures from openstackclient.tests.functional.volume import base @@ -18,6 +20,8 @@ class BaseVolumeTests(base.BaseVolumeTests): """Base class for Volume functional tests.""" + haz_volume_v3: ClassVar[bool] + @classmethod def setUpClass(cls): super().setUpClass() diff --git a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py index b84bb0368b..6921c997dd 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_snapshot.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from typing import ClassVar import uuid from openstackclient.tests.functional.volume.v3 import common @@ -18,24 +19,27 @@ class VolumeSnapshotTests(common.BaseVolumeTests): """Functional tests for volume snapshot.""" - VOLLY = uuid.uuid4().hex + VOLUME_NAME = uuid.uuid4().hex + VOLUME_ID: ClassVar[str] @classmethod def setUpClass(cls): super().setUpClass() # create a test volume used by all snapshot tests cmd_output = cls.openstack( - 'volume create ' + '--size 1 ' + cls.VOLLY, + 'volume create ' + '--size 1 ' + cls.VOLUME_NAME, parse_output=True, ) - cls.wait_for_status('volume', cls.VOLLY, 'available') + cls.wait_for_status('volume', cls.VOLUME_NAME, 'available') cls.VOLUME_ID = cmd_output['id'] @classmethod def tearDownClass(cls): try: - cls.wait_for_status('volume', cls.VOLLY, 'available') - raw_output = cls.openstack('volume delete --force ' + cls.VOLLY) + cls.wait_for_status('volume', cls.VOLUME_NAME, 'available') + raw_output = cls.openstack( + 'volume delete --force ' + cls.VOLUME_NAME + ) cls.assertOutput('', raw_output) finally: super().tearDownClass() @@ -47,7 +51,7 @@ def test_volume_snapshot(self): cmd_output = self.openstack( 'volume snapshot create ' + '--volume ' - + self.VOLLY + + self.VOLUME_NAME + ' --description aaaa ' + '--property Alpha=a ' + name, @@ -83,7 +87,7 @@ def test_volume_snapshot(self): # list volume snapshot --volume cmd_output = self.openstack( - 'volume snapshot list ' + '--volume ' + self.VOLLY, + 'volume snapshot list ' + '--volume ' + self.VOLUME_NAME, parse_output=True, ) names = [x["Name"] for x in cmd_output] diff --git a/openstackclient/tests/functional/volume/v3/test_volume_type.py b/openstackclient/tests/functional/volume/v3/test_volume_type.py index 421b3224f6..cda56b0041 100644 --- a/openstackclient/tests/functional/volume/v3/test_volume_type.py +++ b/openstackclient/tests/functional/volume/v3/test_volume_type.py @@ -173,9 +173,9 @@ def test_encryption_type(self): 'volume type list --encryption-type', parse_output=True, ) - encryption_output = [ + encryption_output = next( t['Encryption'] for t in cmd_output if t['Name'] == encryption_type - ][0] + ) expected = { 'provider': 'LuksEncryptor', 'cipher': 'aes-xts-plain64', diff --git a/openstackclient/tests/unit/common/test_extension.py b/openstackclient/tests/unit/common/test_extension.py index dd684312c1..1141a81a30 100644 --- a/openstackclient/tests/unit/common/test_extension.py +++ b/openstackclient/tests/unit/common/test_extension.py @@ -294,7 +294,7 @@ def setUp(self): self.cmd = extension.ShowExtension(self.app, None) - self.app.client_manager.network.find_extension.return_value = ( + self.network_client.find_extension.return_value = ( self.extension_details ) @@ -322,7 +322,7 @@ def test_show_all_options(self): columns, data = self.cmd.take_action(parsed_args) - self.app.client_manager.network.find_extension.assert_called_with( + self.network_client.find_extension.assert_called_with( self.extension_details.alias, ignore_missing=False ) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index 7bfb2e7f2f..833d71fe76 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -458,7 +458,7 @@ def test_quota_set(self): 'floating_ips': floating_ip_num, 'fixed_ips': fix_ip_num, 'injected_files': injected_file_num, - 'injected_file_content_bytes': injected_file_size_num, # noqa: E501 + 'injected_file_content_bytes': injected_file_size_num, 'injected_file_path_bytes': injected_path_size_num, 'key_pairs': key_pair_num, 'cores': core_num, @@ -729,7 +729,7 @@ def test_quota_set_with_class(self): kwargs_compute = { 'injected_files': injected_file_num, - 'injected_file_content_bytes': injected_file_size_num, # noqa: E501 + 'injected_file_content_bytes': injected_file_size_num, 'injected_file_path_bytes': injected_path_size_num, 'key_pairs': key_pair_num, 'cores': core_num, @@ -827,7 +827,7 @@ def test_quota_set_default(self): kwargs_compute = { 'injected_files': injected_file_num, - 'injected_file_content_bytes': injected_file_size_num, # noqa: E501 + 'injected_file_content_bytes': injected_file_size_num, 'injected_file_path_bytes': injected_path_size_num, 'key_pairs': key_pair_num, 'cores': core_num, diff --git a/openstackclient/tests/unit/compute/v2/test_flavor.py b/openstackclient/tests/unit/compute/v2/test_flavor.py index 25bc8eaa76..6c9104bbbb 100644 --- a/openstackclient/tests/unit/compute/v2/test_flavor.py +++ b/openstackclient/tests/unit/compute/v2/test_flavor.py @@ -494,7 +494,8 @@ def setUp(self): 'VCPUs', 'Is Public', ) - self.columns_long = self.columns + ( + self.columns_long = ( + *self.columns, 'Swap', 'RXTX Factor', 'Properties', diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 0597312510..e88b07a4ec 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1608,8 +1608,8 @@ def find_port(name_or_id, ignore_missing): port_port2.id: port_port2, }[name_or_id] - self.app.client_manager.network.find_network.side_effect = find_network - self.app.client_manager.network.find_port.side_effect = find_port + self.network_client.find_network.side_effect = find_network + self.network_client.find_port.side_effect = find_port arglist = [ '--image', @@ -1728,7 +1728,7 @@ def test_server_create_with_network_tag(self): self.set_compute_api_version('2.43') network = network_fakes.create_one_network() - self.app.client_manager.network.find_network.return_value = network + self.network_client.find_network.return_value = network arglist = [ '--image', @@ -4865,7 +4865,7 @@ def test_server_list_column_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + columns, _data = self.cmd.take_action(parsed_args) self.compute_client.servers.assert_called_with(**self.kwargs) self.assertIn('Project ID', columns) @@ -5329,7 +5329,7 @@ def test_server_list_long_with_host_status_v216(self): ] # Add the expected host_status column and data. - columns_long = self.columns_long + ('Host Status',) + columns_long = (*self.columns_long, 'Host Status') self.data2 = tuple( ( s.id, @@ -5560,7 +5560,7 @@ def test_server_list_v269_with_partial_constructs(self): } fake_server = _server.Server(**server_dict) self.servers.append(fake_server) - columns, data = self.cmd.take_action(parsed_args) + _columns, data = self.cmd.take_action(parsed_args) # get the first three servers out since our interest is in the partial # server. next(data) @@ -5708,7 +5708,7 @@ def test_server_list_column_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + columns, _data = self.cmd.take_action(parsed_args) self.compute_client.servers.assert_called_with(**self.kwargs) self.assertIn('Project ID', columns) @@ -5860,7 +5860,7 @@ def test_server_list_column_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + columns, _data = self.cmd.take_action(parsed_args) self.compute_client.servers.assert_called_with(**self.kwargs) self.assertIn('Project ID', columns) @@ -7896,15 +7896,13 @@ def test_server_resize_no_options(self): ('server', self.server.id), ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - result = self.cmd.take_action(parsed_args) - - self.compute_client.find_server.assert_called_once_with( - self.server.id, ignore_missing=False + self.assertRaises( + test_utils.ParserException, + self.check_parser, + self.cmd, + arglist, + verifylist, ) - self.compute_client.find_flavor.assert_not_called() - self.compute_client.resize_server.assert_not_called() - self.assertIsNone(result) def test_server_resize(self): arglist = [ @@ -8898,7 +8896,7 @@ def setUp(self): None, # OS-EXT-SRV-ATTR:user_data server.PowerStateColumn( self.server.power_state - ), # OS-EXT-STS:power_state # noqa: E501 + ), # OS-EXT-STS:power_state None, # OS-EXT-STS:task_state None, # OS-EXT-STS:vm_state None, # OS-SRV-USG:launched_at diff --git a/openstackclient/tests/unit/identity/v3/test_group.py b/openstackclient/tests/unit/identity/v3/test_group.py index 598402e070..b3ebfb75c4 100644 --- a/openstackclient/tests/unit/identity/v3/test_group.py +++ b/openstackclient/tests/unit/identity/v3/test_group.py @@ -103,7 +103,7 @@ def test_group_add_user_with_error(self, mock_error): except exceptions.CommandError as e: msg = f"1 of 2 users not added to group {self._group.name}." self.assertEqual(msg, str(e)) - msg = f"{self.users[0].name} not added to group {self._group.name}: {str(sdk_exc.ResourceNotFound())}" + msg = f"{self.users[0].name} not added to group {self._group.name}: {sdk_exc.ResourceNotFound()!s}" mock_error.assert_called_once_with(msg) @@ -587,10 +587,7 @@ def test_group_list_long(self): self.identity_sdk_client.groups.assert_called_with() - long_columns = self.columns + ( - 'Domain ID', - 'Description', - ) + long_columns = (*self.columns, 'Domain ID', 'Description') datalist = ( ( self.group.id, @@ -687,7 +684,7 @@ def test_group_remove_user_with_error(self, mock_error): except exceptions.CommandError as e: msg = f"1 of 2 users not removed from group {self._group.id}." self.assertEqual(msg, str(e)) - msg = f"{self.users[0].id} not removed from group {self._group.id}: {str(sdk_exc.ResourceNotFound())}" + msg = f"{self.users[0].id} not removed from group {self._group.id}: {sdk_exc.ResourceNotFound()!s}" mock_error.assert_called_once_with(msg) diff --git a/openstackclient/tests/unit/identity/v3/test_limit.py b/openstackclient/tests/unit/identity/v3/test_limit.py index a1d180b4ba..4c2184c2aa 100644 --- a/openstackclient/tests/unit/identity/v3/test_limit.py +++ b/openstackclient/tests/unit/identity/v3/test_limit.py @@ -10,87 +10,85 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - -from keystoneauth1.exceptions import http as ksa_exceptions +from openstack import exceptions as sdk_exc +from openstack.identity.v3 import domain as _domain +from openstack.identity.v3 import limit as _limit +from openstack.identity.v3 import project as _project +from openstack.identity.v3 import region as _region +from openstack.identity.v3 import service as _service +from openstack.test import fakes as sdk_fakes from osc_lib import exceptions from openstackclient.identity.v3 import limit -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -class TestLimit(identity_fakes.TestIdentityv3): +class TestLimitCreate(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - identity_manager = self.identity_client - - self.limit_mock = identity_manager.limits - - self.services_mock = identity_manager.services - self.services_mock.reset_mock() - - self.projects_mock = identity_manager.projects - self.projects_mock.reset_mock() - - self.regions_mock = identity_manager.regions - self.regions_mock.reset_mock() + self.domain = sdk_fakes.generate_fake_resource(_domain.Domain) + self.project = sdk_fakes.generate_fake_resource( + _project.Project, domain_id=self.domain.id + ) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + self.service = sdk_fakes.generate_fake_resource(_service.Service) + self.resource_limit = 15 -class TestLimitCreate(TestLimit): - def setUp(self): - super().setUp() - - self.service = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.SERVICE), loaded=True - ) - self.services_mock.get.return_value = self.service + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + self.identity_sdk_client.find_project.return_value = self.project + self.identity_sdk_client.find_domain.return_value = self.domain - self.project = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.PROJECT), loaded=True + self.limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_name='foobars', + description=None, + resource_limit=self.resource_limit, + region_id=None, ) - self.projects_mock.get.return_value = self.project - - self.region = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.REGION), loaded=True + self.limit_with_options = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_limit=self.resource_limit, + resource_name='foobars', + description='test description', + region_id=self.region.id, ) - self.regions_mock.get.return_value = self.region self.cmd = limit.CreateLimit(self.app, None) def test_limit_create_without_options(self): - self.limit_mock.create.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.LIMIT), loaded=True - ) + self.identity_sdk_client.create_limit.return_value = self.limit - resource_limit = 15 arglist = [ '--project', - identity_fakes.project_id, + self.project.id, '--service', - identity_fakes.service_id, + self.service.id, '--resource-limit', - str(resource_limit), - identity_fakes.limit_resource_name, + str(self.resource_limit), + self.limit.resource_name, ] verifylist = [ - ('project', identity_fakes.project_id), - ('service', identity_fakes.service_id), - ('resource_name', identity_fakes.limit_resource_name), - ('resource_limit', resource_limit), + ('project', self.project.id), + ('service', self.service.id), + ('resource_name', self.limit.resource_name), + ('resource_limit', self.resource_limit), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - kwargs = {'description': None, 'region': None} - self.limit_mock.create.assert_called_with( - self.project, - self.service, - identity_fakes.limit_resource_name, - resource_limit, - **kwargs, + self.identity_sdk_client.create_limit.assert_called_with( + project_id=self.project.id, + service_id=self.service.id, + resource_name=self.limit.resource_name, + resource_limit=self.resource_limit, ) collist = ( @@ -105,55 +103,58 @@ def test_limit_create_without_options(self): self.assertEqual(collist, columns) datalist = ( None, - identity_fakes.limit_id, - identity_fakes.project_id, + self.limit.id, + self.project.id, None, - resource_limit, - identity_fakes.limit_resource_name, - identity_fakes.service_id, + self.resource_limit, + self.limit.resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_limit_create_with_options(self): - self.limit_mock.create.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.LIMIT_OPTIONS), loaded=True + self.identity_sdk_client.create_limit.return_value = ( + self.limit_with_options ) resource_limit = 15 arglist = [ '--project', - identity_fakes.project_id, + self.project.id, + '--project-domain', + self.domain.name, '--service', - identity_fakes.service_id, + self.service.id, '--resource-limit', str(resource_limit), '--region', - identity_fakes.region_id, + self.region.id, '--description', - identity_fakes.limit_description, - identity_fakes.limit_resource_name, + self.limit_with_options.description, + self.limit_with_options.resource_name, ] verifylist = [ - ('project', identity_fakes.project_id), - ('service', identity_fakes.service_id), - ('resource_name', identity_fakes.limit_resource_name), + ('project', self.project.id), + ('project_domain', self.domain.name), + ('service', self.service.id), + ('resource_name', self.limit_with_options.resource_name), ('resource_limit', resource_limit), - ('region', identity_fakes.region_id), - ('description', identity_fakes.limit_description), + ('region', self.region.id), + ('description', self.limit_with_options.description), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'description': identity_fakes.limit_description, - 'region': self.region, + 'project_id': self.project.id, + 'service_id': self.service.id, + 'region_id': self.region.id, + 'resource_name': self.limit_with_options.resource_name, + 'resource_limit': resource_limit, + 'description': self.limit_with_options.description, } - self.limit_mock.create.assert_called_with( - self.project, - self.service, - identity_fakes.limit_resource_name, - resource_limit, + self.identity_sdk_client.create_limit.assert_called_with( **kwargs, ) @@ -168,37 +169,41 @@ def test_limit_create_with_options(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.limit_description, - identity_fakes.limit_id, - identity_fakes.project_id, - identity_fakes.region_id, + self.limit_with_options.description, + self.limit_with_options.id, + self.project.id, + self.region.id, resource_limit, - identity_fakes.limit_resource_name, - identity_fakes.service_id, + self.limit_with_options.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestLimitDelete(TestLimit): +class TestLimitDelete(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() self.cmd = limit.DeleteLimit(self.app, None) def test_limit_delete(self): - self.limit_mock.delete.return_value = None + self.limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit + ) + self.identity_sdk_client.delete_limit.return_value = None - arglist = [identity_fakes.limit_id] - verifylist = [('limit_id', [identity_fakes.limit_id])] + arglist = [self.limit.id] + verifylist = [('limit_id', [self.limit.id])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.limit_mock.delete.assert_called_with(identity_fakes.limit_id) + self.identity_sdk_client.delete_limit.assert_called_with(self.limit.id) self.assertIsNone(result) def test_limit_delete_with_exception(self): - return_value = ksa_exceptions.NotFound() - self.limit_mock.delete.side_effect = return_value + self.identity_sdk_client.delete_limit.side_effect = ( + sdk_exc.ResourceNotFound + ) arglist = ['fake-limit-id'] verifylist = [('limit_id', ['fake-limit-id'])] @@ -211,24 +216,42 @@ def test_limit_delete_with_exception(self): self.assertEqual('1 of 1 limits failed to delete.', str(e)) -class TestLimitShow(TestLimit): +class TestLimitShow(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - self.limit_mock.get.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.LIMIT), loaded=True + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + self.service = sdk_fakes.generate_fake_resource(_service.Service) + + self.resource_limit = 15 + + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + self.identity_sdk_client.find_project.return_value = self.project + + self.limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_name='foobars', + description=None, + resource_limit=self.resource_limit, + region_id=None, ) + self.identity_sdk_client.get_limit.return_value = self.limit + self.cmd = limit.ShowLimit(self.app, None) def test_limit_show(self): - arglist = [identity_fakes.limit_id] - verifylist = [('limit_id', identity_fakes.limit_id)] + arglist = [self.limit.id] + verifylist = [('limit_id', self.limit.id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.limit_mock.get.assert_called_with(identity_fakes.limit_id) + self.identity_sdk_client.get_limit.assert_called_with(self.limit.id) collist = ( 'description', @@ -242,45 +265,61 @@ def test_limit_show(self): self.assertEqual(collist, columns) datalist = ( None, - identity_fakes.limit_id, - identity_fakes.project_id, + self.limit.id, + self.project.id, None, - identity_fakes.limit_resource_limit, - identity_fakes.limit_resource_name, - identity_fakes.service_id, + self.resource_limit, + self.limit.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestLimitSet(TestLimit): +class TestLimitSet(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() + + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + self.service = sdk_fakes.generate_fake_resource(_service.Service) + + self.resource_limit = 15 + + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + self.identity_sdk_client.find_project.return_value = self.project + self.cmd = limit.SetLimit(self.app, None) def test_limit_set_description(self): - limit = copy.deepcopy(identity_fakes.LIMIT) - limit['description'] = identity_fakes.limit_description - self.limit_mock.update.return_value = fakes.FakeResource( - None, limit, loaded=True + description = 'limit of foobars' + limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_name='foobars', + description=description, + resource_limit=self.resource_limit, + region_id=None, ) + self.identity_sdk_client.update_limit.return_value = limit arglist = [ '--description', - identity_fakes.limit_description, - identity_fakes.limit_id, + description, + limit.id, ] verifylist = [ - ('description', identity_fakes.limit_description), - ('limit_id', identity_fakes.limit_id), + ('description', description), + ('limit_id', limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.limit_mock.update.assert_called_with( - identity_fakes.limit_id, - description=identity_fakes.limit_description, - resource_limit=None, + self.identity_sdk_client.update_limit.assert_called_with( + limit.id, + description=description, ) collist = ( @@ -294,40 +333,44 @@ def test_limit_set_description(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.limit_description, - identity_fakes.limit_id, - identity_fakes.project_id, + description, + limit.id, + self.project.id, None, - identity_fakes.limit_resource_limit, - identity_fakes.limit_resource_name, - identity_fakes.service_id, + limit.resource_limit, + limit.resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_limit_set_resource_limit(self): resource_limit = 20 - limit = copy.deepcopy(identity_fakes.LIMIT) - limit['resource_limit'] = resource_limit - self.limit_mock.update.return_value = fakes.FakeResource( - None, limit, loaded=True + limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_name='foobars', + description=None, + resource_limit=resource_limit, + region_id=None, ) + self.identity_sdk_client.update_limit.return_value = limit arglist = [ '--resource-limit', str(resource_limit), - identity_fakes.limit_id, + limit.id, ] verifylist = [ ('resource_limit', resource_limit), - ('limit_id', identity_fakes.limit_id), + ('limit_id', limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.limit_mock.update.assert_called_with( - identity_fakes.limit_id, - description=None, + self.identity_sdk_client.update_limit.assert_called_with( + limit.id, resource_limit=resource_limit, ) @@ -343,25 +386,36 @@ def test_limit_set_resource_limit(self): self.assertEqual(collist, columns) datalist = ( None, - identity_fakes.limit_id, - identity_fakes.project_id, + limit.id, + self.project.id, None, resource_limit, - identity_fakes.limit_resource_name, - identity_fakes.service_id, + limit.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestLimitList(TestLimit): +class TestLimitList(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - self.limit_mock.list.return_value = [ - fakes.FakeResource( - None, copy.deepcopy(identity_fakes.LIMIT), loaded=True - ) - ] + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + self.service = sdk_fakes.generate_fake_resource(_service.Service) + + self.resource_limit = 15 + + self.limit = sdk_fakes.generate_fake_resource( + resource_type=_limit.Limit, + project_id=self.project.id, + service_id=self.service.id, + resource_name='foobars', + description=None, + resource_limit=self.resource_limit, + region_id=None, + ) + self.identity_sdk_client.limits.return_value = [self.limit] self.cmd = limit.ListLimit(self.app, None) @@ -372,9 +426,7 @@ def test_limit_list(self): columns, data = self.cmd.take_action(parsed_args) - self.limit_mock.list.assert_called_with( - service=None, resource_name=None, region=None, project=None - ) + self.identity_sdk_client.limits.assert_called_with() collist = ( 'ID', @@ -388,11 +440,11 @@ def test_limit_list(self): self.assertEqual(collist, columns) datalist = ( ( - identity_fakes.limit_id, - identity_fakes.project_id, - identity_fakes.service_id, - identity_fakes.limit_resource_name, - identity_fakes.limit_resource_limit, + self.limit.id, + self.project.id, + self.service.id, + self.limit.resource_name, + self.limit.resource_limit, None, None, ), diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index 065a65cb1f..dae1aebc82 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -13,31 +13,20 @@ # under the License. from unittest import mock -from unittest.mock import call +from openstack import exceptions as sdk_exc +from openstack.identity.v3 import domain as _domain +from openstack.identity.v3 import project as _project +from openstack.identity.v3 import user as _user +from openstack.test import fakes as sdk_fakes from osc_lib import exceptions -from osc_lib import utils -from openstackclient.identity import common from openstackclient.identity.v3 import project from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -class TestProject(identity_fakes.TestIdentityv3): - def setUp(self): - super().setUp() - - # Get a shortcut to the DomainManager Mock - self.domains_mock = self.identity_client.domains - self.domains_mock.reset_mock() - - # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.identity_client.projects - self.projects_mock.reset_mock() - - -class TestProjectCreate(TestProject): - domain = identity_fakes.FakeDomain.create_one_domain() +class TestProjectCreate(identity_fakes.TestIdentityv3): + domain = sdk_fakes.generate_fake_resource(_domain.Domain) columns = ( 'description', @@ -46,39 +35,41 @@ class TestProjectCreate(TestProject): 'id', 'is_domain', 'name', + 'options', 'parent_id', 'tags', ) + project_kwargs_no_options = { + 'description': None, + 'domain_id': None, + 'enabled': True, + 'is_domain': False, + 'parent_id': None, + 'tags': [], + } + def setUp(self): super().setUp() - self.project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': self.domain.id} - ) - self.domains_mock.get.return_value = self.domain - self.projects_mock.create.return_value = self.project - self.datalist = ( - self.project.description, - self.project.domain_id, - True, - self.project.id, - False, - self.project.name, - self.project.parent_id, - self.project.tags, - ) + self.identity_sdk_client.find_domain.return_value = self.domain + # Get the command object to test self.cmd = project.CreateProject(self.app, None) def test_project_create_no_options(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, **self.project_kwargs_no_options + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ - self.project.name, + project.name, ] verifylist = [ ('parent', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('tags', []), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -88,53 +79,43 @@ def test_project_create_no_options(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], - 'options': {}, + 'name': project.name, + 'is_enabled': True, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) + + self.assertEqual(self.columns, columns) - collist = ( - 'description', - 'domain_id', - 'enabled', - 'id', - 'is_domain', - 'name', - 'parent_id', - 'tags', - ) - self.assertEqual(collist, columns) datalist = ( - self.project.description, - self.project.domain_id, + None, + None, True, - self.project.id, + project.id, False, - self.project.name, - self.project.parent_id, - self.project.tags, + project.name, + {}, + None, + [], ) self.assertEqual(datalist, data) def test_project_create_description(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, description='new desc'), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--description', 'new desc', - self.project.name, + project.name, ] verifylist = [ ('description', 'new desc'), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] @@ -145,33 +126,43 @@ def test_project_create_description(self): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, + 'name': project.name, 'description': 'new desc', - 'enabled': True, - 'parent': None, - 'tags': [], - 'options': {}, + 'is_enabled': True, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + 'new desc', + None, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_domain(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, domain_id=self.domain.id), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--domain', - self.project.domain_id, - self.project.name, + project.domain_id, + project.name, ] verifylist = [ - ('domain', self.project.domain_id), + ('domain', project.domain_id), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] @@ -184,63 +175,90 @@ def test_project_create_domain(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': self.project.domain_id, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], - 'options': {}, + 'name': project.name, + 'domain_id': project.domain_id, + 'is_enabled': True, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + self.domain.id, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_domain_no_perms(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, domain_id=self.domain.id), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--domain', - self.project.domain_id, - self.project.name, + project.domain_id, + project.name, ] verifylist = [ - ('domain', self.project.domain_id), + ('domain', project.domain_id), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - mocker = mock.Mock() - mocker.return_value = None - with mock.patch("osc_lib.utils.find_resource", mocker): - columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_domain.side_effect = ( + sdk_exc.ForbiddenException + ) + self.identity_sdk_client.find_domain.return_value = None + + columns, data = self.cmd.take_action(parsed_args) # Set expected values kwargs = { - 'name': self.project.name, - 'domain': self.project.domain_id, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], - 'options': {}, + 'name': project.name, + 'domain_id': project.domain_id, + 'is_enabled': True, } - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) + self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + self.domain.id, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_enable(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, enabled=True), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--enable', - self.project.name, + project.name, ] verifylist = [ ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] @@ -253,29 +271,39 @@ def test_project_create_enable(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], - 'options': {}, + 'name': project.name, + 'is_enabled': True, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_disable(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, enabled=False), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--disable', - self.project.name, + project.name, ] verifylist = [ ('enabled', False), - ('name', self.project.name), + ('name', project.name), ('parent', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -287,32 +315,42 @@ def test_project_create_disable(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': False, - 'parent': None, - 'tags': [], - 'options': {}, + 'name': project.name, + 'is_enabled': False, } - # ProjectManager.create(name=, domain=, - # description=, enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + False, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_property(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, fee='fi', fo='fum'), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--property', 'fee=fi', '--property', 'fo=fum', - self.project.name, + project.name, ] verifylist = [ + ('name', project.name), ('properties', {'fee': 'fi', 'fo': 'fum'}), - ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -323,36 +361,62 @@ def test_project_create_property(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, + 'name': project.name, + 'is_enabled': True, 'fee': 'fi', 'fo': 'fum', - 'tags': [], - 'options': {}, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + self.assertEqual( + ( + 'description', + 'domain_id', + 'enabled', + 'fee', + 'fo', + 'id', + 'is_domain', + 'name', + 'options', + 'parent_id', + 'tags', + ), + columns, + ) + datalist = ( + None, + None, + True, + 'fi', + 'fum', + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_is_domain_false_property(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, is_domain=False), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--property', 'is_domain=false', - self.project.name, + project.name, ] verifylist = [ ('parent', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('tags', []), ('properties', {'is_domain': 'false'}), - ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -364,33 +428,44 @@ def test_project_create_is_domain_false_property(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, + 'name': project.name, + 'is_enabled': True, 'is_domain': False, - 'tags': [], - 'options': {}, } - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_is_domain_true_property(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, is_domain=True), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--property', 'is_domain=true', - self.project.name, + project.name, ] verifylist = [ ('parent', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('tags', []), ('properties', {'is_domain': 'true'}), - ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -402,33 +477,44 @@ def test_project_create_is_domain_true_property(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, + 'name': project.name, + 'is_enabled': True, 'is_domain': True, - 'tags': [], - 'options': {}, } - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + True, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_is_domain_none_property(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, is_domain=None), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--property', 'is_domain=none', - self.project.name, + project.name, ] verifylist = [ ('parent', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('tags', []), ('properties', {'is_domain': 'none'}), - ('name', self.project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -440,40 +526,51 @@ def test_project_create_is_domain_none_property(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, + 'name': project.name, + 'is_enabled': True, 'is_domain': None, - 'tags': [], - 'options': {}, } - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + None, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_parent(self): - self.parent = identity_fakes.FakeProject.create_one_project() - self.project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id} + parent = sdk_fakes.generate_fake_resource(_project.Project) + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, + domain_id=self.domain.id, + parent_id=parent.id, + ), ) - self.projects_mock.get.return_value = self.parent - self.projects_mock.create.return_value = self.project + self.identity_sdk_client.find_project.return_value = parent + self.identity_sdk_client.create_project.return_value = project arglist = [ '--domain', - self.project.domain_id, + project.domain_id, '--parent', - self.parent.name, - self.project.name, + parent.name, + project.name, ] verifylist = [ - ('domain', self.project.domain_id), - ('parent', self.parent.name), + ('domain', project.domain_id), + ('parent', parent.name), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('tags', []), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -481,61 +578,52 @@ def test_project_create_parent(self): columns, data = self.cmd.take_action(parsed_args) kwargs = { - 'name': self.project.name, - 'domain': self.project.domain_id, - 'parent': self.parent.id, - 'description': None, - 'enabled': True, - 'tags': [], - 'options': {}, + 'name': project.name, + 'domain_id': project.domain_id, + 'parent_id': parent.id, + 'is_enabled': True, } + self.identity_sdk_client.create_project.assert_called_with(**kwargs) - self.projects_mock.create.assert_called_with(**kwargs) - - collist = ( - 'description', - 'domain_id', - 'enabled', - 'id', - 'is_domain', - 'name', - 'parent_id', - 'tags', - ) - self.assertEqual(columns, collist) + self.assertEqual(self.columns, columns) datalist = ( - self.project.description, - self.project.domain_id, - self.project.enabled, - self.project.id, - self.project.is_domain, - self.project.name, - self.parent.id, - self.project.tags, + None, + self.domain.id, + True, + project.id, + False, + project.name, + {}, + parent.id, + [], ) self.assertEqual(data, datalist) def test_project_create_invalid_parent(self): - self.projects_mock.resource_class.__name__ = 'Project' - self.projects_mock.get.side_effect = exceptions.NotFound( - 'Invalid parent' + self.identity_sdk_client.find_project.side_effect = ( + sdk_exc.ResourceNotFound ) - self.projects_mock.find.side_effect = exceptions.NotFound( - 'Invalid parent' + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, + domain_id=self.domain.id, + parent_id='invalid', + ), ) arglist = [ '--domain', - self.project.domain_id, + project.domain_id, '--parent', 'invalid', - self.project.name, + project.name, ] verifylist = [ - ('domain', self.project.domain_id), + ('domain', project.domain_id), ('parent', 'invalid'), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -546,17 +634,27 @@ def test_project_create_invalid_parent(self): ) def test_project_create_with_tags(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, + domain_id=self.domain.id, + tags=['foo'], + ), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--domain', - self.project.domain_id, + project.domain_id, '--tag', 'foo', - self.project.name, + project.name, ] verifylist = [ - ('domain', self.project.domain_id), + ('domain', project.domain_id), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', ['foo']), ] @@ -569,29 +667,45 @@ def test_project_create_with_tags(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': self.project.domain_id, - 'description': None, - 'enabled': True, - 'parent': None, + 'name': project.name, + 'domain_id': project.domain_id, + 'is_enabled': True, 'tags': ['foo'], - 'options': {}, } - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + self.domain.id, + True, + project.id, + False, + project.name, + {}, + None, + ['foo'], + ) + self.assertEqual(datalist, data) def test_project_create_with_immutable_option(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, options={'immutable': True} + ), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--immutable', - self.project.name, + project.name, ] verifylist = [ ('immutable', True), ('description', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] @@ -604,31 +718,44 @@ def test_project_create_with_immutable_option(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], + 'name': project.name, + 'is_enabled': True, 'options': {'immutable': True}, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + False, + project.name, + {'immutable': True}, + None, + [], + ) + self.assertEqual(datalist, data) def test_project_create_with_no_immutable_option(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, options={'immutable': False} + ), + ) + self.identity_sdk_client.create_project.return_value = project + arglist = [ '--no-immutable', - self.project.name, + project.name, ] verifylist = [ ('immutable', False), ('description', None), ('enabled', True), - ('name', self.project.name), + ('name', project.name), ('parent', None), ('tags', []), ] @@ -641,36 +768,121 @@ def test_project_create_with_no_immutable_option(self): # Set expected values kwargs = { - 'name': self.project.name, - 'domain': None, - 'description': None, - 'enabled': True, - 'parent': None, - 'tags': [], + 'name': project.name, + 'is_enabled': True, 'options': {'immutable': False}, } - # ProjectManager.create(name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.create.assert_called_with(**kwargs) + self.identity_sdk_client.create_project.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(self.datalist, data) + datalist = ( + None, + None, + True, + project.id, + False, + project.name, + {'immutable': False}, + None, + [], + ) + self.assertEqual(datalist, data) + def test_project_create_conflict_with_or_show(self): + project = sdk_fakes.generate_fake_resource( + _project.Project, **self.project_kwargs_no_options + ) + self.identity_sdk_client.create_project.side_effect = ( + sdk_exc.ConflictException + ) + self.identity_sdk_client.find_project.return_value = project + + arglist = [ + '--or-show', + project.name, + ] + verifylist = [ + ('or_show', True), + ('description', None), + ('enabled', True), + ('name', project.name), + ('parent', None), + ('tags', []), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + kwargs = { + 'name': project.name, + 'is_enabled': True, + } + self.identity_sdk_client.create_project.assert_called_with(**kwargs) + + self.assertEqual(self.columns, columns) + datalist = ( + None, + None, + True, + project.id, + False, + project.name, + {}, + None, + [], + ) + self.assertEqual(datalist, data) + + def test_project_create_conflict_without_or_show(self): + self.identity_sdk_client.create_project.side_effect = ( + sdk_exc.ConflictException + ) + project = sdk_fakes.generate_fake_resource( + _project.Project, **self.project_kwargs_no_options + ) -class TestProjectDelete(TestProject): - project = identity_fakes.FakeProject.create_one_project() + arglist = [ + project.name, + ] + verifylist = [ + ('or_show', False), + ('description', None), + ('enabled', True), + ('name', project.name), + ('parent', None), + ('tags', []), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + sdk_exc.ConflictException, + self.cmd.take_action, + parsed_args, + ) + + +class TestProjectDelete(identity_fakes.TestIdentityv3): + domain = sdk_fakes.generate_fake_resource(_domain.Domain) def setUp(self): super().setUp() - # This is the return value for utils.find_resource() - self.projects_mock.get.return_value = self.project - self.projects_mock.delete.return_value = None + self.project = sdk_fakes.generate_fake_resource(_project.Project) + self.project_with_domain = sdk_fakes.generate_fake_resource( + _project.Project, + name=self.project.name, + domain_id=self.domain.id, + ) + self.identity_sdk_client.delete_project.return_value = None # Get the command object to test self.cmd = project.DeleteProject(self.app, None) def test_project_delete_no_options(self): + self.identity_sdk_client.find_project.return_value = self.project + arglist = [ self.project.id, ] @@ -681,16 +893,72 @@ def test_project_delete_no_options(self): result = self.cmd.take_action(parsed_args) - self.projects_mock.delete.assert_called_with( + self.identity_sdk_client.delete_project.assert_called_with( self.project.id, ) self.assertIsNone(result) - @mock.patch.object(utils, 'find_resource') - def test_delete_multi_projects_with_exception(self, find_mock): - find_mock.side_effect = [self.project, exceptions.CommandError] + def test_project_multi_delete(self): + self.identity_sdk_client.find_project.side_effect = [ + self.project, + self.project_with_domain, + ] + arglist = [self.project.id, self.project_with_domain.id] + verifylist = [ + ('projects', arglist), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + self.identity_sdk_client.delete_project.assert_has_calls( + [ + mock.call(self.project.id), + mock.call(self.project_with_domain.id), + ] + ) + self.assertIsNone(result) + + def test_project_delete_with_forbidden_domain(self): + self.identity_sdk_client.find_domain.side_effect = [ + sdk_exc.ForbiddenException + ] + self.identity_sdk_client.find_project.return_value = ( + self.project_with_domain + ) + + arglist = [ + '--domain', + self.project_with_domain.domain_id, + self.project_with_domain.name, + ] + verifylist = [ + ('domain', self.domain.id), + ('projects', [self.project_with_domain.name]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_project.assert_called_with( + name_or_id=self.project_with_domain.name, + ignore_missing=False, + domain_id=self.domain.id, + ) + self.identity_sdk_client.delete_project.assert_called_once_with( + self.project_with_domain.id + ) + self.assertIsNone(result) + + def test_delete_multi_projects_with_exception(self): + self.identity_sdk_client.find_project.side_effect = [ + self.project, + self.project_with_domain, + sdk_exc.NotFoundException, + ] + arglist = [ self.project.id, + self.project_with_domain.id, 'unexist_project', ] verifylist = [ @@ -702,21 +970,36 @@ def test_delete_multi_projects_with_exception(self, find_mock): self.cmd.take_action(parsed_args) self.fail('CommandError should be raised.') except exceptions.CommandError as e: - self.assertEqual('1 of 2 projects failed to delete.', str(e)) + self.assertEqual('1 of 3 projects failed to delete.', str(e)) - find_mock.assert_any_call(self.projects_mock, self.project.id) - find_mock.assert_any_call(self.projects_mock, 'unexist_project') + self.identity_sdk_client.find_project.assert_has_calls( + [ + mock.call(name_or_id=self.project.id, ignore_missing=False), + mock.call( + name_or_id=self.project_with_domain.id, + ignore_missing=False, + ), + mock.call(name_or_id='unexist_project', ignore_missing=False), + ] + ) - self.assertEqual(2, find_mock.call_count) - self.projects_mock.delete.assert_called_once_with(self.project.id) + self.assertEqual(3, self.identity_sdk_client.find_project.call_count) + self.identity_sdk_client.delete_project.assert_has_calls( + [ + mock.call(self.project.id), + mock.call(self.project_with_domain.id), + ] + ) -class TestProjectList(TestProject): - domain = identity_fakes.FakeDomain.create_one_domain() - project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': domain.id} +class TestProjectList(identity_fakes.TestIdentityv3): + domain = sdk_fakes.generate_fake_resource(_domain.Domain) + project = sdk_fakes.generate_fake_resource( + _project.Project, domain_id=domain.id + ) + projects = list( + sdk_fakes.generate_fake_resources(_project.Project, count=2) ) - projects = identity_fakes.FakeProject.create_projects() columns = ( 'ID', @@ -746,12 +1029,12 @@ class TestProjectList(TestProject): def setUp(self): super().setUp() - self.projects_mock.list.return_value = [self.project] - # Get the command object to test self.cmd = project.ListProject(self.app, None) def test_project_list_no_options(self): + self.identity_sdk_client.projects.return_value = [self.project] + arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -760,12 +1043,14 @@ def test_project_list_no_options(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with() + self.identity_sdk_client.projects.assert_called_with() self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_long(self): + self.identity_sdk_client.projects.return_value = [self.project] + arglist = [ '--long', ] @@ -778,7 +1063,7 @@ def test_project_list_long(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with() + self.identity_sdk_client.projects.assert_called_with() collist = ('ID', 'Name', 'Domain ID', 'Description', 'Enabled') self.assertEqual(collist, columns) @@ -794,6 +1079,8 @@ def test_project_list_long(self): self.assertEqual(datalist, tuple(data)) def test_project_list_domain(self): + self.identity_sdk_client.projects.return_value = [self.project] + arglist = [ '--domain', self.project.domain_id, @@ -802,7 +1089,7 @@ def test_project_list_domain(self): ('domain', self.project.domain_id), ] - self.domains_mock.get.return_value = self.domain + self.identity_sdk_client.find_domain.return_value = self.domain parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -810,14 +1097,16 @@ def test_project_list_domain(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with( - domain=self.project.domain_id + self.identity_sdk_client.projects.assert_called_with( + domain_id=self.project.domain_id ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_domain_no_perms(self): + self.identity_sdk_client.projects.return_value = [self.project] + arglist = [ '--domain', self.project.domain_id, @@ -826,23 +1115,30 @@ def test_project_list_domain_no_perms(self): ('domain', self.project.domain_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - mocker = mock.Mock() - mocker.return_value = None - with mock.patch("osc_lib.utils.find_resource", mocker): - columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_project.side_effect = ( + sdk_exc.ResourceNotFound + ) + self.identity_sdk_client.find_domain.return_value = self.domain + + columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with( - domain=self.project.domain_id + self.identity_sdk_client.projects.assert_called_with( + domain_id=self.project.domain_id ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_parent(self): - self.parent = identity_fakes.FakeProject.create_one_project() - self.project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': self.domain.id, 'parent_id': self.parent.id} + self.parent = sdk_fakes.generate_fake_resource(_project.Project) + self.project = sdk_fakes.generate_fake_resource( + _project.Project, + id=self.project.id, + name=self.project.name, + domain_id=self.domain.id, + parent_id=self.parent.id, ) + self.identity_sdk_client.projects.return_value = [self.project] arglist = [ '--parent', @@ -852,18 +1148,48 @@ def test_project_list_parent(self): ('parent', self.parent.id), ] - self.projects_mock.get.return_value = self.parent + self.identity_sdk_client.find_project.return_value = self.parent + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.projects.assert_called_with( + parent_id=self.parent.id + ) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + + def test_project_list_user(self): + self.user = sdk_fakes.generate_fake_resource(_user.User) + self.project = sdk_fakes.generate_fake_resource( + _project.UserProject, + id=self.project.id, + name=self.project.name, + user_id=self.user.id, + ) + self.identity_sdk_client.user_projects.return_value = [self.project] + + arglist = [ + '--user', + self.user.id, + ] + verifylist = [ + ('user', self.user.id), + ] + + self.identity_sdk_client.find_user.return_value = self.user parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with(parent=self.parent.id) + self.identity_sdk_client.user_projects.assert_called_with(self.user.id) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) def test_project_list_sort(self): - self.projects_mock.list.return_value = self.projects + self.identity_sdk_client.projects.return_value = self.projects arglist = [ '--sort', @@ -877,7 +1203,7 @@ def test_project_list_sort(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. (columns, data) = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with() + self.identity_sdk_client.projects.assert_called_with() collist = ('ID', 'Name') self.assertEqual(collist, columns) @@ -896,6 +1222,8 @@ def test_project_list_sort(self): self.assertEqual(datalists, tuple(data)) def test_project_list_my_projects(self): + self.identity_sdk_client.user_projects.return_value = [self.project] + auth_ref = identity_fakes.fake_auth_ref( identity_fakes.TOKEN_WITH_PROJECT_ID, ) @@ -913,8 +1241,8 @@ def test_project_list_my_projects(self): # returns a tuple containing the column names and an iterable # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.list.assert_called_with( - user=self.app.client_manager.auth_ref.user_id + self.identity_sdk_client.user_projects.assert_called_with( + self.app.client_manager.auth_ref.user_id ) collist = ('ID', 'Name') @@ -928,6 +1256,8 @@ def test_project_list_my_projects(self): self.assertEqual(datalist, tuple(data)) def test_project_list_with_option_enabled(self): + self.identity_sdk_client.projects.return_value = [self.project] + arglist = ['--enabled'] verifylist = [('is_enabled', True)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -938,25 +1268,28 @@ def test_project_list_with_option_enabled(self): columns, data = self.cmd.take_action(parsed_args) kwargs = {'is_enabled': True} - self.projects_mock.list.assert_called_with(**kwargs) + self.identity_sdk_client.projects.assert_called_with(**kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) -class TestProjectSet(TestProject): - domain = identity_fakes.FakeDomain.create_one_domain() - project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': domain.id, 'tags': ['tag1', 'tag2', 'tag3']} +class TestProjectSet(identity_fakes.TestIdentityv3): + domain = sdk_fakes.generate_fake_resource(_domain.Domain) + + project_kwargs_no_options = { + 'domain_id': domain.id, + 'tags': ['tag1', 'tag2', 'tag3'], + } + project = sdk_fakes.generate_fake_resource( + _project.Project, **project_kwargs_no_options ) def setUp(self): super().setUp() - self.domains_mock.get.return_value = self.domain - - self.projects_mock.get.return_value = self.project - self.projects_mock.update.return_value = self.project + self.identity_sdk_client.find_domain.return_value = self.domain + self.identity_sdk_client.find_project.return_value = self.project # Get the command object to test self.cmd = project.SetProject(self.app, None) @@ -997,9 +1330,10 @@ def test_project_set_name(self): kwargs = { 'name': 'qwerty', } - # ProjectManager.update(project, name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_description(self): @@ -1024,7 +1358,9 @@ def test_project_set_description(self): kwargs = { 'description': 'new desc', } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_enable(self): @@ -1047,7 +1383,9 @@ def test_project_set_enable(self): kwargs = { 'enabled': True, } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_disable(self): @@ -1070,7 +1408,9 @@ def test_project_set_disable(self): kwargs = { 'enabled': False, } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_property(self): @@ -1097,7 +1437,9 @@ def test_project_set_property(self): 'fee': 'fi', 'fo': 'fum', } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_tags(self): @@ -1112,7 +1454,7 @@ def test_project_set_tags(self): ] verifylist = [ ('name', 'qwerty'), - ('domain', self.project.domain_id), + ('domain', self.domain.id), ('enabled', None), ('project', self.project.name), ('tags', ['foo']), @@ -1126,9 +1468,9 @@ def test_project_set_tags(self): 'name': 'qwerty', 'tags': sorted({'tag1', 'tag2', 'tag3', 'foo'}), } - # ProjectManager.update(project, name=, domain=, description=, - # enabled=, **kwargs) - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_remove_tags(self): @@ -1149,7 +1491,9 @@ def test_project_remove_tags(self): result = self.cmd.take_action(parsed_args) kwargs = {'tags': list({'tag3'})} - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_with_immutable_option(self): @@ -1173,7 +1517,9 @@ def test_project_set_with_immutable_option(self): kwargs = { 'options': {'immutable': True}, } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) def test_project_set_with_no_immutable_option(self): @@ -1197,114 +1543,108 @@ def test_project_set_with_no_immutable_option(self): kwargs = { 'options': {'immutable': False}, } - self.projects_mock.update.assert_called_with(self.project.id, **kwargs) + self.identity_sdk_client.update_project.assert_called_with( + self.project.id, **kwargs + ) self.assertIsNone(result) -class TestProjectShow(TestProject): - domain = identity_fakes.FakeDomain.create_one_domain() +class TestProjectShow(identity_fakes.TestIdentityv3): + domain = sdk_fakes.generate_fake_resource(_domain.Domain) + + columns = ( + 'description', + 'domain_id', + 'enabled', + 'id', + 'is_domain', + 'name', + 'options', + 'parent_id', + 'tags', + ) + + project_kwargs_no_options = { + 'description': None, + 'domain_id': None, + 'enabled': True, + 'is_domain': False, + 'parent_id': None, + 'tags': [], + } def setUp(self): super().setUp() - self.project = identity_fakes.FakeProject.create_one_project( - attrs={'domain_id': self.domain.id} - ) - # Get the command object to test self.cmd = project.ShowProject(self.app, None) def test_project_show(self): - self.projects_mock.get.return_value = self.project + project = sdk_fakes.generate_fake_resource( + _project.Project, **self.project_kwargs_no_options + ) + self.identity_sdk_client.find_project.return_value = project arglist = [ - self.project.id, + project.id, ] verifylist = [ - ('project', self.project.id), + ('project', project.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.identity_client.tokens.get_token_data.return_value = { - 'token': { - 'project': { - 'domain': {}, - 'name': parsed_args.project, - 'id': parsed_args.project, - } - } - } - # In base command class ShowOne in cliff, abstract method take_action() # returns a two-part tuple with a tuple of column names and a tuple of # data to be shown. columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_called_once_with(self.project.id) - - collist = ( - 'description', - 'domain_id', - 'enabled', - 'id', - 'is_domain', - 'name', - 'parent_id', - 'tags', + self.identity_sdk_client.find_project.assert_called_with( + project.id, ignore_missing=False ) - self.assertEqual(collist, columns) + + self.assertEqual(self.columns, columns) datalist = ( - self.project.description, - self.project.domain_id, + None, + None, True, - self.project.id, + project.id, False, - self.project.name, - self.project.parent_id, - self.project.tags, + project.name, + {}, + None, + [], ) self.assertEqual(datalist, data) def test_project_show_parents(self): - self.project = identity_fakes.FakeProject.create_one_project( - attrs={ - 'parent_id': self.project.parent_id, - 'parents': [{'project': {'id': self.project.parent_id}}], - } + parent = sdk_fakes.generate_fake_resource( + _project.Project, parent_id='default' + ) + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, + parent_id=parent.id, + parents={parent.id: {parent.parent_id: None}}, + ), ) - self.projects_mock.get.return_value = self.project + self.identity_sdk_client.find_project.return_value = project arglist = [ - self.project.id, + project.id, '--parents', ] verifylist = [ - ('project', self.project.id), + ('project', project.id), ('parents', True), ('children', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.identity_client.tokens.get_token_data.return_value = { - 'token': { - 'project': { - 'domain': {}, - 'name': parsed_args.project, - 'id': parsed_args.project, - } - } - } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_has_calls( - [ - call(self.project.id), - call( - self.project.id, - parents_as_ids=True, - subtree_as_ids=False, - ), - ] + self.identity_sdk_client.find_project.assert_called_with( + project.id, parents_as_ids=True, ignore_missing=False ) collist = ( @@ -1314,63 +1654,51 @@ def test_project_show_parents(self): 'id', 'is_domain', 'name', + 'options', 'parent_id', 'parents', 'tags', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( - self.project.description, - self.project.domain_id, - self.project.enabled, - self.project.id, - self.project.is_domain, - self.project.name, - self.project.parent_id, - [{'project': {'id': self.project.parent_id}}], - self.project.tags, + None, + None, + True, + project.id, + False, + project.name, + {}, + parent.id, + {parent.id: {'default': None}}, + [], ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_project_show_subtree(self): - self.project = identity_fakes.FakeProject.create_one_project( - attrs={ - 'parent_id': self.project.parent_id, - 'subtree': [{'project': {'id': 'children-id'}}], - } + child = sdk_fakes.generate_fake_resource( + _project.Project, subtree=None + ) + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, subtree={child.id: None}), ) - self.projects_mock.get.return_value = self.project + self.identity_sdk_client.find_project.return_value = project arglist = [ - self.project.id, + project.id, '--children', ] verifylist = [ - ('project', self.project.id), + ('project', project.id), ('parents', False), ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.identity_client.tokens.get_token_data.return_value = { - 'token': { - 'project': { - 'domain': {}, - 'name': parsed_args.project, - 'id': parsed_args.project, - } - } - } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_has_calls( - [ - call(self.project.id), - call( - self.project.id, - parents_as_ids=False, - subtree_as_ids=True, - ), - ] + + self.identity_sdk_client.find_project.assert_called_with( + project.id, subtree_as_ids=True, ignore_missing=False ) collist = ( @@ -1380,65 +1708,63 @@ def test_project_show_subtree(self): 'id', 'is_domain', 'name', + 'options', 'parent_id', 'subtree', 'tags', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( - self.project.description, - self.project.domain_id, - self.project.enabled, - self.project.id, - self.project.is_domain, - self.project.name, - self.project.parent_id, - [{'project': {'id': 'children-id'}}], - self.project.tags, + None, + None, + True, + project.id, + False, + project.name, + {}, + None, + {child.id: None}, + [], ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_project_show_parents_and_children(self): - self.project = identity_fakes.FakeProject.create_one_project( - attrs={ - 'parent_id': self.project.parent_id, - 'parents': [{'project': {'id': self.project.parent_id}}], - 'subtree': [{'project': {'id': 'children-id'}}], - } + parent = sdk_fakes.generate_fake_resource( + _project.Project, parent_id='default' + ) + child = sdk_fakes.generate_fake_resource( + _project.Project, subtree=None ) - self.projects_mock.get.return_value = self.project + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict( + self.project_kwargs_no_options, + parent_id=parent.id, + parents={parent.id: {parent.parent_id: None}}, + subtree={child.id: None}, + ), + ) + self.identity_sdk_client.find_project.return_value = project arglist = [ - self.project.id, + project.id, '--parents', '--children', ] verifylist = [ - ('project', self.project.id), + ('project', project.id), ('parents', True), ('children', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.identity_client.tokens.get_token_data.return_value = { - 'token': { - 'project': { - 'domain': {}, - 'name': parsed_args.project, - 'id': parsed_args.project, - } - } - } columns, data = self.cmd.take_action(parsed_args) - self.projects_mock.get.assert_has_calls( - [ - call(self.project.id), - call( - self.project.id, - parents_as_ids=True, - subtree_as_ids=True, - ), - ] + + self.identity_sdk_client.find_project.assert_called_with( + project.id, + parents_as_ids=True, + subtree_as_ids=True, + ignore_missing=False, ) collist = ( @@ -1448,42 +1774,36 @@ def test_project_show_parents_and_children(self): 'id', 'is_domain', 'name', + 'options', 'parent_id', 'parents', 'subtree', 'tags', ) - self.assertEqual(columns, collist) + self.assertEqual(collist, columns) datalist = ( - self.project.description, - self.project.domain_id, - self.project.enabled, - self.project.id, - self.project.is_domain, - self.project.name, - self.project.parent_id, - [{'project': {'id': self.project.parent_id}}], - [{'project': {'id': 'children-id'}}], - self.project.tags, + None, + None, + True, + project.id, + False, + project.name, + {}, + parent.id, + {parent.id: {'default': None}}, + {child.id: None}, + [], ) - self.assertEqual(data, datalist) + self.assertEqual(datalist, data) def test_project_show_with_domain(self): - project = identity_fakes.FakeProject.create_one_project( - {"name": self.project.name} - ) - - self.identity_client.tokens.get_token_data.return_value = { - 'token': { - 'project': { - 'domain': {"id": self.project.domain_id}, - 'name': self.project.name, - 'id': self.project.id, - } - } - } + project = sdk_fakes.generate_fake_resource( + _project.Project, + **dict(self.project_kwargs_no_options, domain_id=self.domain.id), + ) + self.identity_sdk_client.find_domain.return_value = self.domain + self.identity_sdk_client.find_project.return_value = project - identity_client = self.identity_client arglist = [ "--domain", self.domain.id, @@ -1495,23 +1815,22 @@ def test_project_show_with_domain(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - project_str = common._get_token_resource( - identity_client, 'project', parsed_args.project, parsed_args.domain + columns, data = self.cmd.take_action(parsed_args) + + self.identity_sdk_client.find_project.assert_called_with( + project.id, domain_id=self.domain.id, ignore_missing=False ) - self.assertEqual(self.project.id, project_str) - arglist = [ - "--domain", - project.domain_id, + self.assertEqual(self.columns, columns) + datalist = ( + None, + self.domain.id, + True, + project.id, + False, project.name, - ] - verifylist = [ - ('domain', project.domain_id), - ('project', project.name), - ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - - project_str = common._get_token_resource( - identity_client, 'project', parsed_args.project, parsed_args.domain + {}, + None, + [], ) - self.assertEqual(project.name, project_str) + self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_protocol.py b/openstackclient/tests/unit/identity/v3/test_protocol.py index c85699685a..4ac77d6b28 100644 --- a/openstackclient/tests/unit/identity/v3/test_protocol.py +++ b/openstackclient/tests/unit/identity/v3/test_protocol.py @@ -12,202 +12,211 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - +from openstack.identity.v3 import federation_protocol as _federation_protocol +from openstack.identity.v3 import mapping as _mapping +from openstack.test import fakes as sdk_fakes from openstackclient.identity.v3 import federation_protocol -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -class TestProtocol(identity_fakes.TestFederatedIdentity): - def setUp(self): - super().setUp() - - federation_lib = self.identity_client.federation - self.protocols_mock = federation_lib.protocols - self.protocols_mock.reset_mock() - - -class TestProtocolCreate(TestProtocol): +class TestProtocolCreate(identity_fakes.TestFederatedIdentity): def setUp(self): super().setUp() - proto = copy.deepcopy(identity_fakes.PROTOCOL_OUTPUT) - resource = fakes.FakeResource(None, proto, loaded=True) - self.protocols_mock.create.return_value = resource + self.proto = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol + ) + self.identity_sdk_client.create_federation_protocol.return_value = ( + self.proto + ) self.cmd = federation_protocol.CreateProtocol(self.app, None) def test_create_protocol(self): argslist = [ - identity_fakes.protocol_id, + self.proto.name, '--identity-provider', - identity_fakes.idp_id, + self.proto.idp_id, '--mapping', - identity_fakes.mapping_id, + self.proto.mapping_id, ] verifylist = [ - ('federation_protocol', identity_fakes.protocol_id), - ('identity_provider', identity_fakes.idp_id), - ('mapping', identity_fakes.mapping_id), + ('federation_protocol', self.proto.name), + ('identity_provider', self.proto.idp_id), + ('mapping', self.proto.mapping_id), ] parsed_args = self.check_parser(self.cmd, argslist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.protocols_mock.create.assert_called_with( - protocol_id=identity_fakes.protocol_id, - identity_provider=identity_fakes.idp_id, - mapping=identity_fakes.mapping_id, + self.identity_sdk_client.create_federation_protocol.assert_called_with( + name=self.proto.id, + idp_id=self.proto.idp_id, + mapping_id=self.proto.mapping_id, ) collist = ('id', 'identity_provider', 'mapping') self.assertEqual(collist, columns) datalist = ( - identity_fakes.protocol_id, - identity_fakes.idp_id, - identity_fakes.mapping_id, + self.proto.id, + self.proto.idp_id, + self.proto.mapping_id, ) self.assertEqual(datalist, data) -class TestProtocolDelete(TestProtocol): +class TestProtocolDelete(identity_fakes.TestFederatedIdentity): def setUp(self): super().setUp() - # This is the return value for utils.find_resource() - self.protocols_mock.get.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.PROTOCOL_OUTPUT), - loaded=True, + self.proto = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol ) - - self.protocols_mock.delete.return_value = None + self.identity_sdk_client.delete_federation_protocol.return_value = None self.cmd = federation_protocol.DeleteProtocol(self.app, None) - def test_delete_identity_provider(self): + def test_delete_protocol(self): arglist = [ '--identity-provider', - identity_fakes.idp_id, - identity_fakes.protocol_id, + self.proto.idp_id, + self.proto.name, ] verifylist = [ - ('federation_protocol', [identity_fakes.protocol_id]), - ('identity_provider', identity_fakes.idp_id), + ('federation_protocol', [self.proto.id]), + ('identity_provider', self.proto.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.protocols_mock.delete.assert_called_with( - identity_fakes.idp_id, identity_fakes.protocol_id + self.identity_sdk_client.delete_federation_protocol.assert_called_with( + idp_id=self.proto.idp_id, + protocol=self.proto.id, + ignore_missing=False, ) self.assertIsNone(result) -class TestProtocolList(TestProtocol): +class TestProtocolList(identity_fakes.TestFederatedIdentity): def setUp(self): super().setUp() - self.protocols_mock.get.return_value = fakes.FakeResource( - None, identity_fakes.PROTOCOL_ID_MAPPING, loaded=True + self.proto1 = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol ) - - self.protocols_mock.list.return_value = [ - fakes.FakeResource( - None, identity_fakes.PROTOCOL_ID_MAPPING, loaded=True - ) + self.proto2 = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol, idp_id=self.proto1 + ) + self.identity_sdk_client.federation_protocols.return_value = [ + self.proto1, + self.proto2, ] - self.cmd = federation_protocol.ListProtocols(self.app, None) def test_list_protocols(self): - arglist = ['--identity-provider', identity_fakes.idp_id] - verifylist = [('identity_provider', identity_fakes.idp_id)] + arglist = ['--identity-provider', self.proto1.idp_id] + verifylist = [('identity_provider', self.proto1.idp_id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.protocols_mock.list.assert_called_with(identity_fakes.idp_id) + self.identity_sdk_client.federation_protocols.assert_called_with( + self.proto1.idp_id + ) + self.assertEqual(columns, ('id', 'mapping')) + datalist = ( + ( + self.proto1.name, + self.proto1.mapping_id, + ), + ( + self.proto2.name, + self.proto2.mapping_id, + ), + ) + self.assertEqual(datalist, tuple(data)) -class TestProtocolSet(TestProtocol): + +class TestProtocolSet(identity_fakes.TestFederatedIdentity): def setUp(self): super().setUp() - self.protocols_mock.get.return_value = fakes.FakeResource( - None, identity_fakes.PROTOCOL_OUTPUT, loaded=True + self.proto = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol ) - self.protocols_mock.update.return_value = fakes.FakeResource( - None, identity_fakes.PROTOCOL_OUTPUT_UPDATED, loaded=True + self.mapping = sdk_fakes.generate_fake_resource(_mapping.Mapping) + self.identity_sdk_client.update_federation_protocol.return_value = ( + self.proto ) - self.cmd = federation_protocol.SetProtocol(self.app, None) def test_set_new_mapping(self): arglist = [ - identity_fakes.protocol_id, + self.proto.name, '--identity-provider', - identity_fakes.idp_id, + self.proto.idp_id, '--mapping', - identity_fakes.mapping_id, + self.mapping.name, ] verifylist = [ - ('identity_provider', identity_fakes.idp_id), - ('federation_protocol', identity_fakes.protocol_id), - ('mapping', identity_fakes.mapping_id), + ('identity_provider', self.proto.idp_id), + ('federation_protocol', self.proto.name), + ('mapping', self.mapping.name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.protocols_mock.update.assert_called_with( - identity_fakes.idp_id, - identity_fakes.protocol_id, - identity_fakes.mapping_id, + self.identity_sdk_client.update_federation_protocol.assert_called_with( + idp_id=self.proto.idp_id, + name=self.proto.name, + mapping_id=self.mapping.id, ) collist = ('id', 'identity_provider', 'mapping') self.assertEqual(collist, columns) datalist = ( - identity_fakes.protocol_id, - identity_fakes.idp_id, - identity_fakes.mapping_id_updated, + self.proto.name, + self.proto.idp_id, + self.proto.mapping_id, ) self.assertEqual(datalist, data) -class TestProtocolShow(TestProtocol): +class TestProtocolShow(identity_fakes.TestFederatedIdentity): def setUp(self): super().setUp() - self.protocols_mock.get.return_value = fakes.FakeResource( - None, identity_fakes.PROTOCOL_OUTPUT, loaded=False + self.proto = sdk_fakes.generate_fake_resource( + _federation_protocol.FederationProtocol + ) + self.identity_sdk_client.get_federation_protocol.return_value = ( + self.proto ) - self.cmd = federation_protocol.ShowProtocol(self.app, None) def test_show_protocol(self): arglist = [ - identity_fakes.protocol_id, + self.proto.name, '--identity-provider', - identity_fakes.idp_id, + self.proto.idp_id, ] verifylist = [ - ('federation_protocol', identity_fakes.protocol_id), - ('identity_provider', identity_fakes.idp_id), + ('federation_protocol', self.proto.name), + ('identity_provider', self.proto.idp_id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.protocols_mock.get.assert_called_with( - identity_fakes.idp_id, identity_fakes.protocol_id + self.identity_sdk_client.get_federation_protocol.assert_called_with( + idp_id=self.proto.idp_id, protocol=self.proto.name ) collist = ('id', 'identity_provider', 'mapping') self.assertEqual(collist, columns) datalist = ( - identity_fakes.protocol_id, - identity_fakes.idp_id, - identity_fakes.mapping_id, + self.proto.name, + self.proto.idp_id, + self.proto.mapping_id, ) self.assertEqual(datalist, data) diff --git a/openstackclient/tests/unit/identity/v3/test_registered_limit.py b/openstackclient/tests/unit/identity/v3/test_registered_limit.py index a120714ec6..1864914fd5 100644 --- a/openstackclient/tests/unit/identity/v3/test_registered_limit.py +++ b/openstackclient/tests/unit/identity/v3/test_registered_limit.py @@ -10,72 +10,79 @@ # License for the specific language governing permissions and limitations # under the License. -import copy - -from keystoneauth1.exceptions import http as ksa_exceptions +from openstack import exceptions as sdk_exc +from openstack.identity.v3 import region as _region +from openstack.identity.v3 import registered_limit as _registered_limit +from openstack.identity.v3 import service as _service +from openstack.test import fakes as sdk_fakes from osc_lib import exceptions from openstackclient.identity.v3 import registered_limit -from openstackclient.tests.unit import fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes -class TestRegisteredLimit(identity_fakes.TestIdentityv3): +class TestRegisteredLimitCreate(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - self.registered_limit_mock = self.identity_client.registered_limits - - self.services_mock = self.identity_client.services - self.services_mock.reset_mock() - - self.regions_mock = self.identity_client.regions - self.regions_mock.reset_mock() + self.service = sdk_fakes.generate_fake_resource(_service.Service) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + self.description = 'default limit of foobars' + self.default_limit = 10 + self.resource_name = 'foobars' -class TestRegisteredLimitCreate(TestRegisteredLimit): - def setUp(self): - super().setUp() + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region - self.service = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.SERVICE), loaded=True + self.registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, ) - self.services_mock.get.return_value = self.service - - self.region = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.REGION), loaded=True + self.registered_limit_with_options = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=self.description, + region_id=self.region.id, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, ) - self.regions_mock.get.return_value = self.region self.cmd = registered_limit.CreateRegisteredLimit(self.app, None) def test_registered_limit_create_without_options(self): - self.registered_limit_mock.create.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.REGISTERED_LIMIT), loaded=True + self.identity_sdk_client.create_registered_limit.return_value = ( + self.registered_limit ) - resource_name = identity_fakes.registered_limit_resource_name - default_limit = identity_fakes.registered_limit_default_limit arglist = [ '--service', - identity_fakes.service_id, + self.service.id, '--default-limit', - '10', - resource_name, + str(self.default_limit), + self.resource_name, ] verifylist = [ - ('service', identity_fakes.service_id), - ('default_limit', default_limit), - ('resource_name', resource_name), + ('service', self.service.id), + ('default_limit', self.default_limit), + ('resource_name', self.resource_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - kwargs = {'description': None, 'region': None} - self.registered_limit_mock.create.assert_called_with( - self.service, resource_name, default_limit, **kwargs + kwargs = { + 'service_id': self.service.id, + 'default_limit': self.default_limit, + 'resource_name': self.resource_name, + } + self.identity_sdk_client.create_registered_limit.assert_called_with( + **kwargs ) collist = ( @@ -89,51 +96,52 @@ def test_registered_limit_create_without_options(self): self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, + self.default_limit, None, - identity_fakes.registered_limit_id, + self.registered_limit.id, None, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_registered_limit_create_with_options(self): - self.registered_limit_mock.create.return_value = fakes.FakeResource( - None, - copy.deepcopy(identity_fakes.REGISTERED_LIMIT_OPTIONS), - loaded=True, + self.identity_sdk_client.create_registered_limit.return_value = ( + self.registered_limit_with_options ) - resource_name = identity_fakes.registered_limit_resource_name - default_limit = identity_fakes.registered_limit_default_limit - description = identity_fakes.registered_limit_description arglist = [ '--region', - identity_fakes.region_id, + self.region.id, '--description', - description, + self.description, '--service', - identity_fakes.service_id, + self.service.id, '--default-limit', - '10', - resource_name, + str(self.default_limit), + self.resource_name, ] verifylist = [ - ('region', identity_fakes.region_id), - ('description', description), - ('service', identity_fakes.service_id), - ('default_limit', default_limit), - ('resource_name', resource_name), + ('region', self.region.id), + ('description', self.description), + ('service', self.service.id), + ('default_limit', self.default_limit), + ('resource_name', self.resource_name), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - kwargs = {'description': description, 'region': self.region} - self.registered_limit_mock.create.assert_called_with( - self.service, resource_name, default_limit, **kwargs + kwargs = { + 'description': self.description, + 'region_id': self.region.id, + 'service_id': self.service.id, + 'default_limit': self.default_limit, + 'resource_name': self.resource_name, + } + self.identity_sdk_client.create_registered_limit.assert_called_with( + **kwargs ) collist = ( @@ -147,41 +155,44 @@ def test_registered_limit_create_with_options(self): self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, - description, - identity_fakes.registered_limit_id, - identity_fakes.region_id, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.default_limit, + self.description, + self.registered_limit_with_options.id, + self.region.id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestRegisteredLimitDelete(TestRegisteredLimit): +class TestRegisteredLimitDelete(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() self.cmd = registered_limit.DeleteRegisteredLimit(self.app, None) def test_registered_limit_delete(self): - self.registered_limit_mock.delete.return_value = None + self.registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + ) + self.identity_sdk_client.delete_registered_limit.return_value = None - arglist = [identity_fakes.registered_limit_id] - verifylist = [ - ('registered_limits', [identity_fakes.registered_limit_id]) - ] + arglist = [self.registered_limit.id] + verifylist = [('registered_limits', [self.registered_limit.id])] parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.registered_limit_mock.delete.assert_called_with( - identity_fakes.registered_limit_id + self.identity_sdk_client.delete_registered_limit.assert_called_with( + self.registered_limit.id, + ignore_missing=False, ) self.assertIsNone(result) def test_registered_limit_delete_with_exception(self): - return_value = ksa_exceptions.NotFound() - self.registered_limit_mock.delete.side_effect = return_value + self.identity_sdk_client.delete_registered_limit.side_effect = ( + sdk_exc.ResourceNotFound + ) arglist = ['fake-registered-limit-id'] verifylist = [('registered_limits', ['fake-registered-limit-id'])] @@ -196,27 +207,52 @@ def test_registered_limit_delete_with_exception(self): ) -class TestRegisteredLimitShow(TestRegisteredLimit): +class TestRegisteredLimitShow(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - self.registered_limit_mock.get.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.REGISTERED_LIMIT), loaded=True + self.service = sdk_fakes.generate_fake_resource(_service.Service) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + + self.description = 'default limit of foobars' + self.default_limit = 10 + self.resource_name = 'foobars' + + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + + self.registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.registered_limit_with_options = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=self.description, + region_id=self.region.id, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, ) self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) def test_registered_limit_show(self): - arglist = [identity_fakes.registered_limit_id] - verifylist = [ - ('registered_limit_id', identity_fakes.registered_limit_id) - ] + self.identity_sdk_client.get_registered_limit.return_value = ( + self.registered_limit + ) + + arglist = [self.registered_limit.id] + verifylist = [('registered_limit_id', self.registered_limit.id)] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.get.assert_called_with( - identity_fakes.registered_limit_id + self.identity_sdk_client.get_registered_limit.assert_called_with( + self.registered_limit.id ) collist = ( @@ -229,50 +265,107 @@ def test_registered_limit_show(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, + self.default_limit, None, - identity_fakes.registered_limit_id, + self.registered_limit.id, None, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.resource_name, + self.service.id, + ) + self.assertEqual(datalist, data) + + def test_registered_limit_show_with_options(self): + self.identity_sdk_client.get_registered_limit.return_value = ( + self.registered_limit_with_options + ) + + arglist = [self.registered_limit_with_options.id] + verifylist = [ + ('registered_limit_id', self.registered_limit_with_options.id) + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.identity_sdk_client.get_registered_limit.assert_called_with( + self.registered_limit_with_options.id + ) + + collist = ( + 'default_limit', + 'description', + 'id', + 'region_id', + 'resource_name', + 'service_id', + ) + self.assertEqual(collist, columns) + datalist = ( + self.default_limit, + self.description, + self.registered_limit_with_options.id, + self.region.id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestRegisteredLimitSet(TestRegisteredLimit): +class TestRegisteredLimitSet(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() + + self.service = sdk_fakes.generate_fake_resource(_service.Service) + self.region = sdk_fakes.generate_fake_resource(_region.Region) + + self.default_limit = 10 + self.resource_name = 'foobars' + + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + + self.registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.cmd = registered_limit.SetRegisteredLimit(self.app, None) def test_registered_limit_set_description(self): - registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) - registered_limit['description'] = ( - identity_fakes.registered_limit_description + updated_description = 'default limit of foobars' + updated_registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + id=self.registered_limit.id, + description=updated_description, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, ) - self.registered_limit_mock.update.return_value = fakes.FakeResource( - None, registered_limit, loaded=True + self.identity_sdk_client.update_registered_limit.return_value = ( + updated_registered_limit ) arglist = [ '--description', - identity_fakes.registered_limit_description, - identity_fakes.registered_limit_id, + updated_description, + self.registered_limit.id, ] verifylist = [ - ('description', identity_fakes.registered_limit_description), - ('registered_limit_id', identity_fakes.registered_limit_id), + ('description', updated_description), + ('registered_limit_id', self.registered_limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.update.assert_called_with( - identity_fakes.registered_limit_id, - service=None, - resource_name=None, - default_limit=None, - description=identity_fakes.registered_limit_description, - region=None, + self.identity_sdk_client.update_registered_limit.assert_called_with( + self.registered_limit.id, + description=updated_description, ) collist = ( @@ -285,43 +378,46 @@ def test_registered_limit_set_description(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, - identity_fakes.registered_limit_description, - identity_fakes.registered_limit_id, + self.default_limit, + updated_description, + self.registered_limit.id, None, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_registered_limit_set_default_limit(self): - registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) - default_limit = 20 - registered_limit['default_limit'] = default_limit - self.registered_limit_mock.update.return_value = fakes.FakeResource( - None, registered_limit, loaded=True + updated_default_limit = 20 + updated_registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + id=self.registered_limit.id, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=updated_default_limit, + resource_name=self.resource_name, + ) + self.identity_sdk_client.update_registered_limit.return_value = ( + updated_registered_limit ) arglist = [ '--default-limit', - str(default_limit), - identity_fakes.registered_limit_id, + str(updated_default_limit), + self.registered_limit.id, ] verifylist = [ - ('default_limit', default_limit), - ('registered_limit_id', identity_fakes.registered_limit_id), + ('default_limit', updated_default_limit), + ('registered_limit_id', self.registered_limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.update.assert_called_with( - identity_fakes.registered_limit_id, - service=None, - resource_name=None, - default_limit=default_limit, - description=None, - region=None, + self.identity_sdk_client.update_registered_limit.assert_called_with( + self.registered_limit.id, + default_limit=updated_default_limit, ) collist = ( @@ -334,43 +430,46 @@ def test_registered_limit_set_default_limit(self): ) self.assertEqual(collist, columns) datalist = ( - default_limit, + updated_default_limit, None, - identity_fakes.registered_limit_id, + self.registered_limit.id, None, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_registered_limit_set_resource_name(self): - registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) - resource_name = 'volumes' - registered_limit['resource_name'] = resource_name - self.registered_limit_mock.update.return_value = fakes.FakeResource( - None, registered_limit, loaded=True + updated_resource_name = 'volumes' + updated_registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + id=self.registered_limit.id, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=updated_resource_name, + ) + self.identity_sdk_client.update_registered_limit.return_value = ( + updated_registered_limit ) arglist = [ '--resource-name', - resource_name, - identity_fakes.registered_limit_id, + updated_resource_name, + self.registered_limit.id, ] verifylist = [ - ('resource_name', resource_name), - ('registered_limit_id', identity_fakes.registered_limit_id), + ('resource_name', updated_resource_name), + ('registered_limit_id', self.registered_limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.update.assert_called_with( - identity_fakes.registered_limit_id, - service=None, - resource_name=resource_name, - default_limit=None, - description=None, - region=None, + self.identity_sdk_client.update_registered_limit.assert_called_with( + self.registered_limit.id, + resource_name=updated_resource_name, ) collist = ( @@ -383,40 +482,43 @@ def test_registered_limit_set_resource_name(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, + self.default_limit, None, - identity_fakes.registered_limit_id, + self.registered_limit.id, None, - resource_name, - identity_fakes.service_id, + updated_resource_name, + self.service.id, ) self.assertEqual(datalist, data) def test_registered_limit_set_service(self): - registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) - service = identity_fakes.FakeService.create_one_service() - registered_limit['service_id'] = service.id - self.registered_limit_mock.update.return_value = fakes.FakeResource( - None, registered_limit, loaded=True + updated_service = sdk_fakes.generate_fake_resource(_service.Service) + self.identity_sdk_client.find_service.return_value = updated_service + updated_registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + id=self.registered_limit.id, + description=None, + region_id=None, + service_id=updated_service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.identity_sdk_client.update_registered_limit.return_value = ( + updated_registered_limit ) - self.services_mock.get.return_value = service - arglist = ['--service', service.id, identity_fakes.registered_limit_id] + arglist = ['--service', updated_service.id, self.registered_limit.id] verifylist = [ - ('service', service.id), - ('registered_limit_id', identity_fakes.registered_limit_id), + ('service', updated_service.id), + ('registered_limit_id', self.registered_limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.update.assert_called_with( - identity_fakes.registered_limit_id, - service=service, - resource_name=None, - default_limit=None, - description=None, - region=None, + self.identity_sdk_client.update_registered_limit.assert_called_with( + self.registered_limit.id, + service_id=updated_service.id, ) collist = ( @@ -429,42 +531,43 @@ def test_registered_limit_set_service(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, + self.default_limit, None, - identity_fakes.registered_limit_id, + self.registered_limit.id, None, - identity_fakes.registered_limit_resource_name, - service.id, + self.resource_name, + updated_service.id, ) self.assertEqual(datalist, data) def test_registered_limit_set_region(self): - registered_limit = copy.deepcopy(identity_fakes.REGISTERED_LIMIT) - region = identity_fakes.REGION - region['id'] = 'RegionTwo' - region = fakes.FakeResource(None, copy.deepcopy(region), loaded=True) - registered_limit['region_id'] = region.id - self.registered_limit_mock.update.return_value = fakes.FakeResource( - None, registered_limit, loaded=True + updated_region = sdk_fakes.generate_fake_resource(_region.Region) + self.identity_sdk_client.get_region.return_value = updated_region + updated_registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + id=self.registered_limit.id, + description=None, + region_id=updated_region.id, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.identity_sdk_client.update_registered_limit.return_value = ( + updated_registered_limit ) - self.regions_mock.get.return_value = region - arglist = ['--region', region.id, identity_fakes.registered_limit_id] + arglist = ['--region', updated_region.id, self.registered_limit.id] verifylist = [ - ('region', region.id), - ('registered_limit_id', identity_fakes.registered_limit_id), + ('region', updated_region.id), + ('registered_limit_id', self.registered_limit.id), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.update.assert_called_with( - identity_fakes.registered_limit_id, - service=None, - resource_name=None, - default_limit=None, - description=None, - region=region, + self.identity_sdk_client.update_registered_limit.assert_called_with( + self.registered_limit.id, + region_id=updated_region.id, ) collist = ( @@ -477,54 +580,86 @@ def test_registered_limit_set_region(self): ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, + self.default_limit, None, - identity_fakes.registered_limit_id, - region.id, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, + self.registered_limit.id, + updated_region.id, + self.resource_name, + self.service.id, ) self.assertEqual(datalist, data) -class TestRegisteredLimitList(TestRegisteredLimit): +class TestRegisteredLimitList(identity_fakes.TestIdentityv3): def setUp(self): super().setUp() - self.registered_limit_mock.get.return_value = fakes.FakeResource( - None, copy.deepcopy(identity_fakes.REGISTERED_LIMIT), loaded=True - ) + self.service = sdk_fakes.generate_fake_resource(_service.Service) + self.region = sdk_fakes.generate_fake_resource(_region.Region) - self.cmd = registered_limit.ShowRegisteredLimit(self.app, None) + self.description = 'default limit of foobars' + self.default_limit = 10 + self.resource_name = 'foobars' - def test_limit_show(self): - arglist = [identity_fakes.registered_limit_id] - verifylist = [ - ('registered_limit_id', identity_fakes.registered_limit_id) + self.identity_sdk_client.find_service.return_value = self.service + self.identity_sdk_client.get_region.return_value = self.region + + self.registered_limit = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=None, + region_id=None, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.registered_limit_with_options = sdk_fakes.generate_fake_resource( + resource_type=_registered_limit.RegisteredLimit, + description=self.description, + region_id=self.region.id, + service_id=self.service.id, + default_limit=self.default_limit, + resource_name=self.resource_name, + ) + self.identity_sdk_client.registered_limits.return_value = [ + self.registered_limit, + self.registered_limit_with_options, ] + + self.cmd = registered_limit.ListRegisteredLimit(self.app, None) + + def test_registered_limit_list(self): + arglist = [] + verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.registered_limit_mock.get.assert_called_with( - identity_fakes.registered_limit_id - ) - + self.identity_sdk_client.registered_limits.assert_called_with() collist = ( - 'default_limit', - 'description', - 'id', - 'region_id', - 'resource_name', - 'service_id', + "ID", + "Service ID", + "Resource Name", + "Default Limit", + "Description", + "Region ID", ) self.assertEqual(collist, columns) datalist = ( - identity_fakes.registered_limit_default_limit, - None, - identity_fakes.registered_limit_id, - None, - identity_fakes.registered_limit_resource_name, - identity_fakes.service_id, - ) - self.assertEqual(datalist, data) + ( + self.registered_limit.id, + self.service.id, + self.resource_name, + self.default_limit, + None, + None, + ), + ( + self.registered_limit_with_options.id, + self.service.id, + self.resource_name, + self.default_limit, + self.description, + self.region.id, + ), + ) + self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 134ba5a0bc..00b310e130 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -1773,7 +1773,7 @@ def setUp(self): # Get the command object to test self.cmd = user.ShowUser(self.app, None) - self.identity_client.auth.client.get_user_id.return_value = ( # noqa: E501 + self.identity_client.auth.client.get_user_id.return_value = ( self.user.id ) self.identity_client.tokens.get_token_data.return_value = { diff --git a/openstackclient/tests/unit/image/v2/test_image.py b/openstackclient/tests/unit/image/v2/test_image.py index e6de9f2ebd..98a18cf91f 100644 --- a/openstackclient/tests/unit/image/v2/test_image.py +++ b/openstackclient/tests/unit/image/v2/test_image.py @@ -305,7 +305,7 @@ def test_image_create_import(self, raw_input): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.create_image.assert_called_with( name=self.new_image.name, @@ -336,7 +336,7 @@ def test_image_create_from_volume(self, mock_get_data_f): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.volume_sdk_client.upload_volume_to_image.assert_called_once_with( volume.id, @@ -397,7 +397,7 @@ def test_image_create_from_volume_v31(self, mock_get_data_f): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.volume_sdk_client.upload_volume_to_image.assert_called_once_with( volume.id, @@ -946,7 +946,7 @@ def test_image_list_marker_option(self, fr_mock): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.images.assert_called_with( marker=self._image.id, ) @@ -966,7 +966,7 @@ def test_image_list_name_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.images.assert_called_with( name='abc', # marker=self._image.id @@ -982,7 +982,7 @@ def test_image_list_status_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.images.assert_called_with(status='active') def test_image_list_hidden_option(self): @@ -994,7 +994,7 @@ def test_image_list_hidden_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.images.assert_called_with(is_hidden=True) def test_image_list_tag_option(self): @@ -1004,7 +1004,7 @@ def test_image_list_tag_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + self.cmd.take_action(parsed_args) self.image_client.images.assert_called_with(tag=['abc', 'cba']) @@ -1329,7 +1329,7 @@ def test_image_set_membership_accept_with_project_no_owner_change(self): self.image_client.update_image.assert_called() call_args = self.image_client.update_image.call_args if call_args: - args, kwargs = call_args + _args, kwargs = call_args self.assertNotIn('owner_id', kwargs) def test_image_set_membership_reject_with_project_no_owner_change(self): @@ -1366,7 +1366,7 @@ def test_image_set_membership_reject_with_project_no_owner_change(self): self.image_client.update_image.assert_called() call_args = self.image_client.update_image.call_args if call_args: - args, kwargs = call_args + _args, kwargs = call_args self.assertNotIn('owner_id', kwargs) def test_image_set_membership_pending_with_project_no_owner_change(self): @@ -1403,7 +1403,7 @@ def test_image_set_membership_pending_with_project_no_owner_change(self): self.image_client.update_image.assert_called() call_args = self.image_client.update_image.call_args if call_args: - args, kwargs = call_args + _args, kwargs = call_args self.assertNotIn('owner_id', kwargs) def test_image_set_options(self): @@ -2085,7 +2085,7 @@ def test_import_image__glance_direct(self): remote_image_id=None, remote_service_interface=None, stores=None, - all_stores=None, + all_stores=False, all_stores_must_succeed=False, ) @@ -2115,7 +2115,7 @@ def test_import_image__web_download(self): remote_image_id=None, remote_service_interface=None, stores=None, - all_stores=None, + all_stores=False, all_stores_must_succeed=False, ) @@ -2253,7 +2253,7 @@ def test_import_image__copy_image(self): remote_image_id=None, remote_service_interface=None, stores=['fast'], - all_stores=None, + all_stores=False, all_stores_must_succeed=False, ) @@ -2285,7 +2285,7 @@ def test_import_image__copy_image_disallow_failure(self): remote_image_id=None, remote_service_interface=None, stores=['fast'], - all_stores=None, + all_stores=False, all_stores_must_succeed=True, ) @@ -2320,7 +2320,7 @@ def test_import_image__glance_download(self): remote_image_id='remote-image-id', remote_service_interface='private', stores=None, - all_stores=None, + all_stores=False, all_stores_must_succeed=False, ) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py b/openstackclient/tests/unit/network/v2/taas/test_tap_flow.py similarity index 85% rename from openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py rename to openstackclient/tests/unit/network/v2/taas/test_tap_flow.py index 8e4f185c8d..8cd1c3f158 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_flow.py +++ b/openstackclient/tests/unit/network/v2/taas/test_tap_flow.py @@ -77,13 +77,9 @@ def test_create_tap_flow(self): 'direction': 'BOTH', }, ) - self.app.client_manager.network.create_tap_flow.return_value = ( - fake_tap_flow - ) - self.app.client_manager.network.find_port.return_value = fake_port - self.app.client_manager.network.find_tap_service.return_value = ( - fake_tap_service - ) + self.network_client.create_tap_flow.return_value = fake_tap_flow + self.network_client.find_port.return_value = fake_port + self.network_client.find_tap_service.return_value = fake_tap_service arg_list = [ '--name', fake_tap_flow['name'], @@ -103,8 +99,7 @@ def test_create_tap_flow(self): parsed_args = self.check_parser(self.cmd, arg_list, verify_list) columns, data = self.cmd.take_action(parsed_args) - mock_create_t_f = self.app.client_manager.network.create_tap_flow - mock_create_t_f.assert_called_once_with( + self.network_client.create_tap_flow.assert_called_once_with( **{ 'name': fake_tap_flow['name'], 'source_port': fake_tap_flow['source_port'], @@ -129,7 +124,7 @@ def test_list_tap_flows(self): fake_tap_flows = list( sdk_fakes.generate_fake_resources(_tap_flow.TapFlow, count=2) ) - self.app.client_manager.network.tap_flows.return_value = fake_tap_flows + self.network_client.tap_flows.return_value = fake_tap_flows arg_list = [] verify_list = [] @@ -137,7 +132,7 @@ def test_list_tap_flows(self): headers, data = self.cmd.take_action(parsed_args) - self.app.client_manager.network.tap_flows.assert_called_once() + self.network_client.tap_flows.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertCountEqual( list(data), @@ -151,7 +146,7 @@ def test_list_tap_flows(self): class TestDeleteTapFlow(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_flow.side_effect = ( + self.network_client.find_tap_flow.side_effect = ( lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) ) self.cmd = osc_tap_flow.DeleteTapFlow(self.app, None) @@ -171,8 +166,9 @@ def test_delete_tap_flow(self): result = self.cmd.take_action(parsed_args) - mock_delete_tap_flow = self.app.client_manager.network.delete_tap_flow - mock_delete_tap_flow.assert_called_once_with(fake_tap_flow['id']) + self.network_client.delete_tap_flow.assert_called_once_with( + fake_tap_flow['id'] + ) self.assertIsNone(result) @@ -190,7 +186,7 @@ class TestShowTapFlow(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_flow.side_effect = ( + self.network_client.find_tap_flow.side_effect = ( lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) ) self.cmd = osc_tap_flow.ShowTapFlow(self.app, None) @@ -198,9 +194,7 @@ def setUp(self): def test_show_tap_flow(self): """Test Show tap flow.""" fake_tap_flow = sdk_fakes.generate_fake_resource(_tap_flow.TapFlow) - self.app.client_manager.network.get_tap_flow.return_value = ( - fake_tap_flow - ) + self.network_client.get_tap_flow.return_value = fake_tap_flow arg_list = [ fake_tap_flow['id'], ] @@ -212,7 +206,7 @@ def test_show_tap_flow(self): headers, data = self.cmd.take_action(parsed_args) - self.app.client_manager.network.get_tap_flow.assert_called_once_with( + self.network_client.get_tap_flow.assert_called_once_with( fake_tap_flow['id'] ) self.assertEqual(self.columns, headers) @@ -245,7 +239,7 @@ class TestUpdateTapFlow(network_fakes.TestNetworkV2): def setUp(self): super().setUp() self.cmd = osc_tap_flow.UpdateTapFlow(self.app, None) - self.app.client_manager.network.find_tap_flow.side_effect = ( + self.network_client.find_tap_flow.side_effect = ( lambda name_or_id, ignore_missing: _tap_flow.TapFlow(id=name_or_id) ) @@ -255,9 +249,7 @@ def test_update_tap_flow(self): new_tap_flow = copy.deepcopy(fake_tap_flow) new_tap_flow['name'] = self._new_name - self.app.client_manager.network.update_tap_flow.return_value = ( - new_tap_flow - ) + self.network_client.update_tap_flow.return_value = new_tap_flow arg_list = [ fake_tap_flow['id'], @@ -270,7 +262,8 @@ def test_update_tap_flow(self): columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} - mock_update_t_f = self.app.client_manager.network.update_tap_flow - mock_update_t_f.assert_called_once_with(new_tap_flow['id'], **attrs) + self.network_client.update_tap_flow.assert_called_once_with( + new_tap_flow['id'], **attrs + ) self.assertEqual(self.columns, columns) self.assertEqual(_get_data(new_tap_flow, self.columns), data) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py b/openstackclient/tests/unit/network/v2/taas/test_tap_mirror.py similarity index 83% rename from openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py rename to openstackclient/tests/unit/network/v2/taas/test_tap_mirror.py index 10f3251c36..1d153b51e9 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_mirror.py +++ b/openstackclient/tests/unit/network/v2/taas/test_tap_mirror.py @@ -64,11 +64,9 @@ def test_create_tap_mirror(self): fake_tap_mirror = sdk_fakes.generate_fake_resource( tap_mirror.TapMirror, **{'port_id': port_id, 'directions': 'IN=99'} ) - self.app.client_manager.network.create_tap_mirror.return_value = ( - fake_tap_mirror - ) - self.app.client_manager.network.find_port.return_value = fake_port - self.app.client_manager.network.find_tap_mirror.side_effect = ( + self.network_client.create_tap_mirror.return_value = fake_tap_mirror + self.network_client.find_port.return_value = fake_port + self.network_client.find_tap_mirror.side_effect = ( lambda _, name_or_id: {'id': name_or_id} ) arg_list = [ @@ -96,13 +94,10 @@ def test_create_tap_mirror(self): ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) - self.app.client_manager.network.find_tap_mirror.return_value = ( - fake_tap_mirror - ) + self.network_client.find_tap_mirror.return_value = fake_tap_mirror columns, data = self.cmd.take_action(parsed_args) - create_tap_m_mock = self.app.client_manager.network.create_tap_mirror - create_tap_m_mock.assert_called_once_with( + self.network_client.create_tap_mirror.assert_called_once_with( **{ 'name': fake_tap_mirror['name'], 'port_id': fake_tap_mirror['port_id'], @@ -128,9 +123,7 @@ def test_list_tap_mirror(self): fake_tap_mirrors = list( sdk_fakes.generate_fake_resources(tap_mirror.TapMirror, count=4) ) - self.app.client_manager.network.tap_mirrors.return_value = ( - fake_tap_mirrors - ) + self.network_client.tap_mirrors.return_value = fake_tap_mirrors arg_list = [] verify_list = [] @@ -139,7 +132,7 @@ def test_list_tap_mirror(self): headers, data = self.cmd.take_action(parsed_args) - self.app.client_manager.network.tap_mirrors.assert_called_once() + self.network_client.tap_mirrors.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertCountEqual( list(data), @@ -153,7 +146,7 @@ def test_list_tap_mirror(self): class TestDeleteTapMirror(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_mirror.side_effect = ( + self.network_client.find_tap_mirror.side_effect = ( lambda name_or_id, ignore_missing: tap_mirror.TapMirror( id=name_or_id ) @@ -177,8 +170,9 @@ def test_delete_tap_mirror(self): parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) - mock_delete_tap_m = self.app.client_manager.network.delete_tap_mirror - mock_delete_tap_m.assert_called_once_with(fake_tap_mirror['id']) + self.network_client.delete_tap_mirror.assert_called_once_with( + fake_tap_mirror['id'] + ) self.assertIsNone(result) @@ -196,7 +190,7 @@ class TestShowTapMirror(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_mirror.side_effect = ( + self.network_client.find_tap_mirror.side_effect = ( lambda name_or_id, ignore_missing: tap_mirror.TapMirror( id=name_or_id ) @@ -209,9 +203,7 @@ def test_show_tap_mirror(self): fake_tap_mirror = sdk_fakes.generate_fake_resource( tap_mirror.TapMirror ) - self.app.client_manager.network.get_tap_mirror.return_value = ( - fake_tap_mirror - ) + self.network_client.get_tap_mirror.return_value = fake_tap_mirror arg_list = [ fake_tap_mirror['id'], ] @@ -223,8 +215,9 @@ def test_show_tap_mirror(self): headers, data = self.cmd.take_action(parsed_args) - mock_get_tap_m = self.app.client_manager.network.get_tap_mirror - mock_get_tap_m.assert_called_once_with(fake_tap_mirror['id']) + self.network_client.get_tap_mirror.assert_called_once_with( + fake_tap_mirror['id'] + ) self.assertEqual(self.columns, headers) fake_data = _get_data( fake_tap_mirror, osc_tap_mirror._get_columns(fake_tap_mirror)[1] @@ -248,7 +241,7 @@ class TestUpdateTapMirror(network_fakes.TestNetworkV2): def setUp(self): super().setUp() self.cmd = osc_tap_mirror.UpdateTapMirror(self.app, None) - self.app.client_manager.network.find_tap_mirror.side_effect = ( + self.network_client.find_tap_mirror.side_effect = ( lambda name_or_id, ignore_missing: tap_mirror.TapMirror( id=name_or_id ) @@ -262,9 +255,7 @@ def test_update_tap_mirror(self): new_tap_mirror = copy.deepcopy(fake_tap_mirror) new_tap_mirror['name'] = self._new_name - self.app.client_manager.network.update_tap_mirror.return_value = ( - new_tap_mirror - ) + self.network_client.update_tap_mirror.return_value = new_tap_mirror arg_list = [ fake_tap_mirror['id'], @@ -277,8 +268,7 @@ def test_update_tap_mirror(self): columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} - mock_update_tap_m = self.app.client_manager.network.update_tap_mirror - mock_update_tap_m.assert_called_once_with( + self.network_client.update_tap_mirror.assert_called_once_with( fake_tap_mirror['id'], **attrs ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py b/openstackclient/tests/unit/network/v2/taas/test_tap_service.py similarity index 82% rename from openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py rename to openstackclient/tests/unit/network/v2/taas/test_tap_service.py index fa766891ee..dac094f90f 100644 --- a/openstackclient/tests/unit/network/v2/taas/test_osc_tap_service.py +++ b/openstackclient/tests/unit/network/v2/taas/test_tap_service.py @@ -65,11 +65,9 @@ def test_create_tap_service(self): fake_tap_service = sdk_fakes.generate_fake_resource( tap_service.TapService, **{'port_id': port_id} ) - self.app.client_manager.network.create_tap_service.return_value = ( - fake_tap_service - ) - self.app.client_manager.network.find_port.return_value = fake_port - self.app.client_manager.network.find_tap_service.side_effect = ( + self.network_client.create_tap_service.return_value = fake_tap_service + self.network_client.find_port.return_value = fake_port + self.network_client.find_tap_service.side_effect = ( lambda _, name_or_id: {'id': name_or_id} ) arg_list = [ @@ -85,13 +83,10 @@ def test_create_tap_service(self): ] parsed_args = self.check_parser(self.cmd, arg_list, verify_list) - self.app.client_manager.network.find_tap_service.return_value = ( - fake_tap_service - ) + self.network_client.find_tap_service.return_value = fake_tap_service columns, data = self.cmd.take_action(parsed_args) - create_tap_s_mock = self.app.client_manager.network.create_tap_service - create_tap_s_mock.assert_called_once_with( + self.network_client.create_tap_service.assert_called_once_with( **{ 'name': fake_tap_service['name'], 'port_id': fake_tap_service['port_id'], @@ -114,9 +109,7 @@ def test_list_tap_service(self): fake_tap_services = list( sdk_fakes.generate_fake_resources(tap_service.TapService, count=4) ) - self.app.client_manager.network.tap_services.return_value = ( - fake_tap_services - ) + self.network_client.tap_services.return_value = fake_tap_services arg_list = [] verify_list = [] @@ -125,7 +118,7 @@ def test_list_tap_service(self): headers, data = self.cmd.take_action(parsed_args) - self.app.client_manager.network.tap_services.assert_called_once() + self.network_client.tap_services.assert_called_once() self.assertEqual(headers, list(headers_long)) self.assertCountEqual( list(data), @@ -139,7 +132,7 @@ def test_list_tap_service(self): class TestDeleteTapService(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_service.side_effect = ( + self.network_client.find_tap_service.side_effect = ( lambda name_or_id, ignore_missing: tap_service.TapService( id=name_or_id ) @@ -163,8 +156,9 @@ def test_delete_tap_service(self): parsed_args = self.check_parser(self.cmd, arg_list, verify_list) result = self.cmd.take_action(parsed_args) - mock_delete_tap_s = self.app.client_manager.network.delete_tap_service - mock_delete_tap_s.assert_called_once_with(fake_tap_service['id']) + self.network_client.delete_tap_service.assert_called_once_with( + fake_tap_service['id'] + ) self.assertIsNone(result) @@ -180,7 +174,7 @@ class TestShowTapService(network_fakes.TestNetworkV2): def setUp(self): super().setUp() - self.app.client_manager.network.find_tap_service.side_effect = ( + self.network_client.find_tap_service.side_effect = ( lambda name_or_id, ignore_missing: tap_service.TapService( id=name_or_id ) @@ -193,9 +187,7 @@ def test_show_tap_service(self): fake_tap_service = sdk_fakes.generate_fake_resource( tap_service.TapService ) - self.app.client_manager.network.get_tap_service.return_value = ( - fake_tap_service - ) + self.network_client.get_tap_service.return_value = fake_tap_service arg_list = [ fake_tap_service['id'], ] @@ -207,8 +199,9 @@ def test_show_tap_service(self): headers, data = self.cmd.take_action(parsed_args) - mock_get_tap_s = self.app.client_manager.network.get_tap_service - mock_get_tap_s.assert_called_once_with(fake_tap_service['id']) + self.network_client.get_tap_service.assert_called_once_with( + fake_tap_service['id'] + ) self.assertEqual(self.columns, headers) fake_data = _get_data( fake_tap_service, osc_tap_service._get_columns(fake_tap_service)[1] @@ -231,7 +224,7 @@ class TestUpdateTapService(network_fakes.TestNetworkV2): def setUp(self): super().setUp() self.cmd = osc_tap_service.UpdateTapService(self.app, None) - self.app.client_manager.network.find_tap_service.side_effect = ( + self.network_client.find_tap_service.side_effect = ( lambda name_or_id, ignore_missing: tap_service.TapService( id=name_or_id ) @@ -245,9 +238,7 @@ def test_update_tap_service(self): new_tap_service = copy.deepcopy(fake_tap_service) new_tap_service['name'] = self._new_name - self.app.client_manager.network.update_tap_service.return_value = ( - new_tap_service - ) + self.network_client.update_tap_service.return_value = new_tap_service arg_list = [ fake_tap_service['id'], @@ -260,8 +251,7 @@ def test_update_tap_service(self): columns, data = self.cmd.take_action(parsed_args) attrs = {'name': self._new_name} - mock_update_tap_s = self.app.client_manager.network.update_tap_service - mock_update_tap_s.assert_called_once_with( + self.network_client.update_tap_service.assert_called_once_with( fake_tap_service['id'], **attrs ) self.assertEqual(self.columns, columns) diff --git a/openstackclient/tests/unit/network/v2/test_address_group.py b/openstackclient/tests/unit/network/v2/test_address_group.py index 48f706631f..97218ab0a6 100644 --- a/openstackclient/tests/unit/network/v2/test_address_group.py +++ b/openstackclient/tests/unit/network/v2/test_address_group.py @@ -519,7 +519,7 @@ def test_unset_one_address(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network_client.remove_addresses_from_address_group.assert_called_once_with( # noqa: E501 + self.network_client.remove_addresses_from_address_group.assert_called_once_with( self._address_group, ['10.0.0.2/32'] ) self.assertIsNone(result) @@ -539,7 +539,7 @@ def test_unset_multiple_addresses(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) result = self.cmd.take_action(parsed_args) - self.network_client.remove_addresses_from_address_group.assert_called_once_with( # noqa: E501 + self.network_client.remove_addresses_from_address_group.assert_called_once_with( self._address_group, ['10.0.0.2/32', '2001::/16'] ) self.assertIsNone(result) 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 ab0ec176a0..685402a763 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip_network.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip_network.py @@ -425,7 +425,8 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): 'Floating Network', 'Project', ) - columns_long = columns + ( + columns_long = ( + *columns, 'Router', 'Status', 'Description', 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 33b9011c65..354036d34f 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,14 +39,14 @@ def setUp(self): class TestCreateFloatingIPPortForwarding(TestFloatingIPPortForwarding): def setUp(self): super().setUp() - self.new_port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( # noqa: E501 + self.new_port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( attrs={ 'internal_port_id': self.port.id, 'floatingip_id': self.floating_ip.id, } ) - self.new_port_forwarding_with_ranges = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( # noqa: E501 + self.new_port_forwarding_with_ranges = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( use_range=True, attrs={ 'internal_port_id': self.port.id, @@ -144,15 +144,15 @@ def test_create_all_options_with_range(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network_client.create_floating_ip_port_forwarding.assert_called_once_with( # noqa: E501 + self.network_client.create_floating_ip_port_forwarding.assert_called_once_with( self.new_port_forwarding.floatingip_id, **{ - 'external_port_range': self.new_port_forwarding_with_ranges.external_port_range, # noqa: E501 - 'internal_ip_address': self.new_port_forwarding_with_ranges.internal_ip_address, # noqa: E501 - 'internal_port_range': self.new_port_forwarding_with_ranges.internal_port_range, # noqa: E501 - 'internal_port_id': self.new_port_forwarding_with_ranges.internal_port_id, # noqa: E501 + 'external_port_range': self.new_port_forwarding_with_ranges.external_port_range, + 'internal_ip_address': self.new_port_forwarding_with_ranges.internal_ip_address, + 'internal_port_range': self.new_port_forwarding_with_ranges.internal_port_range, + 'internal_port_id': self.new_port_forwarding_with_ranges.internal_port_id, 'protocol': self.new_port_forwarding_with_ranges.protocol, - 'description': self.new_port_forwarding_with_ranges.description, # noqa: E501 + 'description': self.new_port_forwarding_with_ranges.description, }, ) self.assertEqual(self.columns, columns) @@ -325,11 +325,11 @@ def test_create_all_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network_client.create_floating_ip_port_forwarding.assert_called_once_with( # noqa: E501 + self.network_client.create_floating_ip_port_forwarding.assert_called_once_with( self.new_port_forwarding.floatingip_id, **{ 'external_port': self.new_port_forwarding.external_port, - 'internal_ip_address': self.new_port_forwarding.internal_ip_address, # noqa: E501 + 'internal_ip_address': self.new_port_forwarding.internal_ip_address, 'internal_port': self.new_port_forwarding.internal_port, 'internal_port_id': self.new_port_forwarding.internal_port_id, 'protocol': self.new_port_forwarding.protocol, @@ -375,7 +375,7 @@ def test_port_forwarding_delete(self): result = self.cmd.take_action(parsed_args) - self.network_client.delete_floating_ip_port_forwarding.assert_called_once_with( # noqa: E501 + self.network_client.delete_floating_ip_port_forwarding.assert_called_once_with( self.floating_ip.id, self._port_forwarding[0].id, ignore_missing=False, @@ -553,7 +553,7 @@ class TestSetFloatingIPPortForwarding(TestFloatingIPPortForwarding): # The Port Forwarding to set. def setUp(self): super().setUp() - self._port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( # noqa: E501 + self._port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( attrs={ 'floatingip_id': self.floating_ip.id, } @@ -675,7 +675,7 @@ class TestShowFloatingIPPortForwarding(TestFloatingIPPortForwarding): def setUp(self): super().setUp() - self._port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( # noqa: E501 + self._port_forwarding = network_fakes.FakeFloatingIPPortForwarding.create_one_port_forwarding( attrs={ 'floatingip_id': self.floating_ip.id, } diff --git a/openstackclient/tests/unit/network/v2/test_network_agent.py b/openstackclient/tests/unit/network/v2/test_network_agent.py index 48b394d7a5..1198e1a6b0 100644 --- a/openstackclient/tests/unit/network/v2/test_network_agent.py +++ b/openstackclient/tests/unit/network/v2/test_network_agent.py @@ -340,8 +340,8 @@ def test_network_agents_list_routers_with_long_option(self): ) # Add a column 'HA State' and corresponding data. - router_agent_columns = self.columns + ('HA State',) - router_agent_data = [d + ('',) for d in self.data] + router_agent_columns = (*self.columns, 'HA State') + router_agent_data = [(*d, '') for d in self.data] self.assertEqual(router_agent_columns, columns) self.assertEqual(len(router_agent_data), len(list(data))) 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 d13bd8cf95..502b881dae 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 @@ -167,7 +167,7 @@ def test_show_dry_run_no_project(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + _columns, _data = self.cmd.take_action(parsed_args) self.network_client.validate_auto_allocated_topology.assert_called_with( None @@ -185,7 +185,7 @@ def test_show_dry_run_project_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + _columns, _data = self.cmd.take_action(parsed_args) self.network_client.validate_auto_allocated_topology.assert_called_with( self.project.id @@ -206,7 +206,7 @@ def test_show_dry_run_project_domain_option(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + _columns, _data = self.cmd.take_action(parsed_args) self.network_client.validate_auto_allocated_topology.assert_called_with( self.project.id diff --git a/openstackclient/tests/unit/network/v2/test_network_flavor.py b/openstackclient/tests/unit/network/v2/test_network_flavor.py index 10038e3933..4044312a15 100644 --- a/openstackclient/tests/unit/network/v2/test_network_flavor.py +++ b/openstackclient/tests/unit/network/v2/test_network_flavor.py @@ -71,7 +71,7 @@ def test_add_flavor_to_service_profile(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.network_client.associate_flavor_with_service_profile.assert_called_once_with( # noqa: E501 + self.network_client.associate_flavor_with_service_profile.assert_called_once_with( self.network_flavor, self.service_profile ) @@ -377,7 +377,7 @@ def test_remove_flavor_from_service_profile(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) self.cmd.take_action(parsed_args) - self.network_client.disassociate_flavor_from_service_profile.assert_called_once_with( # noqa: E501 + self.network_client.disassociate_flavor_from_service_profile.assert_called_once_with( self.network_flavor, self.service_profile ) 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 17f40ef6b9..29f7754340 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_policy.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_policy.py @@ -28,7 +28,7 @@ class TestQosPolicy(network_fakes.TestNetworkV2): def setUp(self): super().setUp() # Get a shortcut to the ProjectManager Mock - self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock = self.identity_client.projects class TestCreateNetworkQosPolicy(TestQosPolicy): diff --git a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py index 4300304022..3448589fa2 100644 --- a/openstackclient/tests/unit/network/v2/test_network_qos_rule.py +++ b/openstackclient/tests/unit/network/v2/test_network_qos_rule.py @@ -226,7 +226,7 @@ def test_create_default_options(self): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.network_client.create_qos_minimum_packet_rate_rule.assert_called_once_with( # noqa: E501 + self.network_client.create_qos_minimum_packet_rate_rule.assert_called_once_with( self.qos_policy.id, **{ 'min_kpps': self.new_rule.min_kpps, @@ -613,7 +613,7 @@ def test_qos_policy_delete(self): self.network_client.find_qos_policy.assert_called_once_with( self.qos_policy.id, ignore_missing=False ) - self.network_client.delete_qos_minimum_packet_rate_rule.assert_called_once_with( # noqa: E501 + self.network_client.delete_qos_minimum_packet_rate_rule.assert_called_once_with( self.new_rule.id, self.qos_policy.id ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/network/v2/test_network_rbac.py b/openstackclient/tests/unit/network/v2/test_network_rbac.py index d3a7192453..656f2624ee 100644 --- a/openstackclient/tests/unit/network/v2/test_network_rbac.py +++ b/openstackclient/tests/unit/network/v2/test_network_rbac.py @@ -224,7 +224,7 @@ def test_network_rbac_create_with_target_all_projects(self): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + _columns, _data = self.cmd.take_action(parsed_args) self.network_client.create_rbac_policy.assert_called_with( **{ diff --git a/openstackclient/tests/unit/network/v2/test_network_segment.py b/openstackclient/tests/unit/network/v2/test_network_segment.py index ab71c32547..5bec650693 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment.py @@ -250,7 +250,7 @@ class TestListNetworkSegment(TestNetworkSegment): 'Network Type', 'Segment', ) - columns_long = columns + ('Physical Network',) + columns_long = (*columns, 'Physical Network') data = [] for _network_segment in _network_segments: diff --git a/openstackclient/tests/unit/network/v2/test_network_segment_range.py b/openstackclient/tests/unit/network/v2/test_network_segment_range.py index 9c9c900e36..db48fb248c 100644 --- a/openstackclient/tests/unit/network/v2/test_network_segment_range.py +++ b/openstackclient/tests/unit/network/v2/test_network_segment_range.py @@ -333,7 +333,7 @@ def test_create_all_options(self): 'shared': self._network_segment_range.shared, 'project_id': mock.ANY, 'network_type': self._network_segment_range.network_type, - 'physical_network': self._network_segment_range.physical_network, # noqa: E501 + 'physical_network': self._network_segment_range.physical_network, 'minimum': self._network_segment_range.minimum, 'maximum': self._network_segment_range.maximum, 'name': self._network_segment_range.name, @@ -450,10 +450,7 @@ class TestListNetworkSegmentRange(TestNetworkSegmentRange): 'Minimum ID', 'Maximum ID', ) - columns_long = columns + ( - 'Used', - 'Available', - ) + columns_long = (*columns, 'Used', 'Available') data = [] for _network_segment_range in _network_segment_ranges: @@ -544,11 +541,11 @@ class TestSetNetworkSegmentRange(TestNetworkSegmentRange): # The network segment range updated. minimum_updated = _network_segment_range.minimum - 5 maximum_updated = _network_segment_range.maximum + 5 - available_updated = ( - list(range(minimum_updated, 104)) - + [105] - + list(range(107, maximum_updated + 1)) - ) + available_updated = [ + *list(range(minimum_updated, 104)), + 105, + *list(range(107, maximum_updated + 1)), + ] _network_segment_range_updated = ( network_fakes.create_one_network_segment_range( attrs={ diff --git a/openstackclient/tests/unit/network/v2/test_network_trunk.py b/openstackclient/tests/unit/network/v2/test_network_trunk.py index 1056c21c30..452870c88d 100644 --- a/openstackclient/tests/unit/network/v2/test_network_trunk.py +++ b/openstackclient/tests/unit/network/v2/test_network_trunk.py @@ -468,7 +468,7 @@ class TestListNetworkTrunk(TestNetworkTrunk): ) columns = ('ID', 'Name', 'Parent Port', 'Description') - columns_long = columns + ('Status', 'State', 'Created At', 'Updated At') + columns_long = (*columns, 'Status', 'State', 'Created At', 'Updated At') data = [] for t in new_trunks: data.append((t['id'], t['name'], t['port_id'], t['description'])) diff --git a/openstackclient/tests/unit/network/v2/test_router.py b/openstackclient/tests/unit/network/v2/test_router.py index 6ebb7809eb..56faa79a6f 100644 --- a/openstackclient/tests/unit/network/v2/test_router.py +++ b/openstackclient/tests/unit/network/v2/test_router.py @@ -711,17 +711,14 @@ class TestListRouter(TestRouter): 'Distributed', 'HA', ) - columns_long = columns + ( + columns_long = ( + *columns, 'Routes', 'External gateway info', 'Availability zones', 'Tags', ) - columns_long_no_az = columns + ( - 'Routes', - 'External gateway info', - 'Tags', - ) + columns_long_no_az = (*columns, 'Routes', 'External gateway info', 'Tags') data = [] for r in routers: @@ -824,7 +821,7 @@ def test_router_list_no_ha_no_distributed(self): with mock.patch.object( self.network_client, "routers", return_value=_routers ): - columns, data = self.cmd.take_action(parsed_args) + columns, _data = self.cmd.take_action(parsed_args) self.assertNotIn("is_distributed", columns) self.assertNotIn("is_ha", columns) @@ -1900,7 +1897,7 @@ def test_show_no_ha_no_distributed(self): with mock.patch.object( self.network_client, "find_router", return_value=_router ): - columns, data = self.cmd.take_action(parsed_args) + columns, _data = self.cmd.take_action(parsed_args) self.assertNotIn("is_distributed", columns) self.assertNotIn("is_ha", columns) diff --git a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py index 9cab52e392..3c869aabf9 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_rule_compute.py @@ -411,7 +411,8 @@ class TestListSecurityGroupRuleCompute(compute_fakes.TestComputev2): 'Direction', 'Remote Security Group', ) - expected_columns_no_group = expected_columns_with_group + ( + expected_columns_no_group = ( + *expected_columns_with_group, 'Security Group', ) @@ -429,7 +430,8 @@ class TestListSecurityGroupRuleCompute(compute_fakes.TestComputev2): rule['port_range'], rule['remote_security_group'], ) - expected_rule_no_group = expected_rule_with_group + ( + expected_rule_no_group = ( + *expected_rule_with_group, _security_group_rule['parent_group_id'], ) expected_data_with_group.append(expected_rule_with_group) diff --git a/openstackclient/tests/unit/network/v2/test_subnet.py b/openstackclient/tests/unit/network/v2/test_subnet.py index e59168e517..e4b4342155 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet.py +++ b/openstackclient/tests/unit/network/v2/test_subnet.py @@ -798,7 +798,8 @@ class TestListSubnet(TestSubnet): 'Network', 'Subnet', ) - columns_long = columns + ( + columns_long = ( + *columns, 'Project', 'DHCP', 'Name Servers', diff --git a/openstackclient/tests/unit/network/v2/test_subnet_pool.py b/openstackclient/tests/unit/network/v2/test_subnet_pool.py index 013550ec1e..eed947bfc1 100644 --- a/openstackclient/tests/unit/network/v2/test_subnet_pool.py +++ b/openstackclient/tests/unit/network/v2/test_subnet_pool.py @@ -471,7 +471,8 @@ class TestListSubnetPool(TestSubnetPool): 'Name', 'Prefixes', ) - columns_long = columns + ( + columns_long = ( + *columns, 'Default Prefix Length', 'Address Scope', 'Default Subnet Pool', diff --git a/openstackclient/tests/unit/object/v1/fakes.py b/openstackclient/tests/unit/object/v1/fakes.py index eedccdc0e8..d43e5065f6 100644 --- a/openstackclient/tests/unit/object/v1/fakes.py +++ b/openstackclient/tests/unit/object/v1/fakes.py @@ -11,9 +11,8 @@ # 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 keystoneauth1 import session +from unittest import mock from openstackclient.api import object_store_v1 as object_store from openstackclient.tests.unit import utils @@ -80,12 +79,14 @@ object_upload_name = 'test-object-name' -class TestObjectv1(utils.TestCommand): +class FakeClientMixin: def setUp(self): super().setUp() - self.app.client_manager.session = session.Session() - self.app.client_manager.object_store = object_store.APIv1( - session=self.app.client_manager.session, - endpoint=ENDPOINT, + self.app.client_manager.object_store = mock.Mock( + spec=object_store.APIv1 ) + self.object_store_client = self.app.client_manager.object_store + + +class TestObjectV1(FakeClientMixin, utils.TestCommand): ... diff --git a/openstackclient/tests/unit/object/v1/test_container.py b/openstackclient/tests/unit/object/v1/test_container.py index 9143df9c9a..0ba9118bf6 100644 --- a/openstackclient/tests/unit/object/v1/test_container.py +++ b/openstackclient/tests/unit/object/v1/test_container.py @@ -14,48 +14,21 @@ # import copy -from unittest import mock -from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import container from openstackclient.tests.unit.object.v1 import fakes as object_fakes -AUTH_TOKEN = "foobar" -AUTH_URL = "http://0.0.0.0" - - -class FakeClient: - def __init__(self, endpoint=None, **kwargs): - self.endpoint = AUTH_URL - self.token = AUTH_TOKEN - - -class TestContainer(object_fakes.TestObjectv1): - columns = ('Name',) - +class TestContainerDelete(object_fakes.TestObjectV1): def setUp(self): super().setUp() - self.app.client_manager.object_store = object_store.APIv1( - session=mock.Mock(), - service_type="object-store", - ) - self.api = self.app.client_manager.object_store - -@mock.patch('openstackclient.api.object_store_v1.APIv1.object_delete') -@mock.patch('openstackclient.api.object_store_v1.APIv1.object_list') -@mock.patch('openstackclient.api.object_store_v1.APIv1.container_delete') -class TestContainerDelete(TestContainer): - def setUp(self): - super().setUp() + self.object_store_client.container_delete.return_value = None # Get the command object to test self.cmd = container.DeleteContainer(self.app, None) - def test_container_delete(self, c_mock, o_list_mock, o_delete_mock): - c_mock.return_value = None - + def test_container_delete(self): arglist = [ object_fakes.container_name, ] @@ -68,16 +41,17 @@ def test_container_delete(self, c_mock, o_list_mock, o_delete_mock): self.assertIsNone(self.cmd.take_action(parsed_args)) kwargs = {} - c_mock.assert_called_with( + self.object_store_client.container_delete.assert_called_with( container=object_fakes.container_name, **kwargs ) - self.assertFalse(o_list_mock.called) - self.assertFalse(o_delete_mock.called) + self.object_store_client.object_list.assert_not_called() + self.object_store_client.object_delete.assert_not_called() - def test_recursive_delete(self, c_mock, o_list_mock, o_delete_mock): - c_mock.return_value = None - o_list_mock.return_value = [object_fakes.OBJECT] - o_delete_mock.return_value = None + def test_recursive_delete(self): + self.object_store_client.object_delete.return_value = None + self.object_store_client.object_list.return_value = [ + object_fakes.OBJECT + ] arglist = [ '--recursive', @@ -91,20 +65,22 @@ def test_recursive_delete(self, c_mock, o_list_mock, o_delete_mock): self.assertIsNone(self.cmd.take_action(parsed_args)) - kwargs = {} - c_mock.assert_called_with( - container=object_fakes.container_name, **kwargs + self.object_store_client.container_delete.assert_called_with( + container=object_fakes.container_name ) - o_list_mock.assert_called_with(container=object_fakes.container_name) - o_delete_mock.assert_called_with( + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name + ) + self.object_store_client.object_delete.assert_called_with( container=object_fakes.container_name, object=object_fakes.OBJECT['name'], ) - def test_r_delete(self, c_mock, o_list_mock, o_delete_mock): - c_mock.return_value = None - o_list_mock.return_value = [object_fakes.OBJECT] - o_delete_mock.return_value = None + def test_r_delete(self): + self.object_store_client.object_delete.return_value = None + self.object_store_client.object_list.return_value = [ + object_fakes.OBJECT + ] arglist = [ '-r', @@ -118,27 +94,29 @@ def test_r_delete(self, c_mock, o_list_mock, o_delete_mock): self.assertIsNone(self.cmd.take_action(parsed_args)) - kwargs = {} - c_mock.assert_called_with( - container=object_fakes.container_name, **kwargs + self.object_store_client.container_delete.assert_called_with( + container=object_fakes.container_name ) - o_list_mock.assert_called_with(container=object_fakes.container_name) - o_delete_mock.assert_called_with( + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name + ) + self.object_store_client.object_delete.assert_called_with( container=object_fakes.container_name, object=object_fakes.OBJECT['name'], ) -@mock.patch('openstackclient.api.object_store_v1.APIv1.container_list') -class TestContainerList(TestContainer): +class TestContainerList(object_fakes.TestObjectV1): + columns = ('Name',) + def setUp(self): super().setUp() # Get the command object to test self.cmd = container.ListContainer(self.app, None) - def test_object_list_containers_no_options(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_no_options(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_3), copy.deepcopy(object_fakes.CONTAINER_2), @@ -153,9 +131,7 @@ def test_object_list_containers_no_options(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = {} - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with() self.assertEqual(self.columns, columns) datalist = ( @@ -165,8 +141,8 @@ def test_object_list_containers_no_options(self, c_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_containers_prefix(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_prefix(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_3), ] @@ -185,11 +161,9 @@ def test_object_list_containers_prefix(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'prefix': 'bit', - } - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with( + prefix='bit', + ) self.assertEqual(self.columns, columns) datalist = ( @@ -198,8 +172,8 @@ def test_object_list_containers_prefix(self, c_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_containers_marker(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_marker(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_3), ] @@ -221,12 +195,10 @@ def test_object_list_containers_marker(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'marker': object_fakes.container_name, - 'end_marker': object_fakes.container_name_3, - } - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with( + marker=object_fakes.container_name, + end_marker=object_fakes.container_name_3, + ) self.assertEqual(self.columns, columns) datalist = ( @@ -235,8 +207,8 @@ def test_object_list_containers_marker(self, c_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_containers_limit(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_limit(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_3), ] @@ -255,11 +227,9 @@ def test_object_list_containers_limit(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'limit': 2, - } - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with( + limit=2, + ) self.assertEqual(self.columns, columns) datalist = ( @@ -268,8 +238,8 @@ def test_object_list_containers_limit(self, c_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_containers_long(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_long(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_3), ] @@ -287,9 +257,7 @@ def test_object_list_containers_long(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = {} - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with() collist = ('Name', 'Bytes', 'Count') self.assertEqual(collist, columns) @@ -307,8 +275,8 @@ def test_object_list_containers_long(self, c_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_containers_all(self, c_mock): - c_mock.return_value = [ + def test_object_list_containers_all(self): + self.object_store_client.container_list.return_value = [ copy.deepcopy(object_fakes.CONTAINER), copy.deepcopy(object_fakes.CONTAINER_2), copy.deepcopy(object_fakes.CONTAINER_3), @@ -327,11 +295,9 @@ def test_object_list_containers_all(self, c_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'full_listing': True, - } - c_mock.assert_called_with(**kwargs) + self.object_store_client.container_list.assert_called_with( + full_listing=True, + ) self.assertEqual(self.columns, columns) datalist = ( @@ -342,16 +308,17 @@ def test_object_list_containers_all(self, c_mock): self.assertEqual(datalist, tuple(data)) -@mock.patch('openstackclient.api.object_store_v1.APIv1.container_show') -class TestContainerShow(TestContainer): +class TestContainerShow(object_fakes.TestObjectV1): def setUp(self): super().setUp() # Get the command object to test self.cmd = container.ShowContainer(self.app, None) - def test_container_show(self, c_mock): - c_mock.return_value = copy.deepcopy(object_fakes.CONTAINER) + def test_container_show(self): + self.object_store_client.container_show.return_value = copy.deepcopy( + object_fakes.CONTAINER + ) arglist = [ object_fakes.container_name, @@ -366,11 +333,8 @@ def test_container_show(self, c_mock): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = {} - # lib.container.show_container(api, url, container) - c_mock.assert_called_with( - container=object_fakes.container_name, **kwargs + self.object_store_client.container_show.assert_called_with( + container=object_fakes.container_name, ) collist = ('bytes', 'count', 'name') diff --git a/openstackclient/tests/unit/object/v1/test_container_all.py b/openstackclient/tests/unit/object/v1/test_container_all.py index 0a795dd860..7de0137884 100644 --- a/openstackclient/tests/unit/object/v1/test_container_all.py +++ b/openstackclient/tests/unit/object/v1/test_container_all.py @@ -13,17 +13,24 @@ import copy +from keystoneauth1 import session from requests_mock.contrib import fixture +from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import container as container_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes -class TestContainerAll(object_fakes.TestObjectv1): +class TestContainerAll(object_fakes.TestObjectV1): def setUp(self): super().setUp() + # these tests require a "real" client since we mock requests self.requests_mock = self.useFixture(fixture.Fixture()) + self.app.client_manager.object_store = object_store.APIv1( + session=session.Session(), + endpoint=object_fakes.ENDPOINT, + ) class TestContainerCreate(TestContainerAll): diff --git a/openstackclient/tests/unit/object/v1/test_object.py b/openstackclient/tests/unit/object/v1/test_object.py index f1777f963c..d544cd00ef 100644 --- a/openstackclient/tests/unit/object/v1/test_object.py +++ b/openstackclient/tests/unit/object/v1/test_object.py @@ -14,29 +14,12 @@ # import copy -from unittest import mock -from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import object as obj from openstackclient.tests.unit.object.v1 import fakes as object_fakes -AUTH_TOKEN = "foobar" -AUTH_URL = "http://0.0.0.0" - - -class TestObject(object_fakes.TestObjectv1): - def setUp(self): - super().setUp() - self.app.client_manager.object_store = object_store.APIv1( - session=mock.Mock(), - service_type="object-store", - ) - self.api = self.app.client_manager.object_store - - -@mock.patch('openstackclient.api.object_store_v1.APIv1.object_list') -class TestObjectList(TestObject): +class TestObjectList(object_fakes.TestObjectV1): columns = ('Name',) datalist = ((object_fakes.object_name_2,),) @@ -46,8 +29,8 @@ def setUp(self): # Get the command object to test self.cmd = obj.ListObject(self.app, None) - def test_object_list_objects_no_options(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_no_options(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT), copy.deepcopy(object_fakes.OBJECT_2), ] @@ -65,7 +48,7 @@ def test_object_list_objects_no_options(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - o_mock.assert_called_with( + self.object_store_client.object_list.assert_called_with( container=object_fakes.container_name, ) @@ -76,8 +59,8 @@ def test_object_list_objects_no_options(self, o_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_objects_prefix(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_prefix(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT_2), ] @@ -97,19 +80,16 @@ def test_object_list_objects_prefix(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'prefix': 'floppy', - } - o_mock.assert_called_with( - container=object_fakes.container_name_2, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name_2, + prefix='floppy', ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_object_list_objects_delimiter(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_delimiter(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT_2), ] @@ -129,19 +109,16 @@ def test_object_list_objects_delimiter(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'delimiter': '=', - } - o_mock.assert_called_with( - container=object_fakes.container_name_2, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name_2, + delimiter='=', ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_object_list_objects_marker(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_marker(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT_2), ] @@ -161,19 +138,16 @@ def test_object_list_objects_marker(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'marker': object_fakes.object_name_2, - } - o_mock.assert_called_with( - container=object_fakes.container_name_2, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name_2, + marker=object_fakes.object_name_2, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_object_list_objects_end_marker(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_end_marker(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT_2), ] @@ -193,19 +167,16 @@ def test_object_list_objects_end_marker(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'end_marker': object_fakes.object_name_2, - } - o_mock.assert_called_with( - container=object_fakes.container_name_2, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name_2, + end_marker=object_fakes.object_name_2, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_object_list_objects_limit(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_limit(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT_2), ] @@ -225,19 +196,16 @@ def test_object_list_objects_limit(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'limit': 2, - } - o_mock.assert_called_with( - container=object_fakes.container_name_2, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name_2, + limit=2, ) self.assertEqual(self.columns, columns) self.assertEqual(self.datalist, tuple(data)) - def test_object_list_objects_long(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_long(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT), copy.deepcopy(object_fakes.OBJECT_2), ] @@ -257,10 +225,8 @@ def test_object_list_objects_long(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = {} - o_mock.assert_called_with( - container=object_fakes.container_name, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name, ) collist = ('Name', 'Bytes', 'Hash', 'Content Type', 'Last Modified') @@ -283,8 +249,8 @@ def test_object_list_objects_long(self, o_mock): ) self.assertEqual(datalist, tuple(data)) - def test_object_list_objects_all(self, o_mock): - o_mock.return_value = [ + def test_object_list_objects_all(self): + self.object_store_client.object_list.return_value = [ copy.deepcopy(object_fakes.OBJECT), copy.deepcopy(object_fakes.OBJECT_2), ] @@ -304,12 +270,9 @@ def test_object_list_objects_all(self, o_mock): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = { - 'full_listing': True, - } - o_mock.assert_called_with( - container=object_fakes.container_name, **kwargs + self.object_store_client.object_list.assert_called_with( + container=object_fakes.container_name, + full_listing=True, ) self.assertEqual(self.columns, columns) @@ -320,16 +283,17 @@ def test_object_list_objects_all(self, o_mock): self.assertEqual(datalist, tuple(data)) -@mock.patch('openstackclient.api.object_store_v1.APIv1.object_show') -class TestObjectShow(TestObject): +class TestObjectShow(object_fakes.TestObjectV1): def setUp(self): super().setUp() # Get the command object to test self.cmd = obj.ShowObject(self.app, None) - def test_object_show(self, c_mock): - c_mock.return_value = copy.deepcopy(object_fakes.OBJECT) + def test_object_show(self): + self.object_store_client.object_show.return_value = copy.deepcopy( + object_fakes.OBJECT + ) arglist = [ object_fakes.container_name, @@ -346,13 +310,9 @@ def test_object_show(self, c_mock): # data to be shown. columns, data = self.cmd.take_action(parsed_args) - # Set expected values - kwargs = {} - # lib.container.show_container(api, url, container) - c_mock.assert_called_with( + self.object_store_client.object_show.assert_called_with( container=object_fakes.container_name, object=object_fakes.object_name_1, - **kwargs, ) collist = ('bytes', 'content_type', 'hash', 'last_modified', 'name') diff --git a/openstackclient/tests/unit/object/v1/test_object_all.py b/openstackclient/tests/unit/object/v1/test_object_all.py index 968667b68e..66dfabf48b 100644 --- a/openstackclient/tests/unit/object/v1/test_object_all.py +++ b/openstackclient/tests/unit/object/v1/test_object_all.py @@ -15,18 +15,25 @@ import io from unittest import mock +from keystoneauth1 import session from osc_lib import exceptions from requests_mock.contrib import fixture +from openstackclient.api import object_store_v1 as object_store from openstackclient.object.v1 import object as object_cmds from openstackclient.tests.unit.object.v1 import fakes as object_fakes -class TestObjectAll(object_fakes.TestObjectv1): +class TestObjectAll(object_fakes.TestObjectV1): def setUp(self): super().setUp() + # these tests require a "real" client since we mock requests self.requests_mock = self.useFixture(fixture.Fixture()) + self.app.client_manager.object_store = object_store.APIv1( + session=session.Session(), + endpoint=object_fakes.ENDPOINT, + ) class TestObjectCreate(TestObjectAll): diff --git a/openstackclient/tests/unit/test_hacking.py b/openstackclient/tests/unit/test_hacking.py new file mode 100644 index 0000000000..ff22ffb2c9 --- /dev/null +++ b/openstackclient/tests/unit/test_hacking.py @@ -0,0 +1,108 @@ +# 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 importlib.util +import os +import re +import subprocess +import sys +import unittest + +import fixtures + +ROOT_DIR = os.path.normpath( + os.path.join(os.path.dirname(__file__), '..', '..', '..') +) +SELFTEST_REGEX = re.compile(r'\b(Okay|[HEW]\d{3}|O\d{3}):\s(.*)') + +# Checks that filter on 'openstackclient/tests/unit' in the filename need the +# temp file written into that path structure so the check is not skipped. +_UNIT_TEST_SUBDIRS = { + 'O401': os.path.join('openstackclient', 'tests', 'unit'), + 'O402': os.path.join('openstackclient', 'tests', 'unit'), +} + + +def _load_checks(): + spec = importlib.util.spec_from_file_location( + '_osc_hacking_checks', + os.path.join(ROOT_DIR, 'hacking', 'checks.py'), + ) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + return module + + +def _get_examples(check): + for line in check.__doc__.splitlines(): + line = line.lstrip() + match = SELFTEST_REGEX.match(line) + if match: + yield match.group(1), match.group(2) + + +class HackingTestCase(unittest.TestCase): + def _test_check(self, code, source): + lines = [ + part.replace(r'\t', '\t') + '\n' for part in source.split(r'\n') + ] + subdir = { + 'O401': os.path.join('openstackclient', 'tests', 'unit'), + 'O402': os.path.join('openstackclient', 'tests', 'unit'), + }.get(code, '') + + with fixtures.TempDir() as tmp: + dirpath = os.path.join(tmp.path, subdir) if subdir else tmp.path + if subdir: + os.makedirs(dirpath) + + fpath = os.path.join(dirpath, 'test_tmp.py') + with open(fpath, 'w') as f: + f.write(''.join(lines)) + + cmd = [ + sys.executable, + '-mflake8', + '--config', + os.path.join(ROOT_DIR, 'tox.ini'), + f'--select={code}', + '--format=%(code)s\t%(path)s\t%(row)d', + fpath, + ] + out, _ = subprocess.Popen( + cmd, stdout=subprocess.PIPE, cwd=ROOT_DIR + ).communicate() + out = out.decode('utf-8') + + if code == 'Okay': + self.assertEqual('', out) + else: + self.assertNotEqual('', out, f"Failed to trigger rule {code}") + self.assertEqual(code, out.split('\t')[0].rstrip(':'), out) + + def test_checks(self): + checks_module = _load_checks() + + for name in sorted(dir(checks_module)): + check = getattr(checks_module, name) + if not callable(check): + continue + + if getattr(check, 'skip_on_py3', None) is not False: + continue + + if not check.__doc__: + continue + + for code, source in _get_examples(check): + with self.subTest(check=name, example=source): + self._test_check(code, source) diff --git a/openstackclient/tests/unit/volume/v2/test_consistency_group.py b/openstackclient/tests/unit/volume/v2/test_consistency_group.py index dc62e5e42f..68021c889a 100644 --- a/openstackclient/tests/unit/volume/v2/test_consistency_group.py +++ b/openstackclient/tests/unit/volume/v2/test_consistency_group.py @@ -129,7 +129,10 @@ def test_add_multiple_volumes_to_consistency_group_with_exception( utils, 'find_resource', side_effect=find_mock_result ) as find_mock: result = self.cmd.take_action(parsed_args) - mock_error.assert_called_with("1 of 2 volumes failed to add.") + mock_error.assert_called_with( + '%(result)s of %(total)s volumes failed to add.', + {'result': 1, 'total': 2}, + ) self.assertIsNone(result) find_mock.assert_any_call( self.consistencygroups_mock, self._consistency_group.id @@ -602,7 +605,10 @@ def test_remove_multiple_volumes_from_consistency_group_with_exception( utils, 'find_resource', side_effect=find_mock_result ) as find_mock: result = self.cmd.take_action(parsed_args) - mock_error.assert_called_with("1 of 2 volumes failed to remove.") + mock_error.assert_called_with( + '%(result)s of %(total)s volumes failed to remove.', + {'result': 1, 'total': 2}, + ) self.assertIsNone(result) find_mock.assert_any_call( self.consistencygroups_mock, self._consistency_group.id diff --git a/openstackclient/tests/unit/volume/v2/test_volume.py b/openstackclient/tests/unit/volume/v2/test_volume.py index b68020fa95..8a9f7777e5 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume.py +++ b/openstackclient/tests/unit/volume/v2/test_volume.py @@ -655,7 +655,7 @@ def test_volume_delete_one_volume(self): arglist = [self.volumes[0].id] verifylist = [ ("force", False), - ("purge", False), + ("cascade", False), ("volumes", [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -674,7 +674,7 @@ def test_volume_delete_multi_volumes(self): arglist = [v.id for v in self.volumes] verifylist = [ ('force', False), - ('purge', False), + ('cascade', False), ('volumes', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -701,7 +701,7 @@ def test_volume_delete_multi_volumes_with_exception(self): ] verifylist = [ ('force', False), - ('purge', False), + ('cascade', False), ('volumes', [self.volumes[0].id, 'unexist_volume']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -732,7 +732,7 @@ def test_volume_delete_with_purge(self): ] verifylist = [ ('force', False), - ('purge', True), + ('cascade', True), ('volumes', [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -754,7 +754,7 @@ def test_volume_delete_with_force(self): ] verifylist = [ ('force', True), - ('purge', False), + ('cascade', False), ('volumes', [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1618,7 +1618,8 @@ def test_volume_set_with_only_retype_policy(self, mock_warning): result = self.cmd.take_action(parsed_args) self.volumes_mock.retype.assert_not_called() mock_warning.assert_called_with( - "'--retype-policy' option will not work without '--type' option" + "'%s' option will not work without '--type' option", + '--retype-policy', ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v2/test_volume_backup.py b/openstackclient/tests/unit/volume/v2/test_volume_backup.py index e7bbb69999..8d7984e804 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_backup.py @@ -220,11 +220,7 @@ class TestBackupList(volume_fakes.TestVolume): 'Incremental', 'Created At', ) - columns_long = columns + ( - 'Availability Zone', - 'Volume', - 'Container', - ) + columns_long = (*columns, 'Availability Zone', 'Volume', 'Container') def setUp(self): super().setUp() diff --git a/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py index 0df379bb24..c1d88c4530 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_snapshot.py @@ -297,7 +297,8 @@ def setUp(self): self.project_mock.get.return_value = self.project self.columns = ("ID", "Name", "Description", "Status", "Size") - self.columns_long = self.columns + ( + self.columns_long = ( + *self.columns, "Created At", "Volume", "Properties", diff --git a/openstackclient/tests/unit/volume/v2/test_volume_type.py b/openstackclient/tests/unit/volume/v2/test_volume_type.py index 6f50ff2ef6..b4df3e63cc 100644 --- a/openstackclient/tests/unit/volume/v2/test_volume_type.py +++ b/openstackclient/tests/unit/volume/v2/test_volume_type.py @@ -332,7 +332,7 @@ class TestTypeList(TestType): "Name", "Is Public", ] - columns_long = columns + ["Description"] + columns_long = [*columns, "Description"] data_with_default_type = [(volume_types[0].id, volume_types[0].name, True)] data = [] for t in volume_types: @@ -436,9 +436,7 @@ def test_type_list_with_encryption(self): 'key_size': None, 'control_location': 'front-end', } - encryption_columns = self.columns + [ - "Encryption", - ] + encryption_columns = [*self.columns, "Encryption"] encryption_data = [] encryption_data.append( ( diff --git a/openstackclient/tests/unit/volume/v3/test_volume.py b/openstackclient/tests/unit/volume/v3/test_volume.py index 33dcfe5a47..703aa04fd3 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume.py +++ b/openstackclient/tests/unit/volume/v3/test_volume.py @@ -912,7 +912,7 @@ def test_volume_delete_one_volume(self): arglist = [self.volumes[0].id] verifylist = [ ("force", False), - ("purge", False), + ("cascade", False), ("volumes", [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -931,7 +931,7 @@ def test_volume_delete_multi_volumes(self): arglist = [v.id for v in self.volumes] verifylist = [ ('force', False), - ('purge', False), + ('cascade', False), ('volumes', arglist), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -958,7 +958,7 @@ def test_volume_delete_multi_volumes_with_exception(self): ] verifylist = [ ('force', False), - ('purge', False), + ('cascade', False), ('volumes', [self.volumes[0].id, 'unexist_volume']), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -989,7 +989,29 @@ def test_volume_delete_with_purge(self): ] verifylist = [ ('force', False), - ('purge', True), + ('cascade', True), + ('volumes', [self.volumes[0].id]), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.volume_sdk_client.find_volume.assert_called_once_with( + self.volumes[0].id, ignore_missing=False + ) + self.volume_sdk_client.delete_volume.assert_called_once_with( + self.volumes[0].id, cascade=True, force=False + ) + + def test_volume_delete_with_cascade(self): + arglist = [ + '--cascade', + self.volumes[0].id, + ] + verifylist = [ + ('force', False), + ('cascade', True), ('volumes', [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1011,7 +1033,7 @@ def test_volume_delete_with_force(self): ] verifylist = [ ('force', True), - ('purge', False), + ('cascade', False), ('volumes', [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1031,7 +1053,7 @@ def test_volume_delete_remote(self): verifylist = [ ("remote", True), ("force", False), - ("purge", False), + ("cascade", False), ("volumes", [self.volumes[0].id]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1052,7 +1074,7 @@ def test_volume_delete_multi_volumes_remote(self): verifylist = [ ('remote', True), ('force', False), - ('purge', False), + ('cascade', False), ('volumes', arglist[1:]), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -1077,7 +1099,6 @@ def test_volume_delete_remote_with_purge(self): verifylist = [ ('remote', True), ('force', False), - ('purge', True), ('volumes', [self.volumes[0].id]), ] @@ -1086,7 +1107,7 @@ def test_volume_delete_remote_with_purge(self): exceptions.CommandError, self.cmd.take_action, parsed_args ) self.assertIn( - "The --force and --purge options are not supported with the " + "The --force and --cascade options are not supported with the " "--remote parameter.", str(exc), ) @@ -1104,7 +1125,7 @@ def test_volume_delete_remote_with_force(self): verifylist = [ ('remote', True), ('force', True), - ('purge', False), + ('cascade', False), ('volumes', [self.volumes[0].id]), ] @@ -1113,7 +1134,7 @@ def test_volume_delete_remote_with_force(self): exceptions.CommandError, self.cmd.take_action, parsed_args ) self.assertIn( - "The --force and --purge options are not supported with the " + "The --force and --cascade options are not supported with the " "--remote parameter.", str(exc), ) @@ -1702,9 +1723,10 @@ def test_volume_migrate(self): host="host@backend-name#pool", force_host_copy=False, lock_volume=False, + cluster=None, ) - def test_volume_migrate_with_option(self): + def test_volume_migrate_with_host(self): arglist = [ "--force-host-copy", "--lock-volume", @@ -1731,9 +1753,66 @@ def test_volume_migrate_with_option(self): host="host@backend-name#pool", force_host_copy=True, lock_volume=True, + cluster=None, ) - def test_volume_migrate_without_host(self): + def test_volume_migrate_with_cluster(self): + self.set_volume_api_version('3.16') + arglist = [ + "--cluster", + "cluster@backend-name#pool", + self.volume.id, + ] + verifylist = [ + ( + "cluster", + "cluster@backend-name#pool", + ), + ("volume", self.volume.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + self.assertIsNone(result) + + self.volume_sdk_client.find_volume.assert_called_with( + self.volume.id, ignore_missing=False + ) + self.volume_sdk_client.migrate_volume.assert_called_once_with( + self.volume.id, + host=None, + force_host_copy=False, + lock_volume=False, + cluster="cluster@backend-name#pool", + ) + + def test_volume_migrate_with_cluster_pre_v316(self): + self.set_volume_api_version('3.15') + arglist = [ + "--cluster", + "cluster@backend-name#pool", + self.volume.id, + ] + verifylist = [ + ( + "cluster", + "cluster@backend-name#pool", + ), + ("volume", self.volume.id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.assertRaises( + exceptions.CommandError, + self.cmd.take_action, + parsed_args, + ) + + self.volume_sdk_client.migrate_volume.assert_not_called() + + def test_volume_migrate_without_host_and_cluster(self): arglist = [ self.volume.id, ] @@ -1750,7 +1829,6 @@ def test_volume_migrate_without_host(self): arglist, verifylist, ) - self.volume_sdk_client.find_volume.assert_not_called() self.volume_sdk_client.migrate_volume.assert_not_called() @@ -1998,7 +2076,8 @@ def test_volume_set_with_only_retype_policy(self, mock_warning): result = self.cmd.take_action(parsed_args) self.volumes_mock.retype.assert_not_called() mock_warning.assert_called_with( - "'--retype-policy' option will not work without '--type' option" + "'%s' option will not work without '--type' option", + '--retype-policy', ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/volume/v3/test_volume_attachment.py b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py index b7838e034a..d8e6dcacb5 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_attachment.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_attachment.py @@ -23,7 +23,7 @@ class TestVolumeAttachment(volume_fakes.TestVolume): def setUp(self): super().setUp() - self.projects_mock = self.app.client_manager.identity.projects + self.projects_mock = self.identity_client.projects class TestVolumeAttachmentCreate(TestVolumeAttachment): diff --git a/openstackclient/tests/unit/volume/v3/test_volume_backup.py b/openstackclient/tests/unit/volume/v3/test_volume_backup.py index 86bde785f6..480912796b 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_backup.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_backup.py @@ -319,11 +319,7 @@ class TestBackupList(volume_fakes.TestVolume): 'Incremental', 'Created At', ) - columns_long = columns + ( - 'Availability Zone', - 'Volume', - 'Container', - ) + columns_long = (*columns, 'Availability Zone', 'Volume', 'Container') def setUp(self): super().setUp() diff --git a/openstackclient/tests/unit/volume/v3/test_volume_snapshot.py b/openstackclient/tests/unit/volume/v3/test_volume_snapshot.py index 85613603de..59fc42baae 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_snapshot.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_snapshot.py @@ -340,7 +340,8 @@ def setUp(self): self.project_mock.get.return_value = self.project self.columns = ("ID", "Name", "Description", "Status", "Size") - self.columns_long = self.columns + ( + self.columns_long = ( + *self.columns, "Created At", "Volume", "Properties", diff --git a/openstackclient/tests/unit/volume/v3/test_volume_type.py b/openstackclient/tests/unit/volume/v3/test_volume_type.py index 828f8b0902..eedd07e5b2 100644 --- a/openstackclient/tests/unit/volume/v3/test_volume_type.py +++ b/openstackclient/tests/unit/volume/v3/test_volume_type.py @@ -331,7 +331,7 @@ class TestTypeList(TestType): "Name", "Is Public", ] - columns_long = columns + ["Description", "Properties"] + columns_long = [*columns, "Description", "Properties"] data_with_default_type = [(volume_types[0].id, volume_types[0].name, True)] data = [] for t in volume_types: @@ -509,9 +509,7 @@ def test_type_list_with_encryption(self): 'key_size': None, 'control_location': 'front-end', } - encryption_columns = self.columns + [ - "Encryption", - ] + encryption_columns = [*self.columns, "Encryption"] encryption_data = [] encryption_data.append( ( diff --git a/openstackclient/volume/v2/consistency_group.py b/openstackclient/volume/v2/consistency_group.py index 4910bb129e..31959c70b0 100644 --- a/openstackclient/volume/v2/consistency_group.py +++ b/openstackclient/volume/v2/consistency_group.py @@ -38,8 +38,8 @@ def _find_volumes(parsed_args_volumes, volume_client): except Exception as e: result += 1 LOG.error( - _("Failed to find volume with name or ID '%(volume)s':%(e)s") - % {'volume': volume, 'e': e} + _("Failed to find volume with name or ID '%(volume)s':%(e)s"), + {'volume': volume, 'e': e}, ) return result, uuid @@ -73,8 +73,8 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.volumes) LOG.error( - _("%(result)s of %(total)s volumes failed to add.") - % {'result': result, 'total': total} + _("%(result)s of %(total)s volumes failed to add."), + {'result': result, 'total': total}, ) if add_uuid: @@ -226,8 +226,8 @@ def take_action(self, parsed_args): _( "Failed to delete consistency group with " "name or ID '%(consistency_group)s':%(e)s" - ) - % {'consistency_group': i, 'e': e} + ), + {'consistency_group': i, 'e': e}, ) if result > 0: @@ -317,8 +317,8 @@ def take_action(self, parsed_args): if result > 0: total = len(parsed_args.volumes) LOG.error( - _("%(result)s of %(total)s volumes failed to remove.") - % {'result': result, 'total': total} + _("%(result)s of %(total)s volumes failed to remove."), + {'result': result, 'total': total}, ) if remove_uuid: diff --git a/openstackclient/volume/v2/consistency_group_snapshot.py b/openstackclient/volume/v2/consistency_group_snapshot.py index 23c3f1034d..4adf4a25c9 100644 --- a/openstackclient/volume/v2/consistency_group_snapshot.py +++ b/openstackclient/volume/v2/consistency_group_snapshot.py @@ -101,8 +101,8 @@ def take_action(self, parsed_args): _( "Failed to delete consistency group snapshot " "with name or ID '%(snapshot)s': %(e)s" - ) - % {'snapshot': snapshot, 'e': e} + ), + {'snapshot': snapshot, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v2/qos_specs.py b/openstackclient/volume/v2/qos_specs.py index 39aa99eb42..6dcc23f623 100644 --- a/openstackclient/volume/v2/qos_specs.py +++ b/openstackclient/volume/v2/qos_specs.py @@ -146,8 +146,8 @@ def take_action(self, parsed_args): _( "Failed to delete QoS specification with " "name or ID '%(qos)s': %(e)s" - ) - % {'qos': i, 'e': e} + ), + {'qos': i, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index 61cce04f7b..095a90223f 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -390,12 +390,19 @@ def get_parser(self, prog_name): ), ) group.add_argument( - "--purge", + "--cascade", action="store_true", help=_( "Remove any snapshots along with volume(s) (defaults to False)" ), ) + group.add_argument( + # now called "cascade", accept old arg for compatibility + "--purge", + action="store_true", + help=argparse.SUPPRESS, + dest='cascade', + ) return parser def take_action(self, parsed_args): @@ -410,7 +417,7 @@ def take_action(self, parsed_args): volume_client.delete_volume( volume_obj.id, force=parsed_args.force, - cascade=parsed_args.purge, + cascade=parsed_args.cascade, ) except Exception as e: result += 1 @@ -548,7 +555,7 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for s in compute_client.servers(): server_cache[s.id] = s - except sdk_exceptions.SDKException: # noqa: S110 + except sdk_exceptions.SDKException: # Just forget it if there's any trouble pass AttachmentsColumnWithCache = functools.partial( @@ -910,12 +917,12 @@ def take_action(self, parsed_args): elif policy: # If the "--migration-policy" is specified without "--type" LOG.warning( - _("'%s' option will not work without '--type' option") - % ( + _("'%s' option will not work without '--type' option"), + ( '--migration-policy' if parsed_args.migration_policy else '--retype-policy' - ) + ), ) kwargs = {} diff --git a/openstackclient/volume/v2/volume_backup.py b/openstackclient/volume/v2/volume_backup.py index 7dbe92c962..7f36652768 100644 --- a/openstackclient/volume/v2/volume_backup.py +++ b/openstackclient/volume/v2/volume_backup.py @@ -175,8 +175,8 @@ def take_action(self, parsed_args): _( "Failed to delete backup with " "name or ID '%(backup)s': %(e)s" - ) - % {'backup': backup, 'e': e} + ), + {'backup': backup, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v2/volume_snapshot.py b/openstackclient/volume/v2/volume_snapshot.py index 3b1dbbabf2..9533a1a69c 100644 --- a/openstackclient/volume/v2/volume_snapshot.py +++ b/openstackclient/volume/v2/volume_snapshot.py @@ -228,8 +228,8 @@ def take_action(self, parsed_args): _( "Failed to delete snapshot with " "name or ID '%(snapshot)s': %(e)s" - ) - % {'snapshot': snapshot, 'e': e} + ), + {'snapshot': snapshot, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v2/volume_transfer_request.py b/openstackclient/volume/v2/volume_transfer_request.py index dcdc527625..2f1bee8592 100644 --- a/openstackclient/volume/v2/volume_transfer_request.py +++ b/openstackclient/volume/v2/volume_transfer_request.py @@ -128,8 +128,8 @@ def take_action(self, parsed_args): _( "Failed to delete volume transfer request " "with name or ID '%(transfer)s': %(e)s" - ) - % {'transfer': t, 'e': e} + ), + {'transfer': t, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index e7b90af95a..9c804615af 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -277,7 +277,7 @@ def take_action(self, parsed_args): msg = _( "Failed to add project %(project)s access to type: %(e)s" ) - LOG.error(msg % {'project': parsed_args.project, 'e': e}) + LOG.error(msg, {'project': parsed_args.project, 'e': e}) properties = {} if parsed_args.properties: @@ -358,8 +358,8 @@ def take_action(self, parsed_args): _( "Failed to delete volume type with " "name or ID '%(volume_type)s': %(e)s" - ) - % {'volume_type': volume_type, 'e': e} + ), + {'volume_type': volume_type, 'e': e}, ) if result > 0: @@ -466,7 +466,7 @@ def take_action(self, parsed_args): _EncryptionInfoColumn = functools.partial( EncryptionInfoColumn, encryption_data=encryption ) - formatters['id'] = _EncryptionInfoColumn + formatters['id'] = _EncryptionInfoColumn # type: ignore return ( column_headers, @@ -763,7 +763,7 @@ def take_action(self, parsed_args): 'Failed to get access project list for volume type ' '%(type)s: %(e)s' ) - LOG.error(msg % {'type': volume_type.id, 'e': e}) + LOG.error(msg, {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) if parsed_args.encryption_type: # show encryption type information for this volume type diff --git a/openstackclient/volume/v3/volume.py b/openstackclient/volume/v3/volume.py index 50ea77fb5a..a29b77b564 100644 --- a/openstackclient/volume/v3/volume.py +++ b/openstackclient/volume/v3/volume.py @@ -515,12 +515,19 @@ def get_parser(self, prog_name): ), ) group.add_argument( - "--purge", + "--cascade", action="store_true", help=_( "Remove any snapshots along with volume(s) (defaults to False)" ), ) + group.add_argument( + # now called "cascade", accept old arg for compatibility + "--purge", + action="store_true", + help=argparse.SUPPRESS, + dest='cascade', + ) parser.add_argument( '--remote', action='store_true', @@ -532,9 +539,9 @@ def take_action(self, parsed_args): volume_client = self.app.client_manager.sdk_connection.volume result = 0 - if parsed_args.remote and (parsed_args.force or parsed_args.purge): + if parsed_args.remote and (parsed_args.force or parsed_args.cascade): msg = _( - "The --force and --purge options are not " + "The --force and --cascade options are not " "supported with the --remote parameter." ) raise exceptions.CommandError(msg) @@ -550,7 +557,7 @@ def take_action(self, parsed_args): volume_client.delete_volume( volume_obj.id, force=parsed_args.force, - cascade=parsed_args.purge, + cascade=parsed_args.cascade, ) except Exception as e: result += 1 @@ -699,7 +706,7 @@ def take_action(self, parsed_args): compute_client = self.app.client_manager.compute for s in compute_client.servers(): server_cache[s.id] = s - except sdk_exceptions.SDKException: # noqa: S110 + except sdk_exceptions.SDKException: # Just forget it if there's any trouble pass AttachmentsColumnWithCache = functools.partial( @@ -736,14 +743,22 @@ def get_parser(self, prog_name): metavar="", help=_("Volume to migrate (name or ID)"), ) - parser.add_argument( + destination_group = parser.add_mutually_exclusive_group(required=True) + destination_group.add_argument( '--host', metavar="", - required=True, help=_( "Destination host (takes the form: host@backend-name#pool)" ), ) + destination_group.add_argument( + '--cluster', + metavar="", + help=_( + "Destination cluster to migrate the volume to " + "(requires --os-volume-api-version 3.16 or higher)" + ), + ) parser.add_argument( '--force-host-copy', action="store_true", @@ -761,7 +776,6 @@ def get_parser(self, prog_name): "(possibly by another operation)" ), ) - # TODO(stephenfin): Add --cluster argument return parser def take_action(self, parsed_args): @@ -769,11 +783,22 @@ def take_action(self, parsed_args): volume = volume_client.find_volume( parsed_args.volume, ignore_missing=False ) + + if parsed_args.cluster and not sdk_utils.supports_microversion( + volume_client, '3.16' + ): + msg = _( + "--os-volume-api-version 3.16 or greater is required to " + "support the volume migration with cluster" + ) + raise exceptions.CommandError(msg) + volume_client.migrate_volume( volume.id, host=parsed_args.host, force_host_copy=parsed_args.force_host_copy, lock_volume=parsed_args.lock_volume, + cluster=parsed_args.cluster, ) @@ -1071,12 +1096,12 @@ def take_action(self, parsed_args): elif policy: # If the "--migration-policy" is specified without "--type" LOG.warning( - _("'%s' option will not work without '--type' option") - % ( + _("'%s' option will not work without '--type' option"), + ( '--migration-policy' if parsed_args.migration_policy else '--retype-policy' - ) + ), ) kwargs = {} diff --git a/openstackclient/volume/v3/volume_backup.py b/openstackclient/volume/v3/volume_backup.py index df9a17eb03..2ab24f3e1f 100644 --- a/openstackclient/volume/v3/volume_backup.py +++ b/openstackclient/volume/v3/volume_backup.py @@ -218,8 +218,8 @@ def take_action(self, parsed_args): _( "Failed to delete backup with " "name or ID '%(backup)s': %(e)s" - ) - % {'backup': backup, 'e': e} + ), + {'backup': backup, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v3/volume_snapshot.py b/openstackclient/volume/v3/volume_snapshot.py index f89174c3da..6eddb5a4db 100644 --- a/openstackclient/volume/v3/volume_snapshot.py +++ b/openstackclient/volume/v3/volume_snapshot.py @@ -246,8 +246,8 @@ def take_action(self, parsed_args): _( "Failed to delete snapshot with " "name or ID '%(snapshot)s': %(e)s" - ) - % {'snapshot': snapshot, 'e': e} + ), + {'snapshot': snapshot, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v3/volume_transfer_request.py b/openstackclient/volume/v3/volume_transfer_request.py index afd4626038..42fd37c1b5 100644 --- a/openstackclient/volume/v3/volume_transfer_request.py +++ b/openstackclient/volume/v3/volume_transfer_request.py @@ -163,8 +163,8 @@ def take_action(self, parsed_args): _( "Failed to delete volume transfer request " "with name or ID '%(transfer)s': %(e)s" - ) - % {'transfer': t, 'e': e} + ), + {'transfer': t, 'e': e}, ) if result > 0: diff --git a/openstackclient/volume/v3/volume_type.py b/openstackclient/volume/v3/volume_type.py index fbce2f2c9a..ba692525b7 100644 --- a/openstackclient/volume/v3/volume_type.py +++ b/openstackclient/volume/v3/volume_type.py @@ -277,7 +277,7 @@ def take_action(self, parsed_args): msg = _( "Failed to add project %(project)s access to type: %(e)s" ) - LOG.error(msg % {'project': parsed_args.project, 'e': e}) + LOG.error(msg, {'project': parsed_args.project, 'e': e}) properties = {} if parsed_args.properties: @@ -358,8 +358,8 @@ def take_action(self, parsed_args): _( "Failed to delete volume type with " "name or ID '%(volume_type)s': %(e)s" - ) - % {'volume_type': volume_type, 'e': e} + ), + {'volume_type': volume_type, 'e': e}, ) if result > 0: @@ -549,7 +549,7 @@ def take_action(self, parsed_args): _EncryptionInfoColumn = functools.partial( EncryptionInfoColumn, encryption_data=encryption ) - formatters['id'] = _EncryptionInfoColumn + formatters['id'] = _EncryptionInfoColumn # type: ignore return ( column_headers, @@ -846,7 +846,7 @@ def take_action(self, parsed_args): 'Failed to get access project list for volume type ' '%(type)s: %(e)s' ) - LOG.error(msg % {'type': volume_type.id, 'e': e}) + LOG.error(msg, {'type': volume_type.id, 'e': e}) volume_type._info.update({'access_project_ids': access_project_ids}) if parsed_args.encryption_type: # show encryption type information for this volume type diff --git a/pyproject.toml b/pyproject.toml index 37fb8d0c26..387d5eebef 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -11,7 +11,6 @@ authors = [ readme = {file = "README.rst", content-type = "text/x-rst"} license = {text = "Apache-2.0"} dynamic = ["version", "dependencies"] -# dependencies = [ ] requires-python = ">=3.10" classifiers = [ "Environment :: OpenStack", @@ -27,10 +26,6 @@ classifiers = [ "Programming Language :: Python :: 3.13", ] -# [project.optional-dependencies] -# test = [ -# ] - [project.urls] Homepage = "https://docs.openstack.org/python-openstackclient/" Repository = "https://opendev.org/openstack/python-openstackclient/" @@ -540,7 +535,7 @@ subnet_pool_set = "openstackclient.network.v2.subnet_pool:SetSubnetPool" subnet_pool_show = "openstackclient.network.v2.subnet_pool:ShowSubnetPool" subnet_pool_unset = "openstackclient.network.v2.subnet_pool:UnsetSubnetPool" -# Tap-as-a-Service +[project.entry-points."openstack.network.v2.taas"] tap_flow_create = "openstackclient.network.v2.taas.tap_flow:CreateTapFlow" tap_flow_delete = "openstackclient.network.v2.taas.tap_flow:DeleteTapFlow" tap_flow_list = "openstackclient.network.v2.taas.tap_flow:ListTapFlow" @@ -744,15 +739,7 @@ follow_imports = "normal" incremental = true check_untyped_defs = true warn_unused_ignores = true -# keep this in-sync with 'mypy.exclude' in '.pre-commit-config.yaml' -exclude = ''' -(?x)( - doc - | examples - | hacking - | releasenotes - ) -''' +exclude = '(?x)(doc | examples | hacking | releasenotes)' [[tool.mypy.overrides]] module = ["openstackclient.tests.unit.*"] @@ -766,7 +753,14 @@ quote-style = "preserve" docstring-code-format = true [tool.ruff.lint] -select = ["E4", "E5", "E7", "E9", "F", "S", "UP"] +select = ["E4", "E5", "E7", "E9", "F", "G", "RUF", "S", "UP"] +ignore = [ + # the following are ignored because they don't provide enough value for the + # changes required + "RUF012", # Mutable default value for class attribute +] +# don't remove hacking (H) or openstackclient (O) checks +external = ["H", "O"] [tool.ruff.lint.per-file-ignores] "openstackclient/tests/*" = ["E501", "S"] diff --git a/releasenotes/notes/add-cluster-option-to-volume-migration-9fe0cc84e9c80a4c.yaml b/releasenotes/notes/add-cluster-option-to-volume-migration-9fe0cc84e9c80a4c.yaml new file mode 100644 index 0000000000..5b9e878056 --- /dev/null +++ b/releasenotes/notes/add-cluster-option-to-volume-migration-9fe0cc84e9c80a4c.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + The ``volume migration`` command now supports the ``--cluster`` + optional argument, allowing volumes to be migrated to a destination + cluster. This feature requires Cinder API microversion 3.16 or + higher and is mutually exclusive with the ``--host`` option. \ No newline at end of file diff --git a/releasenotes/notes/bug-2138903-f75c7348f22db195.yaml b/releasenotes/notes/bug-2138903-f75c7348f22db195.yaml new file mode 100644 index 0000000000..249242146d --- /dev/null +++ b/releasenotes/notes/bug-2138903-f75c7348f22db195.yaml @@ -0,0 +1,5 @@ +--- +fixes: + - | + The ``--all-stores`` of the ``image import`` command is now correctly + treated as a boolean flag. diff --git a/releasenotes/notes/fix-resize-server-args-required-2e9013bcbf207f6a.yaml b/releasenotes/notes/fix-resize-server-args-required-2e9013bcbf207f6a.yaml new file mode 100644 index 0000000000..e1faf7e601 --- /dev/null +++ b/releasenotes/notes/fix-resize-server-args-required-2e9013bcbf207f6a.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + The ``openstack server resize`` command now requires the ``--flavor`` + option or one of the deprecated ``--confirm`` or ``--revert`` options + to be provided. + Previously, the command would silently exit successfully without + performing any action if no option was provided. diff --git a/releasenotes/notes/limits-project-domain-option-84bfbb0e30e21b73.yaml b/releasenotes/notes/limits-project-domain-option-84bfbb0e30e21b73.yaml new file mode 100644 index 0000000000..ab9468022c --- /dev/null +++ b/releasenotes/notes/limits-project-domain-option-84bfbb0e30e21b73.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``--project-domain`` option for the ``limit list`` and ``limit create`` + commands. diff --git a/releasenotes/notes/migrate-federation-protocol-to-sdk-43dc2b50fb277da6.yaml b/releasenotes/notes/migrate-federation-protocol-to-sdk-43dc2b50fb277da6.yaml new file mode 100644 index 0000000000..ea3808a7b0 --- /dev/null +++ b/releasenotes/notes/migrate-federation-protocol-to-sdk-43dc2b50fb277da6.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Migrate ``federation protocol`` commands from keystoneclient to SDK. +upgrade: + - | + Filtering in ``federation protocol`` commands is now case sensitive. diff --git a/releasenotes/notes/migrate-limit-to-sdk-378037ec2b79e302.yaml b/releasenotes/notes/migrate-limit-to-sdk-378037ec2b79e302.yaml new file mode 100644 index 0000000000..d4a57329ef --- /dev/null +++ b/releasenotes/notes/migrate-limit-to-sdk-378037ec2b79e302.yaml @@ -0,0 +1,9 @@ +--- +features: + - | + Migrate ``limit`` commands from keystoneclient to SDK. +upgrade: + - | + Filtering in ``limit`` commands is now case sensitive. + - | + Specifying ``--region None`` is no longer supported for ``limit`` commands. diff --git a/releasenotes/notes/migrate-project-to-sdk-9201efd2804371de.yaml b/releasenotes/notes/migrate-project-to-sdk-9201efd2804371de.yaml new file mode 100644 index 0000000000..90c6031742 --- /dev/null +++ b/releasenotes/notes/migrate-project-to-sdk-9201efd2804371de.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + Migrate ``project`` commands from keystoneclient to SDK. +upgrade: + - | + Filtering in ``project`` commands is now case sensitive. diff --git a/releasenotes/notes/migrate-registered-limit-to-sdk-36b6451e3a799a43.yaml b/releasenotes/notes/migrate-registered-limit-to-sdk-36b6451e3a799a43.yaml new file mode 100644 index 0000000000..77f404a9ff --- /dev/null +++ b/releasenotes/notes/migrate-registered-limit-to-sdk-36b6451e3a799a43.yaml @@ -0,0 +1,10 @@ +--- +features: + - | + Migrate ``registered limit`` commands from keystoneclient to SDK. +upgrade: + - | + Filtering in ``registered limit`` commands is now case sensitive. + - | + Specifying ``--region None`` is no longer supported for ``registered limit`` + commands. diff --git a/releasenotes/notes/use-project-domain-for-parent-cb29ee3f5adeb647.yaml b/releasenotes/notes/use-project-domain-for-parent-cb29ee3f5adeb647.yaml new file mode 100644 index 0000000000..acbf2ba6cf --- /dev/null +++ b/releasenotes/notes/use-project-domain-for-parent-cb29ee3f5adeb647.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + When creating or listing projects, parent project will now be searched from + the same domain as the child project. diff --git a/releasenotes/notes/volume-delete-cascade-384003efc8896096.yaml b/releasenotes/notes/volume-delete-cascade-384003efc8896096.yaml new file mode 100644 index 0000000000..679964f1bc --- /dev/null +++ b/releasenotes/notes/volume-delete-cascade-384003efc8896096.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The ``--purge`` argument to the ``volume delete`` command has been renamed + to ``--cascade`` to better match the Cinder API and the meaning of what + this argument does. An alias is provided for backwards compatibility. diff --git a/releasenotes/source/2026.1.rst b/releasenotes/source/2026.1.rst new file mode 100644 index 0000000000..3d28615808 --- /dev/null +++ b/releasenotes/source/2026.1.rst @@ -0,0 +1,6 @@ +=========================== +2026.1 Series Release Notes +=========================== + +.. release-notes:: + :branch: stable/2026.1 diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst index 2b28cfb92e..2b9b35f7bc 100644 --- a/releasenotes/source/index.rst +++ b/releasenotes/source/index.rst @@ -6,6 +6,7 @@ OpenStackClient Release Notes :maxdepth: 1 unreleased + 2026.1 2025.2 2025.1 2024.2 diff --git a/test-requirements.txt b/test-requirements.txt index c9c1b28ccd..d6a0db5b41 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -1,5 +1,6 @@ coverage!=4.4,>=4.0 # Apache-2.0 fixtures>=3.0.0 # Apache-2.0/BSD +hacking>=8.0.0 # Apache-2.0 requests-mock>=1.2.0 # Apache-2.0 stestr>=1.0.0 # Apache-2.0 testtools>=2.2.0 # MIT diff --git a/tox.ini b/tox.ini index 1988ec8236..36ec6a426e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,111 +4,113 @@ envlist = py3,pep8 [testenv] description = - Run unit tests. + Run unit tests. usedevelop = true setenv = - OS_STDOUT_CAPTURE=1 - OS_STDERR_CAPTURE=1 - OS_TEST_TIMEOUT=60 + OS_STDOUT_CAPTURE=1 + OS_STDERR_CAPTURE=1 + OS_TEST_TIMEOUT=60 deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/test-requirements.txt - -r{toxinidir}/requirements.txt + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/test-requirements.txt + -r{toxinidir}/requirements.txt commands = - stestr run {posargs} + stestr run {posargs} [testenv:pep8] description = - Run style checks. -skip_install = true + Run style checks. deps = - pre-commit + pre-commit + {[testenv:mypy]deps} commands = - pre-commit run --all-files --show-diff-on-failure + pre-commit run --all-files --show-diff-on-failure + {[testenv:mypy]commands} -[testenv:bandit] +[testenv:mypy] description = - Run bandit security checks. -skip_install = true + Run type checks. deps = - pre-commit + {[testenv]deps} + mypy + types-requests commands = - pre-commit run --all-files --show-diff-on-failure bandit + mypy --cache-dir="{envdir}/mypy_cache" {posargs:openstackclient} [testenv:unit-tips] commands = - python -m pip install -q -U -e {toxinidir}/../cliff#egg=cliff - python -m pip install -q -U -e {toxinidir}/../keystoneauth#egg=keystoneauth - python -m pip install -q -U -e {toxinidir}/../osc-lib#egg=osc_lib - python -m pip install -q -U -e {toxinidir}/../openstacksdk#egg=openstacksdk - python -m pip freeze - stestr run {posargs} + python -m pip install -q -U -e {toxinidir}/../cliff#egg=cliff + python -m pip install -q -U -e {toxinidir}/../keystoneauth#egg=keystoneauth + python -m pip install -q -U -e {toxinidir}/../osc-lib#egg=osc_lib + python -m pip install -q -U -e {toxinidir}/../openstacksdk#egg=openstacksdk + python -m pip freeze + stestr run {posargs} [testenv:functional{,-tips,-py310,-py311,-py312,-py313,-py314}] description = - Run functional tests. + Run functional tests. setenv = - OS_TEST_PATH=./openstackclient/tests/functional + OS_TEST_PATH=./openstackclient/tests/functional passenv = - OS_* + OS_* commands = - tips: python -m pip install -q -U -e {toxinidir}/../cliff#egg=cliff - tips: python -m pip install -q -U -e {toxinidir}/../keystoneauth#egg=keystoneauth1 - tips: python -m pip install -q -U -e {toxinidir}/../osc-lib#egg=osc_lib - tips: python -m pip install -q -U -e {toxinidir}/../openstacksdk#egg=openstacksdk - tips: python -m pip freeze - {[testenv]commands} + tips: python -m pip install -q -U -e {toxinidir}/../cliff#egg=cliff + tips: python -m pip install -q -U -e {toxinidir}/../keystoneauth#egg=keystoneauth1 + tips: python -m pip install -q -U -e {toxinidir}/../osc-lib#egg=osc_lib + tips: python -m pip install -q -U -e {toxinidir}/../openstacksdk#egg=openstacksdk + tips: python -m pip freeze + {[testenv]commands} [testenv:venv] description = - Run specified command in a virtual environment with all dependencies installed. + Run specified command in a virtual environment with all dependencies installed. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/requirements.txt - -r{toxinidir}/doc/requirements.txt + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/requirements.txt + -r{toxinidir}/doc/requirements.txt commands = - {posargs} + {posargs} [testenv:cover] description = - Run unit tests and generate coverage report. + Run unit tests and generate coverage report. setenv = - {[testenv]setenv} - PYTHON=coverage run --source openstackclient --parallel-mode + {[testenv]setenv} + PYTHON=coverage run --source openstackclient --parallel-mode commands = - stestr run {posargs} - coverage combine - coverage html -d cover - coverage xml -o cover/coverage.xml + stestr run {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml [testenv:debug] description = - Run specified tests through oslo_debug_helper, which allows use of pdb. + Run specified tests through oslo_debug_helper, which allows use of pdb. passenv = - OS_* + OS_* commands = - oslo_debug_helper -t openstackclient/tests {posargs} + oslo_debug_helper -t openstackclient/tests {posargs} [testenv:docs] description = - Build documentation in HTML format. + Build documentation in HTML format. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/doc/requirements.txt + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt commands = - sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html - sphinx-build -a -E -W -d doc/build/doctrees -b man doc/source doc/build/man - # Validate redirects (must be done after the docs build - whereto doc/build/html/.htaccess doc/test/redirect-tests.txt + sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html + sphinx-build -a -E -W -d doc/build/doctrees -b man doc/source doc/build/man + # Validate redirects (must be done after the docs build + whereto doc/build/html/.htaccess doc/test/redirect-tests.txt [testenv:releasenotes] description = - Build release note documentation in HTML format. + Build release note documentation in HTML format. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} - -r{toxinidir}/doc/requirements.txt + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -r{toxinidir}/doc/requirements.txt commands = - sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html + sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [flake8] show-source = true @@ -126,4 +128,5 @@ extension = O401 = checks:assert_no_duplicated_setup O402 = checks:assert_use_of_client_aliases O403 = checks:assert_find_ignore_missing_kwargs + O404 = checks:assert_use_of_osc_command paths = ./hacking