Add commands for default type overrides

This patch adds command for set,get and delete default volume types
for projects.

This patch adds 3 commands :

1) Set
Set a default volume type for a project
cinder --os-volume-api-version 3.62 default-type-set <vol-type-id> <project-id>

2) Get
Get the default volume type for a project
cinder --os-volume-api-version 3.62 default-type-list --project-id <project-id>
Get all default types
cinder --os-volume-api-version 3.62 default-type-list

3) Unset
Unset default volume type for a project
cinder --os-volume-api-version 3.62 default-type-unset <project-id>

Implements: Blueprint multiple-default-volume-types

Change-Id: Id2fb00c218edbb98df3193577dba6a897c6e73f6
This commit is contained in:
whoami-rajat 2020-07-03 12:31:48 +00:00 committed by Brian Rosmaita
parent 76f2b91d9a
commit 7ee7d376a1
10 changed files with 277 additions and 1 deletions

View File

@ -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 = {}

View File

@ -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)):
"""

View File

@ -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):

View File

@ -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
#

View File

@ -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')

View File

@ -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')

View File

@ -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)

View File

@ -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 "<DefaultVolumeType: %s>" % 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)

View File

@ -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='<volume_type>',
help='Name or ID of the volume type.')
@utils.arg('project',
metavar='<project_id>',
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='<project_id>',
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='<project_id>',
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))

View File

@ -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.