diff --git a/.gitreview b/.gitreview index 4eee726db..f099ca66b 100644 --- a/.gitreview +++ b/.gitreview @@ -2,3 +2,4 @@ host=review.opendev.org port=29418 project=openstack/python-openstackclient.git +defaultbranch=stable/2024.2 diff --git a/.zuul.yaml b/.zuul.yaml index 9c0f0b22c..5538dff57 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -219,7 +219,8 @@ - release-notes-jobs-python3 check: jobs: - - osc-build-image + - osc-build-image: + voting: false - osc-functional-devstack - osc-functional-devstack-tips: # The functional-tips job only tests the latest and shouldn't be run diff --git a/openstackclient/common/clientmanager.py b/openstackclient/common/clientmanager.py index 2e990985b..de7400f9a 100644 --- a/openstackclient/common/clientmanager.py +++ b/openstackclient/common/clientmanager.py @@ -129,10 +129,15 @@ def is_compute_endpoint_enabled(self): # TODO(stephenfin): Drop volume_client argument in OSC 8.0 or later. def is_volume_endpoint_enabled(self, volume_client=None): """Check if volume endpoint is enabled""" + # We check against the service type and all aliases defined by the + # Service Types Authority + # https://service-types.openstack.org/service-types.json return ( - self.is_service_available('volume') is not False + self.is_service_available('block-storage') is not False + or self.is_service_available('volume') is not False or self.is_service_available('volumev3') is not False or self.is_service_available('volumev2') is not False + or self.is_service_available('block-store') is not False ) diff --git a/openstackclient/common/limits.py b/openstackclient/common/limits.py index cb2f331f6..ac613fd83 100644 --- a/openstackclient/common/limits.py +++ b/openstackclient/common/limits.py @@ -130,7 +130,7 @@ def take_action(self, parsed_args): if self.app.client_manager.is_volume_endpoint_enabled(): volume_client = self.app.client_manager.sdk_connection.volume volume_limits = volume_client.get_limits( - project_id=project_id, + project=project_id, ) if parsed_args.is_absolute: diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index ec1f91a6c..d75003a67 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -249,9 +249,14 @@ def _list_quota_compute(self, parsed_args, project_ids): for project_id in project_ids: try: project_data = compute_client.get_quota_set(project_id) + # NOTE(stephenfin): Unfortunately, Nova raises a HTTP 400 (Bad + # Request) if the project ID is invalid, even though the project + # ID is actually the resource's identifier which would normally + # lead us to expect a HTTP 404 (Not Found). except ( - sdk_exceptions.NotFoundException, + sdk_exceptions.BadRequestException, sdk_exceptions.ForbiddenException, + sdk_exceptions.NotFoundException, ) as exc: # Project not found, move on to next one LOG.warning(f"Project {project_id} not found: {exc}") @@ -312,8 +317,8 @@ def _list_quota_volume(self, parsed_args, project_ids): try: project_data = volume_client.get_quota_set(project_id) except ( - sdk_exceptions.NotFoundException, sdk_exceptions.ForbiddenException, + sdk_exceptions.NotFoundException, ) as exc: # Project not found, move on to next one LOG.warning(f"Project {project_id} not found: {exc}") diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 661453b65..7abc5b92e 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -701,7 +701,10 @@ def take_action(self, parsed_args): security_group = compute_v2.find_security_group( compute_client, parsed_args.group )['name'] - compute_client.add_security_group_to_server(server, security_group) + compute_client.add_security_group_to_server( + server, + {'name': security_group}, + ) class AddServerVolume(command.ShowOne): @@ -2648,7 +2651,7 @@ def take_action(self, parsed_args): 'status': parsed_args.status, 'flavor': flavor_id, 'image': image_id, - 'host': parsed_args.host, + 'compute_host': parsed_args.host, 'project_id': project_id, 'all_projects': parsed_args.all_projects, 'user_id': user_id, @@ -3840,7 +3843,7 @@ def _show_progress(progress): kwargs = { 'host': parsed_args.host, - 'password': parsed_args.password, + 'admin_pass': parsed_args.password, } if not sdk_utils.supports_microversion(compute_client, '2.14'): @@ -4039,7 +4042,8 @@ def take_action(self, parsed_args): compute_client, parsed_args.group )['name'] compute_client.remove_security_group_from_server( - server, security_group + server, + {'name': security_group}, ) diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index 4e5f083de..12a7f1479 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -184,13 +184,6 @@ def _get_token_resource(client, resource, parsed_name, parsed_domain=None): return parsed_name -def _get_domain_id_if_requested(identity_client, domain_name_or_id): - if not domain_name_or_id: - return None - domain = find_domain(identity_client, domain_name_or_id) - return domain.id - - def find_domain(identity_client, name_or_id): return _find_identity_resource( identity_client.domains, name_or_id, domains.Domain @@ -198,45 +191,43 @@ def find_domain(identity_client, name_or_id): def find_group(identity_client, name_or_id, domain_name_or_id=None): - domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - if not domain_id: + if domain_name_or_id is None: return _find_identity_resource( identity_client.groups, name_or_id, groups.Group ) - else: - return _find_identity_resource( - identity_client.groups, - name_or_id, - groups.Group, - domain_id=domain_id, - ) + + domain_id = find_domain(identity_client, domain_name_or_id).id + return _find_identity_resource( + identity_client.groups, + name_or_id, + groups.Group, + domain_id=domain_id, + ) def find_project(identity_client, name_or_id, domain_name_or_id=None): - domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - if not domain_id: + if domain_name_or_id is None: return _find_identity_resource( identity_client.projects, name_or_id, projects.Project ) - else: - return _find_identity_resource( - identity_client.projects, - name_or_id, - projects.Project, - domain_id=domain_id, - ) + domain_id = find_domain(identity_client, domain_name_or_id).id + return _find_identity_resource( + identity_client.projects, + name_or_id, + projects.Project, + domain_id=domain_id, + ) def find_user(identity_client, name_or_id, domain_name_or_id=None): - domain_id = _get_domain_id_if_requested(identity_client, domain_name_or_id) - if not domain_id: + if domain_name_or_id is None: return _find_identity_resource( identity_client.users, name_or_id, users.User ) - else: - return _find_identity_resource( - identity_client.users, name_or_id, users.User, domain_id=domain_id - ) + domain_id = find_domain(identity_client, domain_name_or_id).id + return _find_identity_resource( + identity_client.users, name_or_id, users.User, domain_id=domain_id + ) def _find_identity_resource( diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 975329793..12e1527b6 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -146,12 +146,19 @@ def take_action(self, parsed_args): domain_id=role_domain_id, ) + user_domain_id = None + if parsed_args.user_domain: + user_domain_id = _find_sdk_id( + identity_client.find_domain, + name_or_id=parsed_args.user_domain, + ) + user_id = None if parsed_args.user: user_id = _find_sdk_id( identity_client.find_user, name_or_id=parsed_args.user, - domain_id=parsed_args.user_domain, + domain_id=user_domain_id, ) elif parsed_args.authuser: if auth_ref: @@ -171,6 +178,13 @@ def take_action(self, parsed_args): name_or_id=parsed_args.domain, ) + project_domain_id = None + if parsed_args.project_domain: + project_domain_id = _find_sdk_id( + identity_client.find_domain, + name_or_id=parsed_args.project_domain, + ) + project_id = None if parsed_args.project: project_id = _find_sdk_id( @@ -178,7 +192,7 @@ def take_action(self, parsed_args): name_or_id=common._get_token_resource( identity_client, 'project', parsed_args.project ), - domain_id=parsed_args.project_domain, + domain_id=project_domain_id, ) elif parsed_args.authproject: if auth_ref: @@ -187,12 +201,19 @@ def take_action(self, parsed_args): name_or_id=auth_ref.project_id, ) + group_domain_id = None + if parsed_args.group_domain: + group_domain_id = _find_sdk_id( + identity_client.find_domain, + name_or_id=parsed_args.group_domain, + ) + group_id = None if parsed_args.group: group_id = _find_sdk_id( identity_client.find_group, name_or_id=parsed_args.group, - domain_id=parsed_args.group_domain, + domain_id=group_domain_id, ) include_names = True if parsed_args.names else None diff --git a/openstackclient/identity/v3/service.py b/openstackclient/identity/v3/service.py index f04fa979d..41faa2551 100644 --- a/openstackclient/identity/v3/service.py +++ b/openstackclient/identity/v3/service.py @@ -225,7 +225,8 @@ def take_action(self, parsed_args): kwargs['name'] = parsed_args.name if parsed_args.description: kwargs['description'] = parsed_args.description - kwargs['is_enabled'] = parsed_args.is_enabled + if parsed_args.is_enabled is not None: + kwargs['is_enabled'] = parsed_args.is_enabled identity_client.update_service(service.id, **kwargs) diff --git a/openstackclient/identity/v3/user.py b/openstackclient/identity/v3/user.py index 38b9a0ab0..545b1cda7 100644 --- a/openstackclient/identity/v3/user.py +++ b/openstackclient/identity/v3/user.py @@ -249,26 +249,44 @@ def get_parser(self, prog_name): def take_action(self, parsed_args): identity_client = self.app.client_manager.sdk_connection.identity + kwargs = {} + domain_id = None if parsed_args.domain: domain_id = identity_client.find_domain( - name_or_id=parsed_args.domain, + parsed_args.domain, ignore_missing=False, ).id + kwargs['domain_id'] = domain_id - project_id = None if parsed_args.project: - project_id = identity_client.find_project( - name_or_id=parsed_args.project, + project_domain_id = None + if parsed_args.project_domain: + project_domain_id = identity_client.find_domain( + parsed_args.project_domain, + ignore_missing=False, + ).id + kwargs['default_project_id'] = identity_client.find_project( + parsed_args.project, ignore_missing=False, - domain_id=domain_id, + domain_id=project_domain_id, ).id + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if parsed_args.email: + kwargs['email'] = parsed_args.email + is_enabled = True if parsed_args.disable: is_enabled = False - if parsed_args.password_prompt: - parsed_args.password = utils.get_password(self.app.stdin) + + password = None + if parsed_args.password: + password = parsed_args.password + elif parsed_args.password_prompt: + password = utils.get_password(self.app.stdin) if not parsed_args.password: LOG.warning( @@ -278,24 +296,26 @@ def take_action(self, parsed_args): ) ) options = _get_options_for_user(identity_client, parsed_args) + if options: + kwargs['options'] = options try: user = identity_client.create_user( - default_project_id=project_id, - description=parsed_args.description, - domain_id=domain_id, - email=parsed_args.email, is_enabled=is_enabled, name=parsed_args.name, - password=parsed_args.password, - options=options, + password=password, + **kwargs, ) except sdk_exc.ConflictException: if parsed_args.or_show: + kwargs = {} + if domain_id: + kwargs['domain_id'] = domain_id + user = identity_client.find_user( - name_or_id=parsed_args.name, - domain_id=domain_id, + parsed_args.name, ignore_missing=False, + **kwargs, ) LOG.info(_('Returning existing user %s'), user.name) else: diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index d6638d2f7..b7aaf510e 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -1295,7 +1295,7 @@ def test_server_add_remove_port(self): ) if ip_address in cmd_output['addresses']['private']: # Hang out for a bit and try again - print('retrying add port check') + print('retrying remove port check') wait_time += 10 time.sleep(10) else: @@ -1367,6 +1367,92 @@ def test_server_add_fixed_ip(self): addresses = cmd_output['addresses']['private'] self.assertIn(ip_address, addresses) + def test_server_add_remove_security_group(self): + name = uuid.uuid4().hex + cmd_output = self.openstack( + 'server create ' + + '--network private ' + + '--flavor ' + + self.flavor_name + + ' ' + + '--image ' + + self.image_name + + ' ' + + '--wait ' + + name, + parse_output=True, + ) + + self.assertIsNotNone(cmd_output['id']) + self.assertEqual(name, cmd_output['name']) + self.addCleanup(self.openstack, 'server delete --wait ' + name) + + # create security group + security_group_name = uuid.uuid4().hex + + cmd_output = self.openstack( + 'security group list', + parse_output=True, + ) + self.assertNotIn(security_group_name, cmd_output) + + cmd_output = self.openstack( + 'security group create ' + security_group_name, + parse_output=True, + ) + self.assertIsNotNone(cmd_output['id']) + self.addCleanup( + self.openstack, 'security group delete ' + security_group_name + ) + + # add security group to server, assert the name of the security group + # appears + self.openstack( + 'server add security group ' + name + ' ' + security_group_name + ) + + wait_time = 0 + while wait_time < 60: + cmd_output = self.openstack( + 'server show ' + name, + parse_output=True, + ) + if security_group_name not in [ + x['name'] for x in cmd_output['security_groups'] + ]: + # Hang out for a bit and try again + print('retrying add security group check') + wait_time += 10 + time.sleep(10) + else: + break + security_groups = [x['name'] for x in cmd_output['security_groups']] + self.assertIn(security_group_name, security_groups) + + # remove security group, assert the name of the security group doesn't + # appear + self.openstack( + 'server remove security group ' + name + ' ' + security_group_name + ) + + wait_time = 0 + while wait_time < 60: + cmd_output = self.openstack( + 'server show ' + name, + parse_output=True, + ) + if security_group_name not in [ + x['name'] for x in cmd_output['security_groups'] + ]: + # Hang out for a bit and try again + print('retrying remove security group check') + wait_time += 10 + time.sleep(10) + else: + break + security_groups = [x['name'] for x in cmd_output['security_groups']] + self.assertNotIn(security_group_name, security_groups) + def test_server_add_remove_volume(self): volume_wait_for = volume_common.BaseVolumeTests.wait_for_status diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 434d2e61f..67171e2bd 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1186,7 +1186,7 @@ def test_server_add_security_group__nova_network(self): self.server.id, ignore_missing=False ) self.compute_sdk_client.add_security_group_to_server.assert_called_once_with( - self.server, 'fake_sg' + self.server, {'name': 'fake_sg'} ) mock_find_nova_net_sg.assert_called_once_with( self.compute_sdk_client, 'fake_sg' @@ -1207,7 +1207,7 @@ def test_server_add_security_group(self): self.server.id, ignore_missing=False ) self.compute_sdk_client.add_security_group_to_server.assert_called_once_with( - self.server, 'fake_sg' + self.server, {'name': 'fake_sg'} ) self.assertIsNone(result) @@ -4581,7 +4581,7 @@ def setUp(self): 'status': None, 'flavor': None, 'image': None, - 'host': None, + 'compute_host': None, 'project_id': None, 'all_projects': False, 'user_id': None, @@ -6974,7 +6974,7 @@ def test_evacuate(self): evac_args = { 'host': None, 'on_shared_storage': False, - 'password': None, + 'admin_pass': None, } self._test_evacuate(args, verify_args, evac_args) @@ -6991,7 +6991,7 @@ def test_evacuate_with_password(self): evac_args = { 'host': None, 'on_shared_storage': False, - 'password': 'password', + 'admin_pass': 'password', } self._test_evacuate(args, verify_args, evac_args) @@ -7008,7 +7008,7 @@ def test_evacuate_with_host(self): ('server', self.server.id), ('host', 'target-host'), ] - evac_args = {'host': host, 'password': None} + evac_args = {'host': host, 'admin_pass': None} self._test_evacuate(args, verify_args, evac_args) @@ -7041,7 +7041,7 @@ def test_evacuate_without_share_storage(self): evac_args = { 'host': None, 'on_shared_storage': True, - 'password': None, + 'admin_pass': None, } self._test_evacuate(args, verify_args, evac_args) @@ -7072,7 +7072,7 @@ def test_evacuate_with_wait_ok(self, mock_wait_for_status): evac_args = { 'host': None, 'on_shared_storage': False, - 'password': None, + 'admin_pass': None, } self._test_evacuate(args, verify_args, evac_args) mock_wait_for_status.assert_called_once_with( @@ -7394,7 +7394,7 @@ def test_server_remove_security_group__nova_network(self): self.server.id, ignore_missing=False ) self.compute_sdk_client.remove_security_group_from_server.assert_called_once_with( - self.server, 'fake_sg' + self.server, {'name': 'fake_sg'} ) mock_find_nova_net_sg.assert_called_once_with( self.compute_sdk_client, 'fake_sg' @@ -7415,7 +7415,7 @@ def test_server_remove_security_group(self): self.server.id, ignore_missing=False ) self.compute_sdk_client.remove_security_group_from_server.assert_called_once_with( - self.server, 'fake_sg' + self.server, {'name': 'fake_sg'} ) self.assertIsNone(result) diff --git a/openstackclient/tests/unit/identity/v3/test_role_assignment.py b/openstackclient/tests/unit/identity/v3/test_role_assignment.py index ec7c7da56..65dad0d77 100644 --- a/openstackclient/tests/unit/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/unit/identity/v3/test_role_assignment.py @@ -165,10 +165,13 @@ def test_role_assignment_list_user(self): arglist = ['--user', self.user.name] verifylist = [ ('user', self.user.name), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -181,6 +184,79 @@ def test_role_assignment_list_user(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_user.assert_called_with( + name_or_id=self.user.name, ignore_missing=False, domain_id=None + ) + self.identity_sdk_client.role_assignments.assert_called_with( + role_id=None, + user_id=self.user.id, + group_id=None, + scope_project_id=None, + scope_domain_id=None, + scope_system=None, + effective=None, + include_names=None, + inherited_to=None, + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.role.id, + self.user.id, + '', + '', + self.domain.id, + '', + False, + ), + ( + self.role.id, + self.user.id, + '', + self.project.id, + '', + '', + False, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_user_with_domain(self): + self.identity_sdk_client.role_assignments.return_value = [ + self.assignment_with_domain_id_and_user_id, + self.assignment_with_project_id_and_user_id, + ] + + arglist = ['--user', self.user.name, '--user-domain', self.domain.name] + verifylist = [ + ('user', self.user.name), + ('user_domain', self.domain.name), + ('group', None), + ('group_domain', None), + ('system', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', None), + ('inherited', False), + ('names', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # 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.identity_sdk_client.find_domain.assert_called_with( + name_or_id=self.domain.name, ignore_missing=False + ) + self.identity_sdk_client.find_user.assert_called_with( + name_or_id=self.user.name, + ignore_missing=False, + domain_id=self.domain.id, + ) self.identity_sdk_client.role_assignments.assert_called_with( role_id=None, user_id=self.user.id, @@ -230,10 +306,13 @@ def test_role_assignment_list_user_not_found(self, find_mock): arglist = ['--user', self.user.id] verifylist = [ ('user', self.user.id), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -290,10 +369,13 @@ def test_role_assignment_list_group(self): arglist = ['--group', self.group.name] verifylist = [ ('user', None), + ('user_domain', None), ('group', self.group.name), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -306,6 +388,85 @@ def test_role_assignment_list_group(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_group.assert_called_with( + name_or_id=self.group.name, ignore_missing=False, domain_id=None + ) + self.identity_sdk_client.role_assignments.assert_called_with( + role_id=None, + user_id=None, + group_id=self.group.id, + scope_project_id=None, + scope_domain_id=None, + scope_system=None, + effective=None, + include_names=None, + inherited_to=None, + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.role.id, + '', + self.group.id, + '', + self.domain.id, + '', + False, + ), + ( + self.role.id, + '', + self.group.id, + self.project.id, + '', + '', + False, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_group_with_domain(self): + self.identity_sdk_client.role_assignments.return_value = [ + self.assignment_with_domain_id_and_group_id, + self.assignment_with_project_id_and_group_id, + ] + + arglist = [ + '--group', + self.group.name, + '--group-domain', + self.domain.name, + ] + verifylist = [ + ('user', None), + ('user_domain', None), + ('group', self.group.name), + ('group_domain', self.domain.name), + ('system', None), + ('domain', None), + ('project', None), + ('project_domain', None), + ('role', None), + ('effective', None), + ('inherited', False), + ('names', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # 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.identity_sdk_client.find_domain.assert_called_with( + name_or_id=self.domain.name, ignore_missing=False + ) + self.identity_sdk_client.find_group.assert_called_with( + name_or_id=self.group.name, + ignore_missing=False, + domain_id=self.domain.id, + ) self.identity_sdk_client.role_assignments.assert_called_with( role_id=None, user_id=None, @@ -350,10 +511,13 @@ def test_role_assignment_list_domain(self): arglist = ['--domain', self.domain.name] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', self.domain.name), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -410,10 +574,13 @@ def test_role_assignment_list_project(self): arglist = ['--project', self.project.name] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', self.project.name), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -426,6 +593,85 @@ def test_role_assignment_list_project(self): # containing the data to be listed. columns, data = self.cmd.take_action(parsed_args) + self.identity_sdk_client.find_project.assert_called_with( + name_or_id=self.project.name, ignore_missing=False, domain_id=None + ) + self.identity_sdk_client.role_assignments.assert_called_with( + role_id=None, + user_id=None, + group_id=None, + scope_project_id=self.project.id, + scope_domain_id=None, + scope_system=None, + effective=None, + include_names=None, + inherited_to=None, + ) + + self.assertEqual(self.columns, columns) + datalist = ( + ( + self.role.id, + self.user.id, + '', + self.project.id, + '', + '', + False, + ), + ( + self.role.id, + '', + self.group.id, + self.project.id, + '', + '', + False, + ), + ) + self.assertEqual(datalist, tuple(data)) + + def test_role_assignment_list_project_with_domain(self): + self.identity_sdk_client.role_assignments.return_value = [ + self.assignment_with_project_id_and_user_id, + self.assignment_with_project_id_and_group_id, + ] + + arglist = [ + '--project', + self.project.name, + '--project-domain', + self.domain.name, + ] + verifylist = [ + ('user', None), + ('user_domain', None), + ('group', None), + ('group_domain', None), + ('system', None), + ('domain', None), + ('project', self.project.name), + ('project_domain', self.domain.name), + ('role', None), + ('effective', None), + ('inherited', False), + ('names', False), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # In base command class Lister in cliff, abstract method take_action() + # 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.identity_sdk_client.find_domain.assert_called_with( + name_or_id=self.domain.name, ignore_missing=False + ) + self.identity_sdk_client.find_project.assert_called_with( + name_or_id=self.project.name, + ignore_missing=False, + domain_id=self.domain.id, + ) self.identity_sdk_client.role_assignments.assert_called_with( role_id=None, user_id=None, @@ -476,10 +722,13 @@ def test_role_assignment_list_def_creds(self): ] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -529,10 +778,13 @@ def test_role_assignment_list_effective(self): arglist = ['--effective'] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', True), ('inherited', False), @@ -611,10 +863,13 @@ def test_role_assignment_list_inherited(self): arglist = ['--inherited'] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', True), @@ -707,10 +962,13 @@ def test_role_assignment_list_include_names(self): arglist = ['--names'] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', None), ('effective', None), ('inherited', False), @@ -799,10 +1057,13 @@ def test_role_assignment_list_domain_role(self): ] verifylist = [ ('user', None), + ('user_domain', None), ('group', None), + ('group_domain', None), ('system', None), ('domain', None), ('project', None), + ('project_domain', None), ('role', role_2.name), ('effective', None), ('inherited', False), diff --git a/openstackclient/tests/unit/identity/v3/test_service.py b/openstackclient/tests/unit/identity/v3/test_service.py index 4c02d9891..d6dd0287b 100644 --- a/openstackclient/tests/unit/identity/v3/test_service.py +++ b/openstackclient/tests/unit/identity/v3/test_service.py @@ -311,7 +311,6 @@ def test_service_set_type(self): # Set expected values kwargs = { 'type': self.service.type, - 'is_enabled': None, } self.identity_sdk_client.update_service.assert_called_with( self.service.id, **kwargs @@ -338,7 +337,6 @@ def test_service_set_name(self): # Set expected values kwargs = { 'name': self.service.name, - 'is_enabled': None, } self.identity_sdk_client.update_service.assert_called_with( self.service.id, **kwargs @@ -365,7 +363,6 @@ def test_service_set_description(self): # Set expected values kwargs = { 'description': self.service.description, - 'is_enabled': None, } self.identity_sdk_client.update_service.assert_called_with( self.service.id, **kwargs diff --git a/openstackclient/tests/unit/identity/v3/test_user.py b/openstackclient/tests/unit/identity/v3/test_user.py index 19c1a6dcc..7f4c2497e 100644 --- a/openstackclient/tests/unit/identity/v3/test_user.py +++ b/openstackclient/tests/unit/identity/v3/test_user.py @@ -91,11 +91,6 @@ def test_user_create_no_options(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': None, } @@ -127,11 +122,6 @@ def test_user_create_password(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': 'secret', } @@ -165,11 +155,6 @@ def test_user_create_password_prompt(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': 'abc123', } @@ -200,12 +185,8 @@ def test_user_create_email(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, 'email': 'barney@example.com', 'is_enabled': True, - 'options': {}, 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -236,11 +217,7 @@ def test_user_create_project(self): kwargs = { 'name': self.user.name, 'default_project_id': self.project.id, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, - 'options': {}, 'password': None, } self.identity_sdk_client.create_user.assert_called_with(**kwargs) @@ -284,14 +261,13 @@ def test_user_create_project_domain(self): kwargs = { 'name': self.user.name, 'default_project_id': self.project.id, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': None, } - self.identity_sdk_client.create_user.assert_called_with(**kwargs) + self.identity_sdk_client.create_user.assert_called_once_with(**kwargs) + self.identity_sdk_client.find_domain.assert_called_once_with( + self.project.domain_id, ignore_missing=False + ) self.assertEqual(self.columns, columns) datalist = ( @@ -328,11 +304,7 @@ def test_user_create_domain(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, 'domain_id': self.domain.id, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': None, } @@ -361,11 +333,6 @@ def test_user_create_enable(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': True, 'password': None, } @@ -394,11 +361,6 @@ def test_user_create_disable(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, - 'options': {}, 'is_enabled': False, 'password': None, } @@ -428,10 +390,6 @@ def test_user_create_ignore_lockout_failure_attempts(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_lockout_failure_attempts': True}, 'password': None, @@ -462,10 +420,6 @@ def test_user_create_no_ignore_lockout_failure_attempts(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_lockout_failure_attempts': False}, 'password': None, @@ -496,10 +450,6 @@ def test_user_create_ignore_password_expiry(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_password_expiry': True}, 'password': None, @@ -530,10 +480,6 @@ def test_user_create_no_ignore_password_expiry(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_password_expiry': False}, 'password': None, @@ -564,10 +510,6 @@ def test_user_create_ignore_change_password_upon_first_use(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_change_password_upon_first_use': True}, 'password': None, @@ -598,10 +540,6 @@ def test_user_create_no_ignore_change_password_upon_first_use(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'ignore_change_password_upon_first_use': False}, 'password': None, @@ -632,10 +570,6 @@ def test_user_create_enables_lock_password(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'lock_password': True}, 'password': None, @@ -666,10 +600,6 @@ def test_user_create_disables_lock_password(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'lock_password': False}, 'password': None, @@ -700,10 +630,6 @@ def test_user_create_enable_multi_factor_auth(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'multi_factor_auth_enabled': True}, 'password': None, @@ -734,10 +660,6 @@ def test_user_create_disable_multi_factor_auth(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': {'multi_factor_auth_enabled': False}, 'password': None, @@ -774,10 +696,6 @@ def test_user_create_option_with_multi_factor_auth_rule(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': { 'multi_factor_auth_rules': [["password", "totp"], ["password"]] @@ -815,10 +733,6 @@ def test_user_create_with_multiple_options(self): # Set expected values kwargs = { 'name': self.user.name, - 'default_project_id': None, - 'description': None, - 'domain_id': None, - 'email': None, 'is_enabled': True, 'options': { 'ignore_password_expiry': True, diff --git a/releasenotes/notes/bug-2084580-cb1e8c47501e730c.yaml b/releasenotes/notes/bug-2084580-cb1e8c47501e730c.yaml new file mode 100644 index 000000000..5ac30b373 --- /dev/null +++ b/releasenotes/notes/bug-2084580-cb1e8c47501e730c.yaml @@ -0,0 +1,8 @@ +--- +fixes: + - | + The ``quota set`` and ``limits show`` commands will now check for the + ``block-storage`` and ``block-store`` service types along with ``volume``, + ``volumev2`` and ``volumev3``. + + [Bug `2084580 `_] diff --git a/tox.ini b/tox.ini index e7c84c511..51495096a 100644 --- a/tox.ini +++ b/tox.ini @@ -11,7 +11,7 @@ setenv = OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=60 deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.2} -r{toxinidir}/test-requirements.txt -r{toxinidir}/requirements.txt commands = @@ -63,7 +63,7 @@ commands = description = Run specified command in a virtual environment with all dependencies installed. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.2} -r{toxinidir}/requirements.txt -r{toxinidir}/doc/requirements.txt commands = @@ -93,7 +93,7 @@ commands = description = Build documentation in HTML format. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.2} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d doc/build/doctrees -b html doc/source doc/build/html @@ -105,7 +105,7 @@ commands = description = Build release note documentation in HTML format. deps = - -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/master} + -c{env:TOX_CONSTRAINTS_FILE:https://releases.openstack.org/constraints/upper/2024.2} -r{toxinidir}/doc/requirements.txt commands = sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html