diff --git a/openstackclient/compute/v2/server.py b/openstackclient/compute/v2/server.py index e14392a533..28c28b1944 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -183,9 +183,13 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): 'updated_at': 'updated', 'user_data': 'OS-EXT-SRV-ATTR:user_data', 'vm_state': 'OS-EXT-STS:vm_state', - 'pinned_availability_zone': 'pinned_availability_zone', 'scheduler_hints': 'scheduler_hints', } + # NOTE(ratailor): microversion 2.96 introduces + # pinned_availability_zone support + if sdk_utils.supports_microversion(compute_client, '2.96'): + column_map['pinned_availability_zone'] = 'pinned_availability_zone' + # Some columns returned by openstacksdk should not be shown because they're # either irrelevant or duplicates ignored_columns = { @@ -240,6 +244,11 @@ def _prep_server_detail(compute_client, image_client, server, *, refresh=True): if not sdk_utils.supports_microversion(compute_client, '2.100'): info.pop('scheduler_hints', None) + # NOTE(ratailor): microversion 2.96 introduces + # pinned_availability_zone support + if not sdk_utils.supports_microversion(compute_client, '2.96'): + info.pop('pinned_availability_zone', None) + # Convert the image blob to a name image_info = info.get('image', {}) if image_info and any(image_info.values()): @@ -2838,18 +2847,19 @@ class ListServer(command.Lister): if parsed_args.long: columns += ( 'availability_zone', - 'pinned_availability_zone', 'hypervisor_hostname', 'metadata', 'scheduler_hints', ) column_headers += ( 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', ) + if sdk_utils.supports_microversion(compute_client, '2.96'): + columns += ('pinned_availability_zone',) + column_headers += ('Pinned Availability Zone',) if parsed_args.all_projects: columns += ('project_id',) @@ -2887,10 +2897,11 @@ class ListServer(command.Lister): column_headers += ('Availability Zone',) if c in ( 'pinned_availability_zone', - "Pinned Availability Zone", + 'Pinned Availability Zone', ): - columns += ('Pinned Availability Zone',) - column_headers += ('Pinned Availability Zone',) + if sdk_utils.supports_microversion(compute_client, '2.96'): + columns += ('pinned_availability_zone',) + column_headers += ('Pinned Availability Zone',) if c in ('Host', "host"): columns += ('hypervisor_hostname',) column_headers += ('Host',) diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 86951360b3..82205bc181 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -1212,7 +1212,6 @@ class TestServerCreate(TestServer): 'locked', 'locked_reason', 'name', - 'pinned_availability_zone', 'progress', 'project_id', 'properties', @@ -1261,7 +1260,6 @@ class TestServerCreate(TestServer): None, # locked None, # locked_reason self.server.name, - None, # pinned_availability_zone None, # progress None, # project_id format_columns.DictColumn({}), # properties @@ -4583,7 +4581,6 @@ class _TestServerList(TestServer): 'Flavor Name', 'Flavor ID', 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', @@ -4732,7 +4729,6 @@ class TestServerList(_TestServerList): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(None), @@ -4809,8 +4805,6 @@ class TestServerList(_TestServerList): '-c', 'Availability Zone', '-c', - 'Pinned Availability Zone', - '-c', 'Host', '-c', 'Properties', @@ -4835,7 +4829,6 @@ class TestServerList(_TestServerList): self.assertIn('Image ID', columns) self.assertIn('Flavor ID', columns) self.assertIn('Availability Zone', columns) - self.assertIn('Pinned Availability Zone', columns) self.assertIn('Host', columns) self.assertIn('Properties', columns) self.assertIn('Scheduler Hints', columns) @@ -5249,7 +5242,6 @@ class TestServerList(_TestServerList): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(s.scheduler_hints), @@ -5305,7 +5297,6 @@ class TestServerList(_TestServerList): self.flavor.name, s.flavor['id'], getattr(s, 'availability_zone'), - getattr(s, 'pinned_availability_zone', ''), server.HostColumn(getattr(s, 'hypervisor_hostname')), format_columns.DictColumn(s.metadata), format_columns.DictListColumn(s.scheduler_hints), @@ -5343,7 +5334,6 @@ class TestServerListV273(_TestServerList): 'Image ID', 'Flavor', 'Availability Zone', - 'Pinned Availability Zone', 'Host', 'Properties', 'Scheduler Hints', @@ -5541,6 +5531,157 @@ class TestServerListV273(_TestServerList): self.assertEqual(expected_row, partial_server) +class TestServerListV296(_TestServerList): + 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', + 'Scheduler Hints', + 'Pinned Availability Zone', + ) + + def setUp(self): + super().setUp() + self.set_compute_api_version('2.96') + + Image = collections.namedtuple('Image', 'id name') + self.image_client.images.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 + ] + + Flavor = collections.namedtuple('Flavor', 'id name') + self.compute_client.flavors.return_value = [ + Flavor(id=s.flavor['id'], name=self.flavor.name) + for s in self.servers + ] + + self.data = tuple( + ( + s.id, + s.name, + s.status, + 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, + ) + for s in self.servers + ) + + def test_server_list_long_option(self): + self.data = tuple( + ( + s.id, + s.name, + s.status, + getattr(s, 'task_state'), + server.PowerStateColumn(getattr(s, 'power_state')), + 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, + getattr(s, 'availability_zone'), + server.HostColumn(getattr(s, 'hypervisor_hostname')), + format_columns.DictColumn(s.metadata), + format_columns.DictListColumn(None), + getattr(s, 'pinned_availability_zone', ''), + ) + for s in self.servers + ) + arglist = [ + '--long', + ] + verifylist = [ + ('all_projects', False), + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + self.compute_client.servers.assert_called_with(**self.kwargs) + image_ids = {s.image['id'] for s in self.servers if s.image} + self.image_client.images.assert_called_once_with( + id=f'in:{",".join(image_ids)}', + ) + self.compute_client.flavors.assert_called_once_with(is_public=None) + self.assertEqual(self.columns_long, columns) + self.assertEqual(self.data, tuple(data)) + + def test_server_list_column_option(self): + arglist = [ + '-c', + 'Project ID', + '-c', + 'User ID', + '-c', + 'Created At', + '-c', + 'Security Groups', + '-c', + 'Task State', + '-c', + 'Power State', + '-c', + 'Image ID', + '-c', + 'Flavor ID', + '-c', + 'Availability Zone', + '-c', + 'Host', + '-c', + 'Properties', + '-c', + 'Scheduler Hints', + '-c', + 'Pinned Availability Zone', + '--long', + ] + verifylist = [ + ('long', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + self.compute_client.servers.assert_called_with(**self.kwargs) + self.assertIn('Project ID', columns) + self.assertIn('User ID', columns) + self.assertIn('Created At', columns) + self.assertIn('Security Groups', columns) + self.assertIn('Task State', columns) + self.assertIn('Power State', columns) + self.assertIn('Image ID', columns) + self.assertIn('Flavor ID', columns) + self.assertIn('Availability Zone', columns) + self.assertIn('Pinned Availability Zone', columns) + self.assertIn('Host', columns) + self.assertIn('Properties', columns) + self.assertIn('Scheduler Hints', columns) + self.assertCountEqual(columns, set(columns)) + + class TestServerAction(compute_fakes.TestComputev2): def run_method_with_sdk_servers(self, method_name, server_count): servers = compute_fakes.create_servers(count=server_count) @@ -8533,7 +8674,6 @@ class TestServerShow(TestServer): 'locked', 'locked_reason', 'name', - 'pinned_availability_zone', 'progress', 'project_id', 'properties', @@ -8583,7 +8723,6 @@ class TestServerShow(TestServer): None, # locked None, # locked_reason self.server.name, - None, # pinned_availability_zone None, # progress 'tenant-id-xxx', # project_id format_columns.DictColumn({}), # properties @@ -9522,7 +9661,6 @@ class TestServerGeneral(TestServer): 'locked': None, 'locked_reason': None, 'name': _server.name, - 'pinned_availability_zone': None, 'progress': None, 'project_id': 'tenant-id-xxx', 'properties': format_columns.DictColumn({}), @@ -9609,7 +9747,6 @@ class TestServerGeneral(TestServer): 'locked': None, 'locked_reason': None, 'name': _server.name, - 'pinned_availability_zone': None, 'progress': None, 'project_id': 'tenant-id-xxx', 'properties': format_columns.DictColumn({}),