diff options
Diffstat (limited to 'openstackclient')
| -rw-r--r-- | openstackclient/common/quota.py | 3 | ||||
| -rw-r--r-- | openstackclient/identity/v3/role.py | 4 | ||||
| -rw-r--r-- | openstackclient/identity/v3/role_assignment.py | 14 | ||||
| -rw-r--r-- | openstackclient/tests/common/test_quota.py | 89 | ||||
| -rw-r--r-- | openstackclient/tests/compute/v2/fakes.py | 18 | ||||
| -rw-r--r-- | openstackclient/tests/identity/v3/fakes.py | 14 | ||||
| -rw-r--r-- | openstackclient/tests/identity/v3/test_role.py | 16 | ||||
| -rw-r--r-- | openstackclient/tests/identity/v3/test_role_assignment.py | 142 | ||||
| -rw-r--r-- | openstackclient/tests/volume/v2/fakes.py | 6 | ||||
| -rw-r--r-- | openstackclient/tests/volume/v2/test_type.py | 130 | ||||
| -rw-r--r-- | openstackclient/tests/volume/v2/test_volume.py | 214 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume.py | 109 | ||||
| -rw-r--r-- | openstackclient/volume/v2/volume_type.py | 94 |
13 files changed, 808 insertions, 45 deletions
diff --git a/openstackclient/common/quota.py b/openstackclient/common/quota.py index 4963eeac..e79fd7ed 100644 --- a/openstackclient/common/quota.py +++ b/openstackclient/common/quota.py @@ -97,12 +97,13 @@ class SetQuota(command.Command): compute_kwargs = {} for k, v in COMPUTE_QUOTAS.items(): - value = getattr(parsed_args, v, None) + value = getattr(parsed_args, k, None) if value is not None: compute_kwargs[k] = value volume_kwargs = {} for k, v in VOLUME_QUOTAS.items(): + # TODO(jiaxi): Should use k or v needs discuss value = getattr(parsed_args, v, None) if value is not None: if parsed_args.volume_type: diff --git a/openstackclient/identity/v3/role.py b/openstackclient/identity/v3/role.py index 199b7dca..9243639e 100644 --- a/openstackclient/identity/v3/role.py +++ b/openstackclient/identity/v3/role.py @@ -101,9 +101,9 @@ def _process_identity_and_resource_options(parsed_args, kwargs['project'] = common.find_project( identity_client_manager, parsed_args.project, - parsed_args.group_domain, + parsed_args.project_domain, ).id - kwargs['inherited'] = parsed_args.inherited + kwargs['os_inherit_extension_inherited'] = parsed_args.inherited return kwargs diff --git a/openstackclient/identity/v3/role_assignment.py b/openstackclient/identity/v3/role_assignment.py index 92168498..169c6cb9 100644 --- a/openstackclient/identity/v3/role_assignment.py +++ b/openstackclient/identity/v3/role_assignment.py @@ -64,12 +64,12 @@ class ListRoleAssignment(lister.Lister): help='Project to filter (name or ID)', ) common.add_project_domain_option_to_parser(parser) - + common.add_inherited_option_to_parser(parser) return parser def _as_tuple(self, assignment): return (assignment.role, assignment.user, assignment.group, - assignment.project, assignment.domain) + assignment.project, assignment.domain, assignment.inherited) def take_action(self, parsed_args): self.log.debug('take_action(%s)' % parsed_args) @@ -115,14 +115,17 @@ class ListRoleAssignment(lister.Lister): effective = True if parsed_args.effective else False self.log.debug('take_action(%s)' % parsed_args) - columns = ('Role', 'User', 'Group', 'Project', 'Domain') + columns = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + + inherited_to = 'projects' if parsed_args.inherited else None data = identity_client.role_assignments.list( domain=domain, user=user, group=group, project=project, role=role, - effective=effective) + effective=effective, + os_inherit_extension_inherited_to=inherited_to) data_parsed = [] for assignment in data: @@ -139,6 +142,9 @@ class ListRoleAssignment(lister.Lister): assignment.domain = '' assignment.project = '' + inherited = scope.get('OS-INHERIT:inherited_to') == 'projects' + assignment.inherited = inherited + del assignment.scope if hasattr(assignment, 'user'): diff --git a/openstackclient/tests/common/test_quota.py b/openstackclient/tests/common/test_quota.py new file mode 100644 index 00000000..f0013e48 --- /dev/null +++ b/openstackclient/tests/common/test_quota.py @@ -0,0 +1,89 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from openstackclient.common import quota +from openstackclient.tests.compute.v2 import fakes as compute_fakes +from openstackclient.tests import fakes + + +class FakeQuotaResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, keys): + for key in keys: + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + +class TestQuota(compute_fakes.TestComputev2): + + def setUp(self): + super(TestQuota, self).setUp() + self.quotas_mock = self.app.client_manager.compute.quotas + self.quotas_mock.reset_mock() + + +class TestQuotaSet(TestQuota): + + def setUp(self): + super(TestQuotaSet, self).setUp() + + self.quotas_mock.find.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.quotas_mock.update.return_value = FakeQuotaResource( + None, + copy.deepcopy(compute_fakes.QUOTA), + loaded=True, + ) + + self.cmd = quota.SetQuota(self.app, None) + + def test_quota_set(self): + arglist = [ + '--floating-ips', str(compute_fakes.floating_ip_num), + '--fixed-ips', str(compute_fakes.fix_ip_num), + '--injected-files', str(compute_fakes.injected_file_num), + '--key-pairs', str(compute_fakes.key_pair_num), + compute_fakes.project_name, + ] + verifylist = [ + ('floating_ips', compute_fakes.floating_ip_num), + ('fixed_ips', compute_fakes.fix_ip_num), + ('injected_files', compute_fakes.injected_file_num), + ('key_pairs', compute_fakes.key_pair_num), + ('project', compute_fakes.project_name), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + kwargs = { + 'floating_ips': compute_fakes.floating_ip_num, + 'fixed_ips': compute_fakes.fix_ip_num, + 'injected_files': compute_fakes.injected_file_num, + 'key_pairs': compute_fakes.key_pair_num, + } + + self.quotas_mock.update.assert_called_with('project_test', **kwargs) diff --git a/openstackclient/tests/compute/v2/fakes.py b/openstackclient/tests/compute/v2/fakes.py index c18dea7e..e798bd40 100644 --- a/openstackclient/tests/compute/v2/fakes.py +++ b/openstackclient/tests/compute/v2/fakes.py @@ -62,6 +62,22 @@ FLAVOR = { 'vcpus': flavor_vcpus, } +floating_ip_num = 100 +fix_ip_num = 100 +injected_file_num = 100 +key_pair_num = 100 +project_name = 'project_test' +QUOTA = { + 'project': project_name, + 'floating-ips': floating_ip_num, + 'fix-ips': fix_ip_num, + 'injected-files': injected_file_num, + 'key-pairs': key_pair_num, +} + +QUOTA_columns = tuple(sorted(QUOTA)) +QUOTA_data = tuple(QUOTA[x] for x in sorted(QUOTA)) + class FakeComputev2Client(object): def __init__(self, **kwargs): @@ -73,6 +89,8 @@ class FakeComputev2Client(object): self.extensions.resource_class = fakes.FakeResource(None, {}) self.flavors = mock.Mock() self.flavors.resource_class = fakes.FakeResource(None, {}) + self.quotas = mock.Mock() + self.quotas.resource_class = fakes.FakeResource(None, {}) self.auth_token = kwargs['token'] self.management_url = kwargs['endpoint'] diff --git a/openstackclient/tests/identity/v3/fakes.py b/openstackclient/tests/identity/v3/fakes.py index ae7a684c..9c4de9cc 100644 --- a/openstackclient/tests/identity/v3/fakes.py +++ b/openstackclient/tests/identity/v3/fakes.py @@ -313,6 +313,13 @@ ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID = { 'role': {'id': role_id}, } +ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED = { + 'scope': {'project': {'id': project_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_PROJECT_ID_AND_GROUP_ID = { 'scope': {'project': {'id': project_id}}, 'group': {'id': group_id}, @@ -325,6 +332,13 @@ ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID = { 'role': {'id': role_id}, } +ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED = { + 'scope': {'domain': {'id': domain_id}, + 'OS-INHERIT:inherited_to': 'projects'}, + 'user': {'id': user_id}, + 'role': {'id': role_id}, +} + ASSIGNMENT_WITH_DOMAIN_ID_AND_GROUP_ID = { 'scope': {'domain': {'id': domain_id}}, 'group': {'id': group_id}, diff --git a/openstackclient/tests/identity/v3/test_role.py b/openstackclient/tests/identity/v3/test_role.py index 4ff3b95f..4a0ba066 100644 --- a/openstackclient/tests/identity/v3/test_role.py +++ b/openstackclient/tests/identity/v3/test_role.py @@ -123,7 +123,7 @@ class TestRoleAdd(TestRole): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -156,7 +156,7 @@ class TestRoleAdd(TestRole): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -189,7 +189,7 @@ class TestRoleAdd(TestRole): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -222,7 +222,7 @@ class TestRoleAdd(TestRole): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.grant(role, user=, group=, domain=, project=) self.roles_mock.grant.assert_called_with( @@ -598,7 +598,7 @@ class TestRoleRemove(TestRole): kwargs = { 'user': identity_fakes.user_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -631,7 +631,7 @@ class TestRoleRemove(TestRole): kwargs = { 'user': identity_fakes.user_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -665,7 +665,7 @@ class TestRoleRemove(TestRole): kwargs = { 'group': identity_fakes.group_id, 'domain': identity_fakes.domain_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( @@ -698,7 +698,7 @@ class TestRoleRemove(TestRole): kwargs = { 'group': identity_fakes.group_id, 'project': identity_fakes.project_id, - 'inherited': self._is_inheritance_testcase(), + 'os_inherit_extension_inherited': self._is_inheritance_testcase(), } # RoleManager.revoke(role, user=, group=, domain=, project=) self.roles_mock.revoke.assert_called_with( diff --git a/openstackclient/tests/identity/v3/test_role_assignment.py b/openstackclient/tests/identity/v3/test_role_assignment.py index b1ce8b29..9817f53a 100644 --- a/openstackclient/tests/identity/v3/test_role_assignment.py +++ b/openstackclient/tests/identity/v3/test_role_assignment.py @@ -86,21 +86,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, role=None, user=None, - project=None) + project=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -131,6 +134,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -143,21 +147,24 @@ class TestRoleAssignmentList(TestRoleAssignment): group=None, project=None, role=None, - effective=False) + effective=False, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -188,6 +195,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -200,21 +208,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -245,6 +256,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -257,21 +269,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ), (identity_fakes.role_id, '', identity_fakes.group_id, '', - identity_fakes.domain_id + identity_fakes.domain_id, + False ),) self.assertEqual(datalist, tuple(data)) @@ -302,6 +317,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', identity_fakes.project_name), ('role', None), ('effective', False), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -314,21 +330,24 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=False, project=self.projects_mock.get(), role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False ), (identity_fakes.role_id, '', identity_fakes.group_id, identity_fakes.project_id, - '' + '', + False ),) self.assertEqual(datalist, tuple(data)) @@ -357,6 +376,7 @@ class TestRoleAssignmentList(TestRoleAssignment): ('project', None), ('role', None), ('effective', True), + ('inherited', False), ] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -369,20 +389,84 @@ class TestRoleAssignmentList(TestRoleAssignment): effective=True, project=None, role=None, - user=None) + user=None, + os_inherit_extension_inherited_to=None) - collist = ('Role', 'User', 'Group', 'Project', 'Domain') - self.assertEqual(collist, columns) + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) datalist = (( identity_fakes.role_id, identity_fakes.user_id, '', identity_fakes.project_id, - '' + '', + False + ), (identity_fakes.role_id, + identity_fakes.user_id, + '', + '', + identity_fakes.domain_id, + False + ),) + self.assertEqual(tuple(data), datalist) + + def test_role_assignment_list_inherited(self): + + self.role_assignments_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_PROJECT_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + fakes.FakeResource( + None, + copy.deepcopy( + (identity_fakes. + ASSIGNMENT_WITH_DOMAIN_ID_AND_USER_ID_INHERITED)), + loaded=True, + ), + ] + + arglist = ['--inherited'] + verifylist = [ + ('user', None), + ('group', None), + ('domain', None), + ('project', None), + ('role', None), + ('effective', False), + ('inherited', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + columns, data = self.cmd.take_action(parsed_args) + + self.role_assignments_mock.list.assert_called_with( + domain=None, + group=None, + effective=False, + project=None, + role=None, + user=None, + os_inherit_extension_inherited_to='projects') + + collist = ('Role', 'User', 'Group', 'Project', 'Domain', 'Inherited') + self.assertEqual(columns, collist) + datalist = (( + identity_fakes.role_id, + identity_fakes.user_id, + '', + identity_fakes.project_id, + '', + True ), (identity_fakes.role_id, identity_fakes.user_id, '', '', identity_fakes.domain_id, + True ),) self.assertEqual(datalist, tuple(data)) diff --git a/openstackclient/tests/volume/v2/fakes.py b/openstackclient/tests/volume/v2/fakes.py index a95bc94b..0d8c2024 100644 --- a/openstackclient/tests/volume/v2/fakes.py +++ b/openstackclient/tests/volume/v2/fakes.py @@ -15,11 +15,15 @@ import copy import mock +from openstackclient.tests.compute.v2 import fakes as compute_fakes from openstackclient.tests import fakes from openstackclient.tests.identity.v2_0 import fakes as identity_fakes from openstackclient.tests.image.v2 import fakes as image_fakes from openstackclient.tests import utils +volume_attachment_server = copy.deepcopy(compute_fakes.SERVER) +volume_attachment_server['device'] = 'device' + volume_id = "ce26708d-a7f8-4b4b-9861-4a80256615a6" volume_name = "fake_volume" volume_description = "fake description" @@ -34,7 +38,7 @@ volume_metadata = { volume_metadata_str = "Alpha='a', Beta='b', Gamma='g'" volume_snapshot_id = 1 volume_availability_zone = "nova" -volume_attachments = ["fake_attachments"] +volume_attachments = [volume_attachment_server] VOLUME = { "id": volume_id, diff --git a/openstackclient/tests/volume/v2/test_type.py b/openstackclient/tests/volume/v2/test_type.py index c5b27fa5..9a07263b 100644 --- a/openstackclient/tests/volume/v2/test_type.py +++ b/openstackclient/tests/volume/v2/test_type.py @@ -19,6 +19,20 @@ from openstackclient.tests.volume.v2 import fakes as volume_fakes from openstackclient.volume.v2 import volume_type +class FakeTypeResource(fakes.FakeResource): + + _keys = {'property': 'value'} + + def set_keys(self, args): + self._keys.update(args) + + def unset_keys(self, key): + self._keys.pop(key, None) + + def get_keys(self): + return self._keys + + class TestType(volume_fakes.TestVolume): def setUp(self): @@ -184,6 +198,122 @@ class TestTypeShow(TestType): self.assertEqual(volume_fakes.TYPE_FORMATTED_data, data) +class TestTypeSet(TestType): + + def setUp(self): + super(TestTypeSet, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + # Get the command object to test + self.cmd = volume_type.SetVolumeType(self.app, None) + + def test_type_set_name(self): + new_name = 'new_name' + arglist = [ + '--name', new_name, + volume_fakes.type_id, + ] + verifylist = [ + ('name', new_name), + ('description', None), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'name': new_name, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_description(self): + new_desc = 'new_desc' + arglist = [ + '--description', new_desc, + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', new_desc), + ('property', None), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + # Set expected values + kwargs = { + 'description': new_desc, + } + self.types_mock.update.assert_called_with( + volume_fakes.type_id, + **kwargs + ) + + def test_type_set_property(self): + arglist = [ + '--property', 'myprop=myvalue', + volume_fakes.type_id, + ] + verifylist = [ + ('name', None), + ('description', None), + ('property', {'myprop': 'myvalue'}), + ('volume_type', volume_fakes.type_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + self.assertIn('myprop', result) + self.assertEqual('myvalue', result['myprop']) + + +class TestTypeUnset(TestType): + + def setUp(self): + super(TestTypeUnset, self).setUp() + + self.types_mock.get.return_value = FakeTypeResource( + None, + copy.deepcopy(volume_fakes.TYPE), + loaded=True, + ) + + self.cmd = volume_type.UnsetVolumeType(self.app, None) + + def test_type_unset(self): + arglist = [ + '--property', 'property', + volume_fakes.type_id, + ] + verifylist = [ + ('property', 'property'), + ('volume_type', volume_fakes.type_id), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + result = self.types_mock.get.return_value._keys + + self.assertNotIn('property', result) + + class TestTypeDelete(TestType): def setUp(self): super(TestTypeDelete, self).setUp() diff --git a/openstackclient/tests/volume/v2/test_volume.py b/openstackclient/tests/volume/v2/test_volume.py index 4fffefa4..348ae377 100644 --- a/openstackclient/tests/volume/v2/test_volume.py +++ b/openstackclient/tests/volume/v2/test_volume.py @@ -495,6 +495,220 @@ class TestVolumeCreate(TestVolume): self.assertEqual(datalist, data) +class TestVolumeList(TestVolume): + + def setUp(self): + super(TestVolumeList, self).setUp() + + self.volumes_mock.list.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(volume_fakes.VOLUME), + loaded=True, + ), + ] + + self.users_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.USER), + loaded=True, + ), + ] + + self.projects_mock.get.return_value = [ + fakes.FakeResource( + None, + copy.deepcopy(identity_fakes.PROJECT), + loaded=True, + ), + ] + + # Get the command object to test + self.cmd = volume.ListVolume(self.app, None) + + def test_volume_list_no_options(self): + arglist = [] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_all_projects_option(self): + arglist = [ + '--all-projects', + ] + verifylist = [ + ('long', False), + ('all_projects', True), + ('name', None), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_name(self): + arglist = [ + '--name', volume_fakes.volume_name, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', volume_fakes.volume_name), + ('status', None), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_status(self): + arglist = [ + '--status', volume_fakes.volume_status, + ] + verifylist = [ + ('long', False), + ('all_projects', False), + ('name', None), + ('status', volume_fakes.volume_status), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = ( + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Attached to', + ) + self.assertEqual(collist, tuple(columns)) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + msg, + ), ) + self.assertEqual(datalist, tuple(data)) + + def test_volume_list_long(self): + arglist = [ + '--long', + ] + verifylist = [ + ('long', True), + ('all_projects', False), + ('name', None), + ('status', None), + ] + + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + columns, data = self.cmd.take_action(parsed_args) + + collist = [ + 'ID', + 'Display Name', + 'Status', + 'Size', + 'Type', + 'Bootable', + 'Attached to', + 'Properties', + ] + self.assertEqual(collist, columns) + + server = volume_fakes.volume_attachment_server['id'] + device = volume_fakes.volume_attachment_server['device'] + msg = 'Attached to %s on %s ' % (server, device) + datalist = (( + volume_fakes.volume_id, + volume_fakes.volume_name, + volume_fakes.volume_status, + volume_fakes.volume_size, + volume_fakes.volume_type, + '', + msg, + "Alpha='a', Beta='b', Gamma='g'", + ), ) + self.assertEqual(datalist, tuple(data)) + + class TestVolumeShow(TestVolume): def setUp(self): super(TestVolumeShow, self).setUp() diff --git a/openstackclient/volume/v2/volume.py b/openstackclient/volume/v2/volume.py index d4536f51..fe4a3ff6 100644 --- a/openstackclient/volume/v2/volume.py +++ b/openstackclient/volume/v2/volume.py @@ -14,9 +14,12 @@ """Volume V2 Volume action implementations""" +import copy import logging +import os from cliff import command +from cliff import lister from cliff import show import six @@ -189,6 +192,112 @@ class DeleteVolume(command.Command): return +class ListVolume(lister.Lister): + """List volumes""" + + log = logging.getLogger(__name__ + '.ListVolume') + + def get_parser(self, prog_name): + parser = super(ListVolume, self).get_parser(prog_name) + parser.add_argument( + '--all-projects', + action='store_true', + default=bool(int(os.environ.get("ALL_PROJECTS", 0))), + help='Include all projects (admin only)', + ) + parser.add_argument( + '--long', + action='store_true', + default=False, + help='List additional fields in output', + ) + parser.add_argument( + '--name', + metavar='<name>', + help='Filter results by name', + ) + parser.add_argument( + '--status', + metavar='<status>', + help='Filter results by status', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + + volume_client = self.app.client_manager.volume + compute_client = self.app.client_manager.compute + + def _format_attach(attachments): + """Return a formatted string of a volume's attached instances + + :param volume: a volume.attachments field + :rtype: a string of formatted instances + """ + + msg = '' + for attachment in attachments: + server = attachment['id'] + if server in server_cache: + server = server_cache[server].name + device = attachment['device'] + msg += 'Attached to %s on %s ' % (server, device) + return msg + + if parsed_args.long: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Volume Type', + 'Bootable', + 'Attachments', + 'Metadata', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Type' + column_headers[6] = 'Attached to' + column_headers[7] = 'Properties' + else: + columns = [ + 'ID', + 'Name', + 'Status', + 'Size', + 'Attachments', + ] + column_headers = copy.deepcopy(columns) + column_headers[1] = 'Display Name' + column_headers[4] = 'Attached to' + + # Cache the server list + server_cache = {} + try: + for s in compute_client.servers.list(): + server_cache[s.id] = s + except Exception: + # Just forget it if there's any trouble + pass + + search_opts = { + 'all_projects': parsed_args.all_projects, + 'display_name': parsed_args.name, + 'status': parsed_args.status, + } + + data = volume_client.volumes.list(search_opts=search_opts) + + return (column_headers, + (utils.get_item_properties( + s, columns, + formatters={'Metadata': utils.format_dict, + 'Attachments': _format_attach}, + ) for s in data)) + + class SetVolume(show.ShowOne): """Set volume properties""" diff --git a/openstackclient/volume/v2/volume_type.py b/openstackclient/volume/v2/volume_type.py index 7f9a1c4b..fb0342c5 100644 --- a/openstackclient/volume/v2/volume_type.py +++ b/openstackclient/volume/v2/volume_type.py @@ -143,6 +143,67 @@ class ListVolumeType(lister.Lister): ) for s in data)) +class SetVolumeType(command.Command): + """Set volume type properties""" + + log = logging.getLogger(__name__ + '.SetVolumeType') + + def get_parser(self, prog_name): + parser = super(SetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='<volume-type>', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--name', + metavar='<name>', + help='Set volume type name', + ) + parser.add_argument( + '--description', + metavar='<name>', + help='Set volume type description', + ) + parser.add_argument( + '--property', + metavar='<key=value>', + action=parseractions.KeyValueAction, + help='Property to add or modify for this volume type ' + '(repeat option to set multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, parsed_args.volume_type) + + if (not parsed_args.name + and not parsed_args.description + and not parsed_args.property): + self.app.log.error("No changes requested\n") + return + + kwargs = {} + if parsed_args.name: + kwargs['name'] = parsed_args.name + if parsed_args.description: + kwargs['description'] = parsed_args.description + + if kwargs: + volume_client.volume_types.update( + volume_type.id, + **kwargs + ) + + if parsed_args.property: + volume_type.set_keys(parsed_args.property) + + return + + class ShowVolumeType(show.ShowOne): """Display volume type details""" @@ -165,3 +226,36 @@ class ShowVolumeType(show.ShowOne): properties = utils.format_dict(volume_type._info.pop('extra_specs')) volume_type._info.update({'properties': properties}) return zip(*sorted(six.iteritems(volume_type._info))) + + +class UnsetVolumeType(command.Command): + """Unset volume type properties""" + + log = logging.getLogger(__name__ + '.UnsetVolumeType') + + def get_parser(self, prog_name): + parser = super(UnsetVolumeType, self).get_parser(prog_name) + parser.add_argument( + 'volume_type', + metavar='<volume-type>', + help='Volume type to modify (name or ID)', + ) + parser.add_argument( + '--property', + metavar='<key>', + default=[], + required=True, + help='Property to remove from volume type ' + '(repeat option to remove multiple properties)', + ) + return parser + + def take_action(self, parsed_args): + self.log.debug('take_action(%s)', parsed_args) + volume_client = self.app.client_manager.volume + volume_type = utils.find_resource( + volume_client.volume_types, + parsed_args.volume_type, + ) + volume_type.unset_keys(parsed_args.property) + return |
