diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/common/quota.py | 13 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server.py | 192 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server_group.py | 38 | ||||
| -rw-r--r-- | openstackclient/identity/common.py | 2 | ||||
| -rw-r--r-- | openstackclient/identity/v3/project.py | 10 | ||||
| -rw-r--r-- | openstackclient/tests/unit/common/test_quota.py | 69 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 316 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server_group.py | 47 | ||||
| -rw-r--r-- | openstackclient/tests/unit/identity/v3/test_project.py | 22 |
9 files changed, 502 insertions, 207 deletions
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 314ff8b5..b697c24c 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -279,9 +279,18 @@ class ListQuota(command.Lister, BaseQuota): return parser def take_action(self, parsed_args): - projects = self.app.client_manager.identity.projects.list() result = [] - project_ids = [getattr(p, 'id', '') for p in projects] + project_ids = [] + if parsed_args.project is None: + for p in self.app.client_manager.identity.projects.list(): + project_ids.append(getattr(p, 'id', '')) + else: + identity_client = self.app.client_manager.identity + project = utils.find_resource( + identity_client.projects, + parsed_args.project, + ) + project_ids.append(getattr(project, 'id', '')) if parsed_args.compute: if parsed_args.detail: diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index d3bd1c72..2b68a5cf 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1308,15 +1308,19 @@ class ListServer(command.Lister): # flavor name is given, map it to ID. flavor_id = None if parsed_args.flavor: - flavor_id = utils.find_resource(compute_client.flavors, - parsed_args.flavor).id + flavor_id = utils.find_resource( + compute_client.flavors, + parsed_args.flavor, + ).id # Nova only supports list servers searching by image ID. So if a # image name is given, map it to ID. image_id = None if parsed_args.image: - image_id = utils.find_resource(image_client.images, - parsed_args.image).id + image_id = utils.find_resource( + image_client.images, + parsed_args.image, + ).id search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -1366,76 +1370,85 @@ class ListServer(command.Lister): try: timeutils.parse_isotime(search_opts['changes-since']) except ValueError: + msg = _('Invalid changes-since value: %s') raise exceptions.CommandError( - _('Invalid changes-since value: %s') % - search_opts['changes-since'] + msg % search_opts['changes-since'] ) + columns = ( + 'id', + 'name', + 'status', + ) + column_headers = ( + 'ID', + 'Name', + 'Status', + ) + if parsed_args.long: - columns = ( - 'ID', - 'Name', - 'Status', + columns += ( 'OS-EXT-STS:task_state', 'OS-EXT-STS:power_state', - 'Networks', - 'Image Name', - 'Image ID', - 'Flavor Name', - 'Flavor ID', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - 'Metadata', ) - column_headers = ( - 'ID', - 'Name', - 'Status', + column_headers += ( 'Task State', 'Power State', - 'Networks', + ) + + columns += ('networks',) + column_headers += ('Networks',) + + if parsed_args.long: + columns += ( + 'image_name', + 'image_id', + ) + column_headers += ( 'Image Name', 'Image ID', - 'Flavor Name', - 'Flavor ID', - 'Availability Zone', - 'Host', - 'Properties', ) - mixed_case_fields = [ - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', - ] else: if parsed_args.no_name_lookup: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image ID', - 'Flavor ID', - ) + columns += ('image_id',) else: - columns = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image Name', + columns += ('image_name',) + column_headers += ('Image',) + + # microversion 2.47 puts the embedded flavor into the server response + # body but omits the id, so if not present we just expose the original + # flavor name in the output + if compute_client.api_version >= api_versions.APIVersion('2.47'): + columns += ('flavor_name',) + column_headers += ('Flavor',) + else: + if parsed_args.long: + columns += ( + 'flavor_name', + 'flavor_id', + ) + column_headers += ( 'Flavor Name', + 'Flavor ID', ) - column_headers = ( - 'ID', - 'Name', - 'Status', - 'Networks', - 'Image', - 'Flavor', + else: + if parsed_args.no_name_lookup: + columns += ('flavor_id',) + else: + columns += ('flavor_name',) + column_headers += ('Flavor',) + + if parsed_args.long: + columns += ( + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + 'metadata', + ) + column_headers += ( + 'Availability Zone', + 'Host', + 'Properties', ) - mixed_case_fields = [] marker_id = None @@ -1447,25 +1460,29 @@ class ListServer(command.Lister): if parsed_args.deleted: marker_id = parsed_args.marker else: - marker_id = utils.find_resource(compute_client.servers, - parsed_args.marker).id + marker_id = utils.find_resource( + compute_client.servers, + parsed_args.marker, + ).id - data = compute_client.servers.list(search_opts=search_opts, - marker=marker_id, - limit=parsed_args.limit) + data = compute_client.servers.list( + search_opts=search_opts, + marker=marker_id, + limit=parsed_args.limit) images = {} flavors = {} if data and not parsed_args.no_name_lookup: - # Create a dict that maps image_id to image object. - # Needed so that we can display the "Image Name" column. - # "Image Name" is not crucial, so we swallow any exceptions. - # The 'image' attribute can be an empty string if the server was - # booted from a volume. + # create a dict that maps image_id to image object, which is used + # to display the "Image Name" column. Note that 'image.id' can be + # empty for BFV instances and 'image' can be missing entirely if + # there are infra failures if parsed_args.name_lookup_one_by_one or image_id: - for i_id in set(filter(lambda x: x is not None, - (s.image.get('id') for s in data - if s.image))): + for i_id in set( + s.image['id'] for s in data + if s.image and s.image.get('id') + ): + # "Image Name" is not crucial, so we swallow any exceptions try: images[i_id] = image_client.images.get(i_id) except Exception: @@ -1478,12 +1495,17 @@ class ListServer(command.Lister): except Exception: pass - # Create a dict that maps flavor_id to flavor object. - # Needed so that we can display the "Flavor Name" column. - # "Flavor Name" is not crucial, so we swallow any exceptions. + # create a dict that maps flavor_id to flavor object, which is used + # to display the "Flavor Name" column. Note that 'flavor.id' is not + # present on microversion 2.47 or later and 'flavor' won't be + # present if there are infra failures if parsed_args.name_lookup_one_by_one or flavor_id: - for f_id in set(filter(lambda x: x is not None, - (s.flavor.get('id') for s in data))): + for f_id in set( + s.flavor['id'] for s in data + if s.flavor and s.flavor.get('id') + ): + # "Flavor Name" is not crucial, so we swallow any + # exceptions try: flavors[f_id] = compute_client.flavors.get(f_id) except Exception: @@ -1507,6 +1529,7 @@ class ListServer(command.Lister): # processing of the image and flavor informations. if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue + if 'id' in s.image: image = images.get(s.image['id']) if image: @@ -1515,28 +1538,29 @@ class ListServer(command.Lister): else: s.image_name = '' s.image_id = '' - if 'id' in s.flavor: + + if compute_client.api_version < api_versions.APIVersion('2.47'): flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name s.flavor_id = s.flavor['id'] else: - # TODO(mriedem): Fix this for microversion >= 2.47 where the - # flavor is embedded in the server response without the id. - # We likely need to drop the Flavor ID column in that case if - # --long is specified. - s.flavor_name = '' - s.flavor_id = '' + s.flavor_name = s.flavor['original_name'] table = (column_headers, (utils.get_item_properties( s, columns, - mixed_case_fields=mixed_case_fields, + mixed_case_fields=( + 'OS-EXT-STS:task_state', + 'OS-EXT-STS:power_state', + 'OS-EXT-AZ:availability_zone', + 'OS-EXT-SRV-ATTR:host', + ), formatters={ 'OS-EXT-STS:power_state': _format_servers_list_power_state, - 'Networks': _format_servers_list_networks, - 'Metadata': utils.format_dict, + 'networks': _format_servers_list_networks, + 'metadata': utils.format_dict, }, ) for s in data)) return table diff --git a/openstackclient/compute/v2/server_group.py b/openstackclient/compute/v2/server_group.py index 1af6e28d..b632effd 100644 --- a/openstackclient/compute/v2/server_group.py +++ b/openstackclient/compute/v2/server_group.py @@ -135,11 +135,47 @@ class ListServerGroup(command.Lister): default=False, help=_("List additional fields in output") ) + # TODO(stephenfin): This should really be a --marker option, but alas + # the API doesn't support that for some reason + parser.add_argument( + '--offset', + metavar='<offset>', + type=int, + default=None, + help=_( + 'Index from which to start listing servers. This should ' + 'typically be a factor of --limit. Display all servers groups ' + 'if not specified.' + ), + ) + parser.add_argument( + '--limit', + metavar='<limit>', + type=int, + default=None, + help=_( + "Maximum number of server groups to display. " + "If limit is greater than 'osapi_max_limit' option of Nova " + "API, 'osapi_max_limit' will be used instead." + ), + ) return parser def take_action(self, parsed_args): compute_client = self.app.client_manager.compute - data = compute_client.server_groups.list(parsed_args.all_projects) + + kwargs = {} + + if parsed_args.all_projects: + kwargs['all_projects'] = parsed_args.all_projects + + if parsed_args.offset: + kwargs['offset'] = parsed_args.offset + + if parsed_args.limit: + kwargs['limit'] = parsed_args.limit + + data = compute_client.server_groups.list(**kwargs) policy_key = 'Policies' if compute_client.api_version >= api_versions.APIVersion("2.64"): diff --git a/openstackclient/identity/common.py b/openstackclient/identity/common.py index e8251166..3200e535 100644 --- a/openstackclient/identity/common.py +++ b/openstackclient/identity/common.py @@ -207,7 +207,7 @@ def _find_identity_resource(identity_client_manager, name_or_id, name_or_id, **kwargs) if identity_resource is not None: return identity_resource - except identity_exc.Forbidden: + except (exceptions.Forbidden, identity_exc.Forbidden): pass return resource_type(None, {'id': name_or_id, 'name': name_or_id}) diff --git a/openstackclient/identity/v3/project.py b/openstackclient/identity/v3/project.py index 1d6f1810..35d68881 100644 --- a/openstackclient/identity/v3/project.py +++ b/openstackclient/identity/v3/project.py @@ -194,6 +194,11 @@ class ListProject(command.Lister): help=_('Filter projects by <domain> (name or ID)'), ) parser.add_argument( + '--parent', + metavar='<parent>', + help=_('Filter projects whose parent is <parent> (name or ID)'), + ) + parser.add_argument( '--user', metavar='<user>', help=_('Filter projects by <user> (name or ID)'), @@ -234,6 +239,11 @@ class ListProject(command.Lister): parsed_args.domain).id kwargs['domain'] = domain_id + if parsed_args.parent: + parent_id = common.find_project(identity_client, + parsed_args.parent).id + kwargs['parent'] = parent_id + if parsed_args.user: if parsed_args.domain: user_id = utils.find_resource(identity_client.users, diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index f519f872..6d9ec451 100644 --- a/openstackclient/tests/unit/common/test_quota.py +++ b/openstackclient/tests/unit/common/test_quota.py @@ -392,6 +392,29 @@ class TestQuotaList(TestQuota): parsed_args, ) + def test_quota_list_compute_by_project(self): + # Two projects with non-default quotas + self.compute.quotas.get = mock.Mock( + side_effect=self.compute_quotas, + ) + + arglist = [ + '--compute', + '--project', self.projects[0].name, + ] + verifylist = [ + ('compute', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.compute_column_header, columns) + self.assertEqual(self.compute_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + def test_quota_list_network(self): # Two projects with non-default quotas self.network.get_quota = mock.Mock( @@ -461,6 +484,29 @@ class TestQuotaList(TestQuota): self.assertEqual(self.network_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) + def test_quota_list_network_by_project(self): + # Two projects with non-default quotas + self.network.get_quota = mock.Mock( + side_effect=self.network_quotas, + ) + + arglist = [ + '--network', + '--project', self.projects[0].name, + ] + verifylist = [ + ('network', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.network_column_header, columns) + self.assertEqual(self.network_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + def test_quota_list_volume(self): # Two projects with non-default quotas self.volume.quotas.get = mock.Mock( @@ -530,6 +576,29 @@ class TestQuotaList(TestQuota): self.assertEqual(self.volume_reference_data, ret_quotas[0]) self.assertEqual(1, len(ret_quotas)) + def test_quota_list_volume_by_project(self): + # Two projects with non-default quotas + self.volume.quotas.get = mock.Mock( + side_effect=self.volume_quotas, + ) + + arglist = [ + '--volume', + '--project', self.projects[0].name, + ] + verifylist = [ + ('volume', True), + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + ret_quotas = list(data) + + self.assertEqual(self.volume_column_header, columns) + self.assertEqual(self.volume_reference_data, ret_quotas[0]) + self.assertEqual(1, len(ret_quotas)) + class TestQuotaSet(TestQuota): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 819fc354..b5071b6b 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2337,7 +2337,7 @@ class TestServerDumpCreate(TestServer): self.run_method_with_servers('trigger_crash_dump', 3) -class TestServerList(TestServer): +class _TestServerList(TestServer): # Columns to be listed up. columns = ( @@ -2365,7 +2365,7 @@ class TestServerList(TestServer): ) def setUp(self): - super(TestServerList, self).setUp() + super(_TestServerList, self).setUp() self.search_opts = { 'reservation_id': None, @@ -2420,10 +2420,11 @@ class TestServerList(TestServer): # Get the command object to test self.cmd = server.ListServer(self.app, None) - # Prepare data returned by fake Nova API. - self.data = [] - self.data_long = [] - self.data_no_name_lookup = [] + +class TestServerList(_TestServerList): + + def setUp(self): + super(TestServerList, self).setUp() Image = collections.namedtuple('Image', 'id name') self.images_mock.list.return_value = [ @@ -2438,8 +2439,8 @@ class TestServerList(TestServer): for s in self.servers ] - for s in self.servers: - self.data.append(( + self.data = tuple( + ( s.id, s.name, s.status, @@ -2447,34 +2448,8 @@ class TestServerList(TestServer): # Image will be an empty string if boot-from-volume self.image.name if s.image else s.image, self.flavor.name, - )) - self.data_long.append(( - s.id, - s.name, - s.status, - getattr(s, 'OS-EXT-STS:task_state'), - server._format_servers_list_power_state( - getattr(s, 'OS-EXT-STS:power_state') - ), - server._format_servers_list_networks(s.networks), - # Image will be an empty string if boot-from-volume - self.image.name if s.image else s.image, - s.image['id'] if s.image else s.image, - self.flavor.name, - s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - s.Metadata, - )) - self.data_no_name_lookup.append(( - s.id, - s.name, - s.status, - server._format_servers_list_networks(s.networks), - # Image will be an empty string if boot-from-volume - s.image['id'] if s.image else s.image, - s.flavor['id'] - )) + ) for s in self.servers + ) def test_server_list_no_option(self): arglist = [] @@ -2495,7 +2470,7 @@ class TestServerList(TestServer): self.assertFalse(self.flavors_mock.get.call_count) self.assertFalse(self.images_mock.get.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_no_servers(self): arglist = [] @@ -2514,9 +2489,28 @@ class TestServerList(TestServer): self.assertEqual(0, self.images_mock.list.call_count) self.assertEqual(0, self.flavors_mock.list.call_count) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'OS-EXT-STS:task_state'), + server._format_servers_list_power_state( + getattr(s, 'OS-EXT-STS:power_state') + ), + server._format_servers_list_networks(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, + s.image['id'] if s.image else s.image, + self.flavor.name, + s.flavor['id'], + getattr(s, 'OS-EXT-AZ:availability_zone'), + getattr(s, 'OS-EXT-SRV-ATTR:host'), + s.Metadata, + ) for s in self.servers) arglist = [ '--long', ] @@ -2527,12 +2521,23 @@ class TestServerList(TestServer): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns_long, columns) - self.assertEqual(tuple(self.data_long), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_no_name_lookup_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + server._format_servers_list_networks(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else s.image, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '--no-name-lookup', ] @@ -2546,9 +2551,21 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_n_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + server._format_servers_list_networks(s.networks), + # Image will be an empty string if boot-from-volume + s.image['id'] if s.image else s.image, + s.flavor['id'] + ) for s in self.servers + ) + arglist = [ '-n', ] @@ -2562,7 +2579,7 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data_no_name_lookup), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_name_lookup_one_by_one(self): arglist = [ @@ -2584,7 +2601,7 @@ class TestServerList(TestServer): self.flavors_mock.get.assert_called() self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) def test_server_list_with_image(self): @@ -2604,145 +2621,213 @@ class TestServerList(TestServer): self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) - def test_server_list_with_locked_pre_v273(self): + def test_server_list_with_flavor(self): arglist = [ - '--locked' + '--flavor', self.flavor.id ] verifylist = [ - ('locked', True) + ('flavor', self.flavor.id) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - ex = self.assertRaises(exceptions.CommandError, - self.cmd.take_action, - parsed_args) - self.assertIn( - '--os-compute-api-version 2.73 or greater is required', str(ex)) + columns, data = self.cmd.take_action(parsed_args) - def test_server_list_with_locked_v273(self): + self.flavors_mock.get.has_calls(self.flavor.id) + + self.search_opts['flavor'] = self.flavor.id + self.servers_mock.list.assert_called_with(**self.kwargs) + + self.assertEqual(self.columns, columns) + self.assertEqual(self.data, tuple(data)) + + def test_server_list_with_changes_since(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') arglist = [ - '--locked' + '--changes-since', '2016-03-04T06:27:59Z', + '--deleted' ] verifylist = [ - ('locked', True) + ('changes_since', '2016-03-04T06:27:59Z'), + ('deleted', True), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['locked'] = True + self.search_opts['changes-since'] = '2016-03-04T06:27:59Z' + self.search_opts['deleted'] = True self.servers_mock.list.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertEqual(self.data, tuple(data)) - def test_server_list_with_unlocked_v273(self): + @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) + def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') arglist = [ - '--unlocked' + '--changes-since', 'Invalid time value', ] verifylist = [ - ('unlocked', True) + ('changes_since', 'Invalid time value'), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - columns, data = self.cmd.take_action(parsed_args) + try: + self.cmd.take_action(parsed_args) + self.fail('CommandError should be raised.') + except exceptions.CommandError as e: + self.assertEqual('Invalid changes-since value: Invalid time ' + 'value', str(e)) + mock_parse_isotime.assert_called_once_with( + 'Invalid time value' + ) - self.search_opts['locked'] = False - self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) +class TestServerListV273(_TestServerList): + + # Columns to be listed up. + columns = ( + 'ID', + 'Name', + 'Status', + 'Networks', + 'Image', + 'Flavor', + ) + columns_long = ( + 'ID', + 'Name', + 'Status', + 'Task State', + 'Power State', + 'Networks', + 'Image Name', + 'Image ID', + 'Flavor', + 'Availability Zone', + 'Host', + 'Properties', + ) - def test_server_list_with_locked_and_unlocked_v273(self): + def setUp(self): + super(TestServerListV273, self).setUp() + + # The fake servers' attributes. Use the original attributes names in + # nova, not the ones printed by "server list" command. + self.attrs['flavor'] = { + 'vcpus': self.flavor.vcpus, + 'ram': self.flavor.ram, + 'disk': self.flavor.disk, + 'ephemeral': self.flavor.ephemeral, + 'swap': self.flavor.swap, + 'original_name': self.flavor.name, + 'extra_specs': self.flavor.properties, + } + + # The servers to be listed. + self.servers = self.setup_servers_mock(3) + self.servers_mock.list.return_value = self.servers + + Image = collections.namedtuple('Image', 'id name') + self.images_mock.list.return_value = [ + Image(id=s.image['id'], name=self.image.name) + # Image will be an empty string if boot-from-volume + for s in self.servers if s.image + ] + + # The flavor information is embedded, so now reason for this to be + # called + self.flavors_mock.list = mock.NonCallableMock() + + self.data = tuple( + ( + s.id, + s.name, + s.status, + server._format_servers_list_networks(s.networks), + # Image will be an empty string if boot-from-volume + self.image.name if s.image else s.image, + self.flavor.name, + ) for s in self.servers) + + def test_server_list_with_locked_pre_v273(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') arglist = [ - '--locked', - '--unlocked' + '--locked' ] verifylist = [ - ('locked', True), - ('unlocked', True) + ('locked', True) ] - ex = self.assertRaises( - utils.ParserException, - self.check_parser, self.cmd, arglist, verifylist) - self.assertIn('Argument parse failed', str(ex)) + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + ex = self.assertRaises(exceptions.CommandError, + self.cmd.take_action, + parsed_args) + self.assertIn( + '--os-compute-api-version 2.73 or greater is required', str(ex)) - def test_server_list_with_flavor(self): + def test_server_list_with_locked(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') arglist = [ - '--flavor', self.flavor.id + '--locked' ] verifylist = [ - ('flavor', self.flavor.id) + ('locked', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.get.has_calls(self.flavor.id) - - self.search_opts['flavor'] = self.flavor.id + self.search_opts['locked'] = True self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) - def test_server_list_with_changes_since(self): + def test_server_list_with_unlocked_v273(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') arglist = [ - '--changes-since', '2016-03-04T06:27:59Z', - '--deleted' + '--unlocked' ] verifylist = [ - ('changes_since', '2016-03-04T06:27:59Z'), - ('deleted', True), + ('unlocked', True) ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['changes-since'] = '2016-03-04T06:27:59Z' - self.search_opts['deleted'] = True + self.search_opts['locked'] = False self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) - @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) - def test_server_list_with_invalid_changes_since(self, mock_parse_isotime): + def test_server_list_with_locked_and_unlocked(self): + self.app.client_manager.compute.api_version = \ + api_versions.APIVersion('2.73') arglist = [ - '--changes-since', 'Invalid time value', + '--locked', + '--unlocked' ] verifylist = [ - ('changes_since', 'Invalid time value'), + ('locked', True), + ('unlocked', True) ] - parsed_args = self.check_parser(self.cmd, arglist, verifylist) - try: - self.cmd.take_action(parsed_args) - self.fail('CommandError should be raised.') - except exceptions.CommandError as e: - self.assertEqual('Invalid changes-since value: Invalid time ' - 'value', str(e)) - mock_parse_isotime.assert_called_once_with( - 'Invalid time value' - ) + ex = self.assertRaises( + utils.ParserException, + self.check_parser, self.cmd, arglist, verifylist) + self.assertIn('Argument parse failed', str(ex)) - def test_server_list_v266_with_changes_before(self): + def test_server_list_with_changes_before(self): self.app.client_manager.compute.api_version = ( api_versions.APIVersion('2.66')) arglist = [ @@ -2759,13 +2844,14 @@ class TestServerList(TestServer): self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' self.search_opts['deleted'] = True + self.servers_mock.list.assert_called_with(**self.kwargs) - self.assertEqual(self.columns, columns) - self.assertEqual(tuple(self.data), tuple(data)) + self.assertItemsEqual(self.columns, columns) + self.assertItemsEqual(self.data, tuple(data)) @mock.patch.object(timeutils, 'parse_isotime', side_effect=ValueError) - def test_server_list_v266_with_invalid_changes_before( + def test_server_list_with_invalid_changes_before( self, mock_parse_isotime): self.app.client_manager.compute.api_version = ( api_versions.APIVersion('2.66')) diff --git a/openstackclient/tests/unit/compute/v2/test_server_group.py b/openstackclient/tests/unit/compute/v2/test_server_group.py index 62d929ad..2928b87c 100644 --- a/openstackclient/tests/unit/compute/v2/test_server_group.py +++ b/openstackclient/tests/unit/compute/v2/test_server_group.py @@ -255,10 +255,13 @@ class TestServerGroupList(TestServerGroup): verifylist = [ ('all_projects', False), ('long', False), + ('limit', None), + ('offset', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(False) + + self.server_groups_mock.list.assert_called_once_with() self.assertEqual(self.list_columns, columns) self.assertEqual(self.list_data, tuple(data)) @@ -271,14 +274,49 @@ class TestServerGroupList(TestServerGroup): verifylist = [ ('all_projects', True), ('long', True), + ('limit', None), + ('offset', None), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(True) + self.server_groups_mock.list.assert_called_once_with( + all_projects=True) self.assertEqual(self.list_columns_long, columns) self.assertEqual(self.list_data_long, tuple(data)) + def test_server_group_list_with_limit(self): + arglist = [ + '--limit', '1', + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('limit', 1), + ('offset', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.server_groups_mock.list.assert_called_once_with(limit=1) + + def test_server_group_list_with_offset(self): + arglist = [ + '--offset', '5', + ] + verifylist = [ + ('all_projects', False), + ('long', False), + ('limit', None), + ('offset', 5), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + self.server_groups_mock.list.assert_called_once_with(offset=5) + class TestServerGroupListV264(TestServerGroupV264): @@ -328,7 +366,7 @@ class TestServerGroupListV264(TestServerGroupV264): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(False) + self.server_groups_mock.list.assert_called_once_with() self.assertEqual(self.list_columns, columns) self.assertEqual(self.list_data, tuple(data)) @@ -344,7 +382,8 @@ class TestServerGroupListV264(TestServerGroupV264): ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.server_groups_mock.list.assert_called_once_with(True) + self.server_groups_mock.list.assert_called_once_with( + all_projects=True) self.assertEqual(self.list_columns_long, columns) self.assertEqual(self.list_data_long, tuple(data)) diff --git a/openstackclient/tests/unit/identity/v3/test_project.py b/openstackclient/tests/unit/identity/v3/test_project.py index ad081420..ce799c3a 100644 --- a/openstackclient/tests/unit/identity/v3/test_project.py +++ b/openstackclient/tests/unit/identity/v3/test_project.py @@ -762,6 +762,28 @@ class TestProjectList(TestProject): 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}) + + arglist = [ + '--parent', self.parent.id, + ] + verifylist = [ + ('parent', self.parent.id), + ] + + self.projects_mock.get.return_value = self.parent + + 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.assertEqual(self.columns, columns) + self.assertEqual(self.datalist, tuple(data)) + def test_project_list_sort(self): self.projects_mock.list.return_value = self.projects |
