diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/common/quota.py | 26 | ||||
| -rw-r--r-- | openstackclient/compute/v2/server.py | 192 | ||||
| -rw-r--r-- | openstackclient/network/v2/security_group.py | 34 | ||||
| -rw-r--r-- | openstackclient/tests/functional/network/v2/test_security_group.py | 4 | ||||
| -rw-r--r-- | openstackclient/tests/unit/common/test_quota.py | 92 | ||||
| -rw-r--r-- | openstackclient/tests/unit/compute/v2/test_server.py | 316 | ||||
| -rw-r--r-- | openstackclient/tests/unit/network/v2/fakes.py | 1 | ||||
| -rw-r--r-- | openstackclient/tests/unit/network/v2/test_security_group_network.py | 10 |
8 files changed, 469 insertions, 206 deletions
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 37437344..0eeea812 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -161,6 +161,13 @@ class BaseQuota(object): raise return quota._info + def _network_quota_to_dict(self, network_quota): + if type(network_quota) is not dict: + dict_quota = network_quota.to_dict() + else: + dict_quota = network_quota + return {k: v for k, v in dict_quota.items() if v is not None} + def get_network_quota(self, parsed_args): quota_class = ( parsed_args.quota_class if 'quota_class' in parsed_args else False) @@ -174,13 +181,11 @@ class BaseQuota(object): client = self.app.client_manager.network if default: network_quota = client.get_quota_default(project) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) else: network_quota = client.get_quota(project, details=detail) - if type(network_quota) is not dict: - network_quota = network_quota.to_dict() + network_quota = self._network_quota_to_dict(network_quota) if detail: # NOTE(slaweq): Neutron returns values with key "used" but # Nova for example returns same data with key "in_use" @@ -274,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 8be78049..194ccc8c 100644 --- a/openstackclient/compute/v2/server.py +++ b/openstackclient/compute/v2/server.py @@ -1315,15 +1315,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 = image_client.find_image(parsed_args.image, - ignore_missing=False).id + image_id = image_client.find_image( + parsed_args.image, + ignore_missing=False, + ).id search_opts = { 'reservation_id': parsed_args.reservation_id, @@ -1373,76 +1377,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 @@ -1454,25 +1467,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.get_image(i_id) except Exception: @@ -1485,12 +1502,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: @@ -1514,6 +1536,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: @@ -1522,28 +1545,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/network/v2/security_group.py b/openstackclient/network/v2/security_group.py index 2033af14..aec9c56e 100644 --- a/openstackclient/network/v2/security_group.py +++ b/openstackclient/network/v2/security_group.py @@ -120,6 +120,19 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): metavar='<project>', help=self.enhance_help_neutron(_("Owner's project (name or ID)")) ) + stateful_group = parser.add_mutually_exclusive_group() + stateful_group.add_argument( + "--stateful", + action='store_true', + default=None, + help=_("Security group is stateful (Default)") + ) + stateful_group.add_argument( + "--stateless", + action='store_true', + default=None, + help=_("Security group is stateless") + ) identity_common.add_project_domain_option_to_parser( parser, enhance_help=self.enhance_help_neutron) _tag.add_tag_option_to_parser_for_create( @@ -138,6 +151,10 @@ class CreateSecurityGroup(common.NetworkAndComputeShowOne): attrs = {} attrs['name'] = parsed_args.name attrs['description'] = self._get_description(parsed_args) + if parsed_args.stateful: + attrs['stateful'] = True + if parsed_args.stateless: + attrs['stateful'] = False if parsed_args.project is not None: identity_client = self.app.client_manager.identity project_id = identity_common.find_project( @@ -315,6 +332,19 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): metavar="<description>", help=_("New security group description") ) + stateful_group = parser.add_mutually_exclusive_group() + stateful_group.add_argument( + "--stateful", + action='store_true', + default=None, + help=_("Security group is stateful (Default)") + ) + stateful_group.add_argument( + "--stateless", + action='store_true', + default=None, + help=_("Security group is stateless") + ) return parser def update_parser_network(self, parser): @@ -331,6 +361,10 @@ class SetSecurityGroup(common.NetworkAndComputeCommand): attrs['name'] = parsed_args.name if parsed_args.description is not None: attrs['description'] = parsed_args.description + if parsed_args.stateful: + attrs['stateful'] = True + if parsed_args.stateless: + attrs['stateful'] = False # NOTE(rtheis): Previous behavior did not raise a CommandError # if there were no updates. Maintain this behavior and issue # the update. diff --git a/openstackclient/tests/functional/network/v2/test_security_group.py b/openstackclient/tests/functional/network/v2/test_security_group.py index 8ae24b72..d46f8db7 100644 --- a/openstackclient/tests/functional/network/v2/test_security_group.py +++ b/openstackclient/tests/functional/network/v2/test_security_group.py @@ -42,7 +42,7 @@ class SecurityGroupTests(common.NetworkTests): def test_security_group_set(self): other_name = uuid.uuid4().hex raw_output = self.openstack( - 'security group set --description NSA --name ' + + 'security group set --description NSA --stateless --name ' + other_name + ' ' + self.NAME ) self.assertEqual('', raw_output) @@ -50,8 +50,10 @@ class SecurityGroupTests(common.NetworkTests): cmd_output = json.loads(self.openstack( 'security group show -f json ' + other_name)) self.assertEqual('NSA', cmd_output['description']) + self.assertFalse(cmd_output['stateful']) def test_security_group_show(self): cmd_output = json.loads(self.openstack( 'security group show -f json ' + self.NAME)) self.assertEqual(self.NAME, cmd_output['name']) + self.assertTrue(cmd_output['stateful']) diff --git a/openstackclient/tests/unit/common/test_quota.py b/openstackclient/tests/unit/common/test_quota.py index bd59ca77..a84726a9 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): @@ -968,3 +1037,26 @@ class TestQuotaShow(TestQuota): identity_fakes.project_id, details=False ) self.assertNotCalled(self.network.get_quota_default) + + def test_network_quota_show_remove_empty(self): + arglist = [ + self.projects[0].name, + ] + verifylist = [ + ('project', self.projects[0].name), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # First check that all regular values are returned + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA), len(result)) + + # set 1 of the values to None, and verify it is not returned + orig_get_quota = self.network.get_quota + network_quotas = copy.copy(network_fakes.QUOTA) + network_quotas['healthmonitor'] = None + self.network.get_quota = mock.Mock(return_value=network_quotas) + result = self.cmd.get_network_quota(parsed_args) + self.assertEqual(len(network_fakes.QUOTA) - 1, len(result)) + # Go back to default mock + self.network.get_quota = orig_get_quota diff --git a/openstackclient/tests/unit/compute/v2/test_server.py b/openstackclient/tests/unit/compute/v2/test_server.py index 8ec8217d..c715e814 100644 --- a/openstackclient/tests/unit/compute/v2/test_server.py +++ b/openstackclient/tests/unit/compute/v2/test_server.py @@ -2498,7 +2498,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 = ( @@ -2526,7 +2526,7 @@ class TestServerList(TestServer): ) def setUp(self): - super(TestServerList, self).setUp() + super(_TestServerList, self).setUp() self.search_opts = { 'reservation_id': None, @@ -2584,10 +2584,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.return_value = [ @@ -2602,8 +2603,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, @@ -2611,34 +2612,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 = [] @@ -2659,7 +2634,7 @@ class TestServerList(TestServer): self.assertFalse(self.flavors_mock.get.call_count) self.assertFalse(self.get_image_mock.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 = [] @@ -2678,9 +2653,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', ] @@ -2691,12 +2685,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', ] @@ -2710,9 +2715,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', ] @@ -2726,7 +2743,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 = [ @@ -2748,7 +2765,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): @@ -2769,145 +2786,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.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 = [ @@ -2924,13 +3009,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/network/v2/fakes.py b/openstackclient/tests/unit/network/v2/fakes.py index 6c16a8d5..a7d81a65 100644 --- a/openstackclient/tests/unit/network/v2/fakes.py +++ b/openstackclient/tests/unit/network/v2/fakes.py @@ -1227,6 +1227,7 @@ class FakeSecurityGroup(object): 'id': 'security-group-id-' + uuid.uuid4().hex, 'name': 'security-group-name-' + uuid.uuid4().hex, 'description': 'security-group-description-' + uuid.uuid4().hex, + 'stateful': True, 'project_id': 'project-id-' + uuid.uuid4().hex, 'security_group_rules': [], 'tags': [] diff --git a/openstackclient/tests/unit/network/v2/test_security_group_network.py b/openstackclient/tests/unit/network/v2/test_security_group_network.py index 67908fa8..7c1d7fb6 100644 --- a/openstackclient/tests/unit/network/v2/test_security_group_network.py +++ b/openstackclient/tests/unit/network/v2/test_security_group_network.py @@ -49,6 +49,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'stateful', 'tags', ) @@ -58,6 +59,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.name, _security_group.project_id, security_group.NetworkSecurityGroupRulesColumn([]), + _security_group.stateful, _security_group.tags, ) @@ -101,6 +103,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): '--description', self._security_group.description, '--project', self.project.name, '--project-domain', self.domain.name, + '--stateful', self._security_group.name, ] verifylist = [ @@ -108,6 +111,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): ('name', self._security_group.name), ('project', self.project.name), ('project_domain', self.domain.name), + ('stateful', self._security_group.stateful), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -115,6 +119,7 @@ class TestCreateSecurityGroupNetwork(TestSecurityGroupNetwork): self.network.create_security_group.assert_called_once_with(**{ 'description': self._security_group.description, + 'stateful': self._security_group.stateful, 'name': self._security_group.name, 'tenant_id': self.project.id, }) @@ -421,11 +426,13 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): arglist = [ '--name', new_name, '--description', new_description, + '--stateful', self._security_group.name, ] verifylist = [ ('description', new_description), ('group', self._security_group.name), + ('stateful', self._security_group.stateful), ('name', new_name), ] @@ -435,6 +442,7 @@ class TestSetSecurityGroupNetwork(TestSecurityGroupNetwork): attrs = { 'description': new_description, 'name': new_name, + 'stateful': True, } self.network.update_security_group.assert_called_once_with( self._security_group, @@ -489,6 +497,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): 'name', 'project_id', 'rules', + 'stateful', 'tags', ) @@ -499,6 +508,7 @@ class TestShowSecurityGroupNetwork(TestSecurityGroupNetwork): _security_group.project_id, security_group.NetworkSecurityGroupRulesColumn( [_security_group_rule._info]), + _security_group.stateful, _security_group.tags, ) |
