diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index dbd9d1eca..ff4cc9310 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"2": "3"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.61" +MAX_VERSION = "3.62" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/base.py b/cinderclient/base.py index a317ad432..40ce381be 100644 --- a/cinderclient/base.py +++ b/cinderclient/base.py @@ -331,6 +331,26 @@ class Manager(common_base.HookableMixin): else: return self.resource_class(self, body, loaded=True) + def _get_all_with_base_url(self, url, response_key=None): + resp, body = self.api.client.get_with_base_url(url) + if response_key: + if isinstance(body[response_key], list): + return [self.resource_class(self, res, loaded=True) + for res in body[response_key] if res] + return self.resource_class(self, body[response_key], + loaded=True) + return self.resource_class(self, body, loaded=True) + + def _create_update_with_base_url(self, url, body, response_key=None): + resp, body = self.api.client.create_update_with_base_url( + url, body=body) + if response_key: + return self.resource_class(self, body[response_key], loaded=True) + return self.resource_class(self, body, loaded=True) + + def _delete_with_base_url(self, url, response_key=None): + self.api.client.delete_with_base_url(url) + class ManagerWithFind(six.with_metaclass(abc.ABCMeta, Manager)): """ diff --git a/cinderclient/client.py b/cinderclient/client.py index fdf1f363e..eb8c0de23 100644 --- a/cinderclient/client.py +++ b/cinderclient/client.py @@ -269,6 +269,12 @@ class SessionClient(adapter.LegacyJsonAdapter): def get_with_base_url(self, url, **kwargs): return self._cs_request_base_url(url, 'GET', **kwargs) + def create_update_with_base_url(self, url, **kwargs): + return self._cs_request_base_url(url, 'PUT', **kwargs) + + def delete_with_base_url(self, url, **kwargs): + return self._cs_request_base_url(url, 'DELETE', **kwargs) + class HTTPClient(object): diff --git a/cinderclient/tests/unit/v2/fakes.py b/cinderclient/tests/unit/v2/fakes.py index 0cf1faa9e..0dd269e1d 100644 --- a/cinderclient/tests/unit/v2/fakes.py +++ b/cinderclient/tests/unit/v2/fakes.py @@ -309,6 +309,30 @@ def _stub_server_versions(): ] +def stub_default_type(): + return { + 'default_type': { + 'project_id': '629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + 'volume_type_id': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + } + } + + +def stub_default_types(): + return { + 'default_types': [ + { + 'project_id': '629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + 'volume_type_id': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + }, + { + 'project_id': 'a0c01994-1245-416e-8fc9-1aca86329bfd', + 'volume_type_id': 'ff094b46-f82a-4a74-9d9e-d3d08116ad93' + } + ] + } + + class FakeClient(fakes.FakeClient, client.Client): def __init__(self, api_version=None, *args, **kwargs): @@ -1055,9 +1079,35 @@ class FakeHTTPClient(base_client.HTTPClient): {'transfer': _stub_transfer(transfer1, base_uri, tenant_id)}) def get_with_base_url(self, url, **kw): + if 'default-types' in url: + return self._cs_request(url, 'GET', **kw) server_versions = _stub_server_versions() return (200, {'versions': server_versions}) + def create_update_with_base_url(self, url, **kwargs): + return self._cs_request(url, 'PUT', **kwargs) + + def put_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kwargs): + default_type = stub_default_type() + return (200, {}, default_type) + + def get_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kw): + default_types = stub_default_type() + return (200, {}, default_types) + + def get_v3_default_types(self, **kw): + default_types = stub_default_types() + return (200, {}, default_types) + + def delete_with_base_url(self, url, **kwargs): + return self._cs_request(url, 'DELETE', **kwargs) + + def delete_v3_default_types_629632e7_99d2_4c40_9ae3_106fa3b1c9b7( + self, **kwargs): + return (204, {}, {}) + # # Services # diff --git a/cinderclient/tests/unit/v3/test_default_types.py b/cinderclient/tests/unit/v3/test_default_types.py new file mode 100644 index 000000000..621aeb804 --- /dev/null +++ b/cinderclient/tests/unit/v3/test_default_types.py @@ -0,0 +1,46 @@ +# All Rights Reserved. +# +# 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. + +from cinderclient import api_versions +from cinderclient.tests.unit import utils +from cinderclient.tests.unit.v3 import fakes + +defaults = fakes.FakeClient(api_versions.APIVersion('3.62')) + + +class VolumeTypeDefaultTest(utils.TestCase): + + def test_set(self): + defaults.default_types.create('4c298f16-e339-4c80-b934-6cbfcb7525a0', + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'PUT', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + body={'default_type': + {'volume_type': '4c298f16-e339-4c80-b934-6cbfcb7525a0'}} + ) + + def test_get(self): + defaults.default_types.list('629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'GET', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + + def test_get_all(self): + defaults.default_types.list() + defaults.assert_called( + 'GET', 'v3/default-types') + + def test_unset(self): + defaults.default_types.delete('629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + defaults.assert_called( + 'DELETE', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 8338a3462..0332ae3d1 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -1611,3 +1611,33 @@ class ShellTest(utils.TestCase): def test_transfer_list_with_filters(self, command, expected): self.run_command('--os-volume-api-version 3.52 %s' % command) self.assert_called('GET', expected) + + def test_default_type_set(self): + self.run_command('--os-volume-api-version 3.62 default-type-set ' + '4c298f16-e339-4c80-b934-6cbfcb7525a0 ' + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + body = { + 'default_type': + { + 'volume_type': '4c298f16-e339-4c80-b934-6cbfcb7525a0' + } + } + self.assert_called( + 'PUT', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7', + body=body) + + def test_default_type_list_project(self): + self.run_command('--os-volume-api-version 3.62 default-type-list ' + '--project-id 629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + self.assert_called( + 'GET', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + + def test_default_type_list(self): + self.run_command('--os-volume-api-version 3.62 default-type-list') + self.assert_called('GET', 'v3/default-types') + + def test_default_type_delete(self): + self.run_command('--os-volume-api-version 3.62 default-type-unset ' + '629632e7-99d2-4c40-9ae3-106fa3b1c9b7') + self.assert_called( + 'DELETE', 'v3/default-types/629632e7-99d2-4c40-9ae3-106fa3b1c9b7') diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py index 5703826ea..770d9d605 100644 --- a/cinderclient/v3/client.py +++ b/cinderclient/v3/client.py @@ -21,6 +21,7 @@ from cinderclient.v3 import capabilities from cinderclient.v3 import cgsnapshots from cinderclient.v3 import clusters from cinderclient.v3 import consistencygroups +from cinderclient.v3 import default_types from cinderclient.v3 import group_snapshots from cinderclient.v3 import group_types from cinderclient.v3 import groups @@ -80,6 +81,7 @@ class Client(object): volume_type_access.VolumeTypeAccessManager(self) self.volume_encryption_types = \ volume_encryption_types.VolumeEncryptionTypeManager(self) + self.default_types = default_types.DefaultVolumeTypeManager(self) self.qos_specs = qos_specs.QoSSpecsManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) diff --git a/cinderclient/v3/default_types.py b/cinderclient/v3/default_types.py new file mode 100644 index 000000000..58e04ccb3 --- /dev/null +++ b/cinderclient/v3/default_types.py @@ -0,0 +1,65 @@ +# 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. + + +"""Default Volume Type interface.""" + +from cinderclient import base + + +class DefaultVolumeType(base.Resource): + """Default volume types for projects.""" + def __repr__(self): + return "" % self.project_id + + +class DefaultVolumeTypeManager(base.ManagerWithFind): + """Manage :class:`DefaultVolumeType` resources.""" + resource_class = DefaultVolumeType + + def create(self, volume_type, project_id): + """Creates a default volume type for a project + + :param volume_type: Name or ID of the volume type + :param project_id: Project to set default type for + """ + + body = { + "default_type": { + "volume_type": volume_type + } + } + + return self._create_update_with_base_url( + 'v3/default-types/%s' % project_id, body, + response_key='default_type') + + def list(self, project_id=None): + """List the default types.""" + + url = 'v3/default-types' + response_key = "default_types" + + if project_id: + url += '/' + project_id + response_key = "default_type" + + return self._get_all_with_base_url(url, response_key) + + def delete(self, project_id): + """Removes the default volume type for a project + + :param project_id: The ID of the project to unset default for. + """ + + return self._delete_with_base_url('v3/default-types/%s' % project_id) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 1ccf02e56..eaded7eab 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -2598,3 +2598,54 @@ def do_transfer_list(cs, args): columns = ['ID', 'Volume ID', 'Name'] utils.print_list(transfers, columns) AppendFilters.filters = [] + + +@api_versions.wraps('3.62') +@utils.arg('volume_type', + metavar='', + help='Name or ID of the volume type.') +@utils.arg('project', + metavar='', + help='ID of project for which to set default type.') +def do_default_type_set(cs, args): + """Sets a default volume type for a project.""" + volume_type = args.volume_type + project = args.project + + default_type = cs.default_types.create(volume_type, project) + utils.print_dict(default_type._info) + + +@api_versions.wraps('3.62') +@utils.arg('--project-id', + metavar='', + default=None, + help='ID of project for which to show the default type.') +def do_default_type_list(cs, args): + """Lists all default volume types.""" + + project_id = args.project_id + default_types = cs.default_types.list(project_id) + columns = ['Volume Type ID', 'Project ID'] + if project_id: + utils.print_dict(default_types._info) + else: + utils.print_list(default_types, columns) + + +@api_versions.wraps('3.62') +@utils.arg('project_id', + metavar='', + nargs='+', + help='ID of project for which to unset default type.') +def do_default_type_unset(cs, args): + """Unset default volume types.""" + + for project_id in args.project_id: + try: + cs.default_types.delete(project_id) + print("Default volume type for project %s has been unset " + "successfully." % (project_id)) + except Exception as e: + print("Unset for default volume type for project %s failed: %s" + % (project_id, e)) diff --git a/releasenotes/notes/project-default-types-727156d1db10a24d.yaml b/releasenotes/notes/project-default-types-727156d1db10a24d.yaml new file mode 100644 index 000000000..c4385a595 --- /dev/null +++ b/releasenotes/notes/project-default-types-727156d1db10a24d.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added support to set, get, and unset the default volume type for + projects with Block Storage API version 3.62 and higher. +