summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/common/quota.py26
-rw-r--r--openstackclient/compute/v2/server.py192
-rw-r--r--openstackclient/network/v2/security_group.py34
-rw-r--r--openstackclient/tests/functional/network/v2/test_security_group.py4
-rw-r--r--openstackclient/tests/unit/common/test_quota.py92
-rw-r--r--openstackclient/tests/unit/compute/v2/test_server.py316
-rw-r--r--openstackclient/tests/unit/network/v2/fakes.py1
-rw-r--r--openstackclient/tests/unit/network/v2/test_security_group_network.py10
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,
)