diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index 609faf5aad..f3315b9dc2 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -66,6 +66,28 @@ class PowerStateColumn(cliff_columns.FormattableColumn): return 'N/A' +class AddressesColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of a server's addresses.""" + + def human_readable(self): + try: + return utils.format_dict_of_list({ + k: [i['addr'] for i in v if 'addr' in i] + for k, v in self._value.items()}) + except Exception: + return 'N/A' + + +class HostColumn(cliff_columns.FormattableColumn): + """Generate a formatted string of a hostname.""" + + def human_readable(self): + if self._value is None: + return '' + + return self._value + + def _get_ip_address(addresses, address_type, ip_address_family): # Old style addresses if address_type in addresses: @@ -2287,7 +2309,7 @@ class ListServer(command.Lister): return parser def take_action(self, parsed_args): - compute_client = self.app.client_manager.compute + compute_client = self.app.client_manager.sdk_connection.compute identity_client = self.app.client_manager.identity image_client = self.app.client_manager.image @@ -2312,10 +2334,11 @@ class ListServer(command.Lister): # flavor name is given, map it to ID. flavor_id = None if parsed_args.flavor: - flavor_id = utils.find_resource( - compute_client.flavors, - parsed_args.flavor, - ).id + flavor = compute_client.find_flavor(parsed_args.flavor) + if flavor is None: + msg = _('Unable to find flavor: %s') % parsed_args.flavor + raise exceptions.CommandError(msg) + flavor_id = flavor.id # Nova only supports list servers searching by image ID. So if a # image name is given, map it to ID. @@ -2331,19 +2354,21 @@ class ListServer(command.Lister): 'ip': parsed_args.ip, 'ip6': parsed_args.ip6, 'name': parsed_args.name, - 'instance_name': parsed_args.instance_name, 'status': parsed_args.status, 'flavor': flavor_id, 'image': image_id, 'host': parsed_args.host, - 'tenant_id': project_id, - 'all_tenants': parsed_args.all_projects, + 'project_id': project_id, + 'all_projects': parsed_args.all_projects, 'user_id': user_id, 'deleted': parsed_args.deleted, 'changes-before': parsed_args.changes_before, 'changes-since': parsed_args.changes_since, } + if parsed_args.instance_name is not None: + search_opts['instance_name'] = parsed_args.instance_name + if parsed_args.availability_zone: search_opts['availability_zone'] = parsed_args.availability_zone @@ -2375,7 +2400,7 @@ class ListServer(command.Lister): search_opts['power_state'] = power_state if parsed_args.tags: - if compute_client.api_version < api_versions.APIVersion('2.26'): + if not sdk_utils.supports_microversion(compute_client, '2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --tag option' @@ -2385,7 +2410,7 @@ class ListServer(command.Lister): search_opts['tags'] = ','.join(parsed_args.tags) if parsed_args.not_tags: - if compute_client.api_version < api_versions.APIVersion('2.26'): + if not sdk_utils.supports_microversion(compute_client, '2.26'): msg = _( '--os-compute-api-version 2.26 or greater is required to ' 'support the --not-tag option' @@ -2395,7 +2420,7 @@ class ListServer(command.Lister): search_opts['not-tags'] = ','.join(parsed_args.not_tags) if parsed_args.locked: - if compute_client.api_version < api_versions.APIVersion('2.73'): + if not sdk_utils.supports_microversion(compute_client, '2.73'): msg = _( '--os-compute-api-version 2.73 or greater is required to ' 'support the --locked option' @@ -2404,7 +2429,7 @@ class ListServer(command.Lister): search_opts['locked'] = True elif parsed_args.unlocked: - if compute_client.api_version < api_versions.APIVersion('2.73'): + if not sdk_utils.supports_microversion(compute_client, '2.73'): msg = _( '--os-compute-api-version 2.73 or greater is required to ' 'support the --unlocked option' @@ -2413,10 +2438,14 @@ class ListServer(command.Lister): search_opts['locked'] = False + if parsed_args.limit is not None: + search_opts['limit'] = parsed_args.limit + search_opts['paginated'] = False + LOG.debug('search options: %s', search_opts) if search_opts['changes-before']: - if compute_client.api_version < api_versions.APIVersion('2.66'): + if not sdk_utils.supports_microversion(compute_client, '2.66'): msg = _('--os-compute-api-version 2.66 or later is required') raise exceptions.CommandError(msg) @@ -2450,15 +2479,15 @@ class ListServer(command.Lister): if parsed_args.long: columns += ( - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', + 'task_state', + 'power_state', ) column_headers += ( 'Task State', 'Power State', ) - columns += ('networks',) + columns += ('addresses',) column_headers += ('Networks',) if parsed_args.long: @@ -2480,7 +2509,7 @@ class ListServer(command.Lister): # microversion 2.47 puts the embedded flavor into the server response # body but omits the id, so if not present we just expose the original # flavor name in the output - if compute_client.api_version >= api_versions.APIVersion('2.47'): + if sdk_utils.supports_microversion(compute_client, '2.47'): columns += ('flavor_name',) column_headers += ('Flavor',) else: @@ -2502,8 +2531,8 @@ class ListServer(command.Lister): if parsed_args.long: columns += ( - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', + 'availability_zone', + 'hypervisor_hostname', 'metadata', ) column_headers += ( @@ -2512,40 +2541,38 @@ class ListServer(command.Lister): 'Properties', ) - marker_id = None - # support for additional columns if parsed_args.columns: for c in parsed_args.columns: if c in ('Project ID', 'project_id'): - columns += ('tenant_id',) + columns += ('project_id',) column_headers += ('Project ID',) if c in ('User ID', 'user_id'): columns += ('user_id',) column_headers += ('User ID',) if c in ('Created At', 'created_at'): - columns += ('created',) + columns += ('created_at',) column_headers += ('Created At',) if c in ('Security Groups', 'security_groups'): columns += ('security_groups_name',) column_headers += ('Security Groups',) if c in ("Task State", "task_state"): - columns += ('OS-EXT-STS:task_state',) + columns += ('task_state',) column_headers += ('Task State',) if c in ("Power State", "power_state"): - columns += ('OS-EXT-STS:power_state',) + columns += ('power_state',) column_headers += ('Power State',) if c in ("Image ID", "image_id"): columns += ('Image ID',) column_headers += ('Image ID',) if c in ("Flavor ID", "flavor_id"): - columns += ('Flavor ID',) + columns += ('flavor_id',) column_headers += ('Flavor ID',) if c in ('Availability Zone', "availability_zone"): - columns += ('OS-EXT-AZ:availability_zone',) + columns += ('availability_zone',) column_headers += ('Availability Zone',) if c in ('Host', "host"): - columns += ('OS-EXT-SRV-ATTR:host',) + columns += ('hypervisor_hostname',) column_headers += ('Host',) if c in ('Properties', "properties"): columns += ('Metadata',) @@ -2555,7 +2582,7 @@ class ListServer(command.Lister): column_headers = tuple(column_headers) columns = tuple(columns) - if parsed_args.marker: + if parsed_args.marker is not None: # Check if both "--marker" and "--deleted" are used. # In that scenario a lookup is not needed as the marker # needs to be an ID, because find_resource does not @@ -2563,16 +2590,10 @@ class ListServer(command.Lister): if parsed_args.deleted: marker_id = parsed_args.marker else: - marker_id = utils.find_resource( - compute_client.servers, - parsed_args.marker, - ).id + marker_id = compute_client.find_server(parsed_args.marker).id + search_opts['marker'] = marker_id - data = compute_client.servers.list( - search_opts=search_opts, - marker=marker_id, - limit=parsed_args.limit, - ) + data = list(compute_client.servers(**search_opts)) images = {} flavors = {} @@ -2627,12 +2648,12 @@ class ListServer(command.Lister): # "Flavor Name" is not crucial, so we swallow any # exceptions try: - flavors[f_id] = compute_client.flavors.get(f_id) + flavors[f_id] = compute_client.find_flavor(f_id) except Exception: pass else: try: - flavors_list = compute_client.flavors.list(is_public=None) + flavors_list = compute_client.flavors(is_public=None) for i in flavors_list: flavors[i.id] = i except Exception: @@ -2641,7 +2662,7 @@ class ListServer(command.Lister): # Populate image_name, image_id, flavor_name and flavor_id attributes # of server objects so that we can display those columns. for s in data: - if compute_client.api_version >= api_versions.APIVersion('2.69'): + if sdk_utils.supports_microversion(compute_client, '2.69'): # NOTE(tssurya): From 2.69, we will have the keys 'flavor' # and 'image' missing in the server response during # infrastructure failure situations. @@ -2650,7 +2671,7 @@ class ListServer(command.Lister): if not hasattr(s, 'image') or not hasattr(s, 'flavor'): continue - if 'id' in s.image: + if 'id' in s.image and s.image.id is not None: image = images.get(s.image['id']) if image: s.image_name = image.name @@ -2663,7 +2684,7 @@ class ListServer(command.Lister): s.image_name = IMAGE_STRING_FOR_BFV s.image_id = IMAGE_STRING_FOR_BFV - if compute_client.api_version < api_versions.APIVersion('2.47'): + if not sdk_utils.supports_microversion(compute_client, '2.47'): flavor = flavors.get(s.flavor['id']) if flavor: s.flavor_name = flavor.name @@ -2673,7 +2694,7 @@ class ListServer(command.Lister): # Add a list with security group name as attribute for s in data: - if hasattr(s, 'security_groups'): + if hasattr(s, 'security_groups') and s.security_groups is not None: s.security_groups_name = [x["name"] for x in s.security_groups] else: s.security_groups_name = [] @@ -2686,10 +2707,10 @@ class ListServer(command.Lister): # it's on, providing useful information to a user in this # situation. if ( - compute_client.api_version >= api_versions.APIVersion('2.16') and + sdk_utils.supports_microversion(compute_client, '2.16') and parsed_args.long ): - if any([hasattr(s, 'host_status') for s in data]): + if any([s.host_status is not None for s in data]): columns += ('Host Status',) column_headers += ('Host Status',) @@ -2699,16 +2720,17 @@ class ListServer(command.Lister): utils.get_item_properties( s, columns, mixed_case_fields=( - 'OS-EXT-STS:task_state', - 'OS-EXT-STS:power_state', - 'OS-EXT-AZ:availability_zone', - 'OS-EXT-SRV-ATTR:host', + 'task_state', + 'power_state', + 'availability_zone', + 'host', ), formatters={ - 'OS-EXT-STS:power_state': PowerStateColumn, - 'networks': format_columns.DictListColumn, + 'power_state': PowerStateColumn, + 'addresses': AddressesColumn, 'metadata': format_columns.DictColumn, 'security_groups_name': format_columns.ListColumn, + 'hypervisor_hostname': HostColumn, }, ) for s in data ), diff --git a/openstackclient/tests/functional/compute/v2/test_server.py b/openstackclient/tests/functional/compute/v2/test_server.py index 0558ef6213..8c62fa9d45 100644 --- a/openstackclient/tests/functional/compute/v2/test_server.py +++ b/openstackclient/tests/functional/compute/v2/test_server.py @@ -105,7 +105,7 @@ class ServerTests(common.ComputeTestCase): 'server list -f json --deleted --marker ' + name2 )) except exceptions.CommandFailed as e: - self.assertIn('marker [%s] not found (HTTP 400)' % (name2), + self.assertIn('marker [%s] not found' % (name2), e.stderr.decode('utf-8')) def test_server_list_with_changes_before(self): diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index f59c954a3b..45aff6a370 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -117,6 +117,21 @@ class TestServer(compute_fakes.TestComputev2): # Set object methods to be tested. Could be overwritten in subclass. self.methods = {} + patcher = mock.patch.object( + sdk_utils, 'supports_microversion', return_value=True) + self.addCleanup(patcher.stop) + self.supports_microversion_mock = patcher.start() + self._set_mock_microversion( + self.app.client_manager.compute.api_version.get_string()) + + def _set_mock_microversion(self, mock_v): + """Set a specific microversion for the mock supports_microversion().""" + self.supports_microversion_mock.reset_mock(return_value=True) + + self.supports_microversion_mock.side_effect = ( + lambda _, v: + api_versions.APIVersion(v) <= api_versions.APIVersion(mock_v)) + def setup_servers_mock(self, count): # If we are creating more than one server, make one of them # boot-from-volume @@ -4448,32 +4463,25 @@ class _TestServerList(TestServer): def setUp(self): super(_TestServerList, self).setUp() - self.search_opts = { + # Default params of the core function of the command in the case of no + # commandline option specified. + self.kwargs = { 'reservation_id': None, 'ip': None, 'ip6': None, 'name': None, - 'instance_name': None, 'status': None, 'flavor': None, 'image': None, 'host': None, - 'tenant_id': None, - 'all_tenants': False, + 'project_id': None, + 'all_projects': False, 'user_id': None, 'deleted': False, 'changes-since': None, 'changes-before': None, } - # Default params of the core function of the command in the case of no - # commandline option specified. - self.kwargs = { - 'search_opts': self.search_opts, - 'marker': None, - 'limit': None, - } - # The fake servers' attributes. Use the original attributes names in # nova, not the ones printed by "server list" command. self.attrs = { @@ -4488,10 +4496,6 @@ class _TestServerList(TestServer): 'Metadata': format_columns.DictColumn({}), } - # The servers to be listed. - self.servers = self.setup_servers_mock(3) - self.servers_mock.list.return_value = self.servers - self.image = image_fakes.create_one_image() # self.images_mock.return_value = [self.image] @@ -4499,7 +4503,12 @@ class _TestServerList(TestServer): self.get_image_mock.return_value = self.image self.flavor = compute_fakes.FakeFlavor.create_one_flavor() - self.flavors_mock.get.return_value = self.flavor + self.sdk_client.find_flavor.return_value = self.flavor + self.attrs['flavor'] = {'original_name': self.flavor.name} + + # The servers to be listed. + self.servers = self.setup_sdk_servers_mock(3) + self.sdk_client.servers.return_value = self.servers # Get the command object to test self.cmd = server.ListServer(self.app, None) @@ -4518,7 +4527,7 @@ class TestServerList(_TestServerList): ] Flavor = collections.namedtuple('Flavor', 'id name') - self.flavors_mock.list.return_value = [ + self.sdk_client.flavors.return_value = [ Flavor(id=s.flavor['id'], name=self.flavor.name) for s in self.servers ] @@ -4528,7 +4537,7 @@ class TestServerList(_TestServerList): s.id, s.name, s.status, - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, @@ -4547,9 +4556,9 @@ class TestServerList(_TestServerList): columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_called() - self.flavors_mock.list.assert_called() + self.sdk_client.flavors.assert_called() # we did not pass image or flavor, so gets on those must be absent self.assertFalse(self.flavors_mock.get.call_count) self.assertFalse(self.get_image_mock.call_count) @@ -4564,14 +4573,14 @@ class TestServerList(_TestServerList): ('deleted', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) - self.servers_mock.list.return_value = [] + self.sdk_client.servers.return_value = [] self.data = () columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_not_called() - self.flavors_mock.list.assert_not_called() + self.sdk_client.flavors.assert_not_called() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4581,19 +4590,19 @@ class TestServerList(_TestServerList): s.id, s.name, s.status, - getattr(s, 'OS-EXT-STS:task_state'), + getattr(s, 'task_state'), server.PowerStateColumn( - getattr(s, 'OS-EXT-STS:power_state') + getattr(s, 'power_state') ), - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - s.Metadata, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), ) for s in self.servers ) arglist = [ @@ -4606,12 +4615,12 @@ class TestServerList(_TestServerList): 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.sdk_client.servers.assert_called_with(**self.kwargs) image_ids = {s.image['id'] for s in self.servers if s.image} self.images_mock.assert_called_once_with( id=f'in:{",".join(image_ids)}', ) - self.flavors_mock.list.assert_called_once_with(is_public=None) + self.sdk_client.flavors.assert_called_once_with(is_public=None) self.assertEqual(self.columns_long, columns) self.assertEqual(self.data, tuple(data)) @@ -4637,7 +4646,7 @@ class TestServerList(_TestServerList): columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertIn('Project ID', columns) self.assertIn('User ID', columns) self.assertIn('Created At', columns) @@ -4656,7 +4665,7 @@ class TestServerList(_TestServerList): s.id, s.name, s.status, - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, s.flavor['id'] @@ -4674,9 +4683,9 @@ class TestServerList(_TestServerList): columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_not_called() - self.flavors_mock.list.assert_not_called() + self.sdk_client.flavors.assert_not_called() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4686,7 +4695,7 @@ class TestServerList(_TestServerList): s.id, s.name, s.status, - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, s.flavor['id'] @@ -4704,9 +4713,9 @@ class TestServerList(_TestServerList): columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_not_called() - self.flavors_mock.list.assert_not_called() + self.sdk_client.flavors.assert_not_called() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4723,11 +4732,11 @@ class TestServerList(_TestServerList): columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_not_called() - self.flavors_mock.list.assert_not_called() + self.sdk_client.flavors.assert_not_called() self.get_image_mock.assert_called() - self.flavors_mock.get.assert_called() + self.sdk_client.find_flavor.assert_called() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4747,10 +4756,10 @@ class TestServerList(_TestServerList): self.find_image_mock.assert_called_with(self.image.id, ignore_missing=False) - self.search_opts['image'] = self.image.id - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['image'] = self.image.id + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_not_called() - self.flavors_mock.list.assert_called_once() + self.sdk_client.flavors.assert_called_once() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4767,12 +4776,13 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.flavors_mock.get.assert_has_calls([mock.call(self.flavor.id)]) + self.sdk_client.find_flavor.assert_has_calls( + [mock.call(self.flavor.id)]) - self.search_opts['flavor'] = self.flavor.id - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['flavor'] = self.flavor.id + self.sdk_client.servers.assert_called_with(**self.kwargs) self.images_mock.assert_called_once() - self.flavors_mock.list.assert_not_called() + self.sdk_client.flavors.assert_not_called() self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4791,9 +4801,9 @@ class TestServerList(_TestServerList): 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.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['changes-since'] = '2016-03-04T06:27:59Z' + self.kwargs['deleted'] = True + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) @@ -4820,8 +4830,7 @@ class TestServerList(_TestServerList): ) def test_server_list_with_tag(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.26') + self._set_mock_microversion('2.26') arglist = [ '--tag', 'tag1', @@ -4834,16 +4843,15 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['tags'] = 'tag1,tag2' + self.kwargs['tags'] = 'tag1,tag2' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) def test_server_list_with_tag_pre_v225(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.25') + self._set_mock_microversion('2.25') arglist = [ '--tag', 'tag1', @@ -4863,9 +4871,7 @@ class TestServerList(_TestServerList): str(ex)) def test_server_list_with_not_tag(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.26') - + self._set_mock_microversion('2.26') arglist = [ '--not-tag', 'tag1', '--not-tag', 'tag2', @@ -4877,16 +4883,15 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['not-tags'] = 'tag1,tag2' + self.kwargs['not-tags'] = 'tag1,tag2' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(self.data, tuple(data)) def test_server_list_with_not_tag_pre_v226(self): - self.app.client_manager.compute.api_version = api_versions.APIVersion( - '2.25') + self._set_mock_microversion('2.25') arglist = [ '--not-tag', 'tag1', @@ -4916,8 +4921,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['availability_zone'] = 'test-az' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['availability_zone'] = 'test-az' + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -4932,8 +4937,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['key_name'] = 'test-key' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['key_name'] = 'test-key' + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -4948,8 +4953,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['config_drive'] = True - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['config_drive'] = True + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -4964,8 +4969,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['config_drive'] = False - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['config_drive'] = False + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -4980,8 +4985,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['progress'] = '100' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['progress'] = '100' + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -5005,8 +5010,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['vm_state'] = 'active' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['vm_state'] = 'active' + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -5021,8 +5026,8 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['task_state'] = 'deleting' - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['task_state'] = 'deleting' + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) @@ -5037,33 +5042,31 @@ class TestServerList(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['power_state'] = 1 - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['power_state'] = 1 + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns, columns) self.assertEqual(tuple(self.data), tuple(data)) def test_server_list_long_with_host_status_v216(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.16') - + self._set_mock_microversion('2.16') self.data1 = tuple( ( s.id, s.name, s.status, - getattr(s, 'OS-EXT-STS:task_state'), + getattr(s, 'task_state'), server.PowerStateColumn( - getattr(s, 'OS-EXT-STS:power_state') + getattr(s, 'power_state') ), - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - s.Metadata, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), ) for s in self.servers) arglist = [ @@ -5078,18 +5081,18 @@ class TestServerList(_TestServerList): 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.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(self.columns_long, columns) self.assertEqual(tuple(self.data1), tuple(data)) # Next test with host_status in the data -- the column should be # present in this case. - self.servers_mock.reset_mock() + self.sdk_client.servers.reset_mock() self.attrs['host_status'] = 'UP' - servers = self.setup_servers_mock(3) - self.servers_mock.list.return_value = servers + servers = self.setup_sdk_servers_mock(3) + self.sdk_client.servers.return_value = servers # Make sure the returned image and flavor IDs match the servers. Image = collections.namedtuple('Image', 'id name') @@ -5099,12 +5102,6 @@ class TestServerList(_TestServerList): for s in servers if s.image ] - Flavor = collections.namedtuple('Flavor', 'id name') - self.flavors_mock.list.return_value = [ - Flavor(id=s.flavor['id'], name=self.flavor.name) - for s in servers - ] - # Add the expected host_status column and data. columns_long = self.columns_long + ('Host Status',) self.data2 = tuple( @@ -5112,25 +5109,25 @@ class TestServerList(_TestServerList): s.id, s.name, s.status, - getattr(s, 'OS-EXT-STS:task_state'), + getattr(s, 'task_state'), server.PowerStateColumn( - getattr(s, 'OS-EXT-STS:power_state') + getattr(s, 'power_state') ), - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, s.image['id'] if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, s.flavor['id'], - getattr(s, 'OS-EXT-AZ:availability_zone'), - getattr(s, 'OS-EXT-SRV-ATTR:host'), - s.Metadata, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), s.host_status, ) for s in servers) columns, data = self.cmd.take_action(parsed_args) - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertEqual(columns_long, columns) self.assertEqual(tuple(self.data2), tuple(data)) @@ -5178,8 +5175,8 @@ class TestServerListV273(_TestServerList): } # The servers to be listed. - self.servers = self.setup_servers_mock(3) - self.servers_mock.list.return_value = self.servers + self.servers = self.setup_sdk_servers_mock(3) + self.sdk_client.servers.return_value = self.servers Image = collections.namedtuple('Image', 'id name') self.images_mock.return_value = [ @@ -5190,14 +5187,14 @@ class TestServerListV273(_TestServerList): # The flavor information is embedded, so now reason for this to be # called - self.flavors_mock.list = mock.NonCallableMock() + self.sdk_client.flavors = mock.NonCallableMock() self.data = tuple( ( s.id, s.name, s.status, - format_columns.DictListColumn(s.networks), + server.AddressesColumn(s.addresses), # Image will be an empty string if boot-from-volume self.image.name if s.image else server.IMAGE_STRING_FOR_BFV, self.flavor.name, @@ -5221,8 +5218,7 @@ class TestServerListV273(_TestServerList): def test_server_list_with_locked(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') + self._set_mock_microversion('2.73') arglist = [ '--locked' ] @@ -5233,16 +5229,15 @@ class TestServerListV273(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['locked'] = True - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['locked'] = True + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, tuple(data)) def test_server_list_with_unlocked_v273(self): + self._set_mock_microversion('2.73') - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') arglist = [ '--unlocked' ] @@ -5253,16 +5248,15 @@ class TestServerListV273(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['locked'] = False - self.servers_mock.list.assert_called_with(**self.kwargs) + self.kwargs['locked'] = False + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, tuple(data)) def test_server_list_with_locked_and_unlocked(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.73') + self._set_mock_microversion('2.73') arglist = [ '--locked', '--unlocked' @@ -5278,8 +5272,7 @@ class TestServerListV273(_TestServerList): self.assertIn('Argument parse failed', str(ex)) def test_server_list_with_changes_before(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) + self._set_mock_microversion('2.66') arglist = [ '--changes-before', '2016-03-05T06:27:59Z', '--deleted' @@ -5292,10 +5285,10 @@ class TestServerListV273(_TestServerList): parsed_args = self.check_parser(self.cmd, arglist, verifylist) columns, data = self.cmd.take_action(parsed_args) - self.search_opts['changes-before'] = '2016-03-05T06:27:59Z' - self.search_opts['deleted'] = True + self.kwargs['changes-before'] = '2016-03-05T06:27:59Z' + self.kwargs['deleted'] = True - self.servers_mock.list.assert_called_with(**self.kwargs) + self.sdk_client.servers.assert_called_with(**self.kwargs) self.assertCountEqual(self.columns, columns) self.assertCountEqual(self.data, tuple(data)) @@ -5303,9 +5296,7 @@ class TestServerListV273(_TestServerList): @mock.patch.object(iso8601, 'parse_date', side_effect=iso8601.ParseError) def test_server_list_with_invalid_changes_before( self, mock_parse_isotime): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.66')) - + self._set_mock_microversion('2.66') arglist = [ '--changes-before', 'Invalid time value', ] @@ -5325,8 +5316,7 @@ class TestServerListV273(_TestServerList): ) def test_server_with_changes_before_pre_v266(self): - self.app.client_manager.compute.api_version = ( - api_versions.APIVersion('2.65')) + self._set_mock_microversion('2.65') arglist = [ '--changes-before', '2016-03-05T06:27:59Z', @@ -5344,8 +5334,7 @@ class TestServerListV273(_TestServerList): parsed_args) def test_server_list_v269_with_partial_constructs(self): - self.app.client_manager.compute.api_version = \ - api_versions.APIVersion('2.69') + self._set_mock_microversion('2.69') arglist = [] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -5371,10 +5360,10 @@ class TestServerListV273(_TestServerList): # it will fail at formatting the networks info later on. "networks": {} } - server = compute_fakes.fakes.FakeResource( + fake_server = compute_fakes.fakes.FakeResource( info=server_dict, ) - self.servers.append(server) + self.servers.append(fake_server) columns, data = self.cmd.take_action(parsed_args) # get the first three servers out since our interest is in the partial # server. @@ -5384,7 +5373,7 @@ class TestServerListV273(_TestServerList): partial_server = next(data) expected_row = ( 'server-id-95a56bfc4xxxxxx28d7e418bfd97813a', '', - 'UNKNOWN', format_columns.DictListColumn({}), '', '') + 'UNKNOWN', server.AddressesColumn(''), '', '') self.assertEqual(expected_row, partial_server) diff --git a/releasenotes/notes/nova-gaps-server-list-to-sdk-88c8bfc10a9e3032.yaml b/releasenotes/notes/nova-gaps-server-list-to-sdk-88c8bfc10a9e3032.yaml new file mode 100644 index 0000000000..01d690e3e9 --- /dev/null +++ b/releasenotes/notes/nova-gaps-server-list-to-sdk-88c8bfc10a9e3032.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + The ``server list`` command now uses the OpenStack SDK instead of the + Python nova bindings.