Add volume type support to Datera
This allows created volumes to have a volume type specified with them. Implements: blueprint datera-extra-spec-support Implements: blueprint datera-qos-support Change-Id: I217d75deecbbf2282125703c296dc524174c4660
This commit is contained in:
parent
4e8aa92614
commit
5a246962c3
|
@ -20,6 +20,7 @@ from cinder import exception
|
|||
from cinder import test
|
||||
from cinder.volume import configuration as conf
|
||||
from cinder.volume.drivers import datera
|
||||
from cinder.volume import volume_types
|
||||
|
||||
|
||||
class DateraVolumeTestCase(test.TestCase):
|
||||
|
@ -103,6 +104,57 @@ class DateraVolumeTestCase(test.TestCase):
|
|||
self.assertEqual(1, self.mock_api.retry_count)
|
||||
self.assertIsNone(self.driver.create_volume(self.volume))
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_volume_with_extra_specs(self, mock_get_type):
|
||||
self.mock_api.return_value = {
|
||||
u'status': u'available',
|
||||
u'name': u'volume-00000001',
|
||||
u'parent': u'00000000-0000-0000-0000-000000000000',
|
||||
u'uuid': u'c20aba21-6ef6-446b-b374-45733b4883ba',
|
||||
u'snapshots': {},
|
||||
u'targets': {},
|
||||
u'num_replicas': u'2',
|
||||
u'sub_type': u'IS_ORIGINAL',
|
||||
u'size': u'1073741824'
|
||||
}
|
||||
|
||||
mock_get_type.return_value = {
|
||||
'name': u'The Best',
|
||||
'qos_specs_id': None,
|
||||
'deleted': False,
|
||||
'created_at': '2015-08-14 04:18:11',
|
||||
'updated_at': None,
|
||||
'extra_specs': {
|
||||
u'volume_backend_name': u'datera',
|
||||
u'qos:max_iops_read': u'2000',
|
||||
u'qos:max_iops_write': u'4000',
|
||||
u'qos:max_iops_total': u'4000'
|
||||
},
|
||||
'is_public': True,
|
||||
'deleted_at': None,
|
||||
'id': u'dffb4a83-b8fb-4c19-9f8c-713bb75db3b1',
|
||||
'description': None
|
||||
}
|
||||
|
||||
mock_volume = _stub_volume(
|
||||
volume_type_id='dffb4a83-b8fb-4c19-9f8c-713bb75db3b1'
|
||||
)
|
||||
|
||||
assert_body = {
|
||||
u'max_iops_read': u'2000',
|
||||
'numReplicas': '2',
|
||||
'uuid': u'c20aba21-6ef6-446b-b374-45733b4883ba',
|
||||
'size': '1073741824',
|
||||
u'max_iops_write': u'4000',
|
||||
u'max_iops_total': u'4000',
|
||||
'name': u'volume-00000001'
|
||||
}
|
||||
|
||||
self.assertIsNone(self.driver.create_volume(mock_volume))
|
||||
self.mock_api.assert_called_once_with('volumes', 'post',
|
||||
body=assert_body)
|
||||
self.assertTrue(mock_get_type.called)
|
||||
|
||||
def test_create_cloned_volume_success(self):
|
||||
self.mock_api.return_value = {
|
||||
'status': 'available',
|
||||
|
@ -416,6 +468,7 @@ def _stub_volume(*args, **kwargs):
|
|||
volume['display_name'] = kwargs.get('display_name', name)
|
||||
volume['size'] = kwargs.get('size', size)
|
||||
volume['provider_location'] = kwargs.get('provider_location', None)
|
||||
volume['volume_type_id'] = kwargs.get('volume_type_id', None)
|
||||
return volume
|
||||
|
||||
|
||||
|
|
|
@ -23,10 +23,13 @@ from oslo_utils import units
|
|||
import requests
|
||||
import six
|
||||
|
||||
from cinder import context
|
||||
from cinder import exception
|
||||
from cinder.i18n import _, _LE, _LI, _LW
|
||||
from cinder import utils
|
||||
from cinder.volume.drivers.san import san
|
||||
from cinder.volume import qos_specs
|
||||
from cinder.volume import volume_types
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
@ -133,11 +136,26 @@ class DateraDriver(san.SanISCSIDriver):
|
|||
_('Resource not ready.'))
|
||||
|
||||
def _create_resource(self, resource, resource_type, body):
|
||||
result = self._issue_api_request(resource_type, 'post', body=body)
|
||||
type_id = resource.get('volume_type_id', None)
|
||||
if resource_type == 'volumes':
|
||||
if type_id is not None:
|
||||
policies = self._get_policies_by_volume_type(type_id)
|
||||
if policies:
|
||||
body.update(policies)
|
||||
|
||||
if result['status'] == 'available':
|
||||
return
|
||||
self._wait_for_resource(resource['id'], resource_type)
|
||||
result = None
|
||||
try:
|
||||
result = self._issue_api_request(resource_type, 'post', body=body)
|
||||
except exception.Invalid:
|
||||
if resource_type == 'volumes' and type_id:
|
||||
LOG.error(_LE("Creation request failed. Please verify the "
|
||||
"extra-specs set for your volume types are "
|
||||
"entered correctly."))
|
||||
raise
|
||||
else:
|
||||
if result['status'] == 'available':
|
||||
return
|
||||
self._wait_for_resource(resource['id'], resource_type)
|
||||
|
||||
def create_volume(self, volume):
|
||||
"""Create a logical volume."""
|
||||
|
@ -299,6 +317,30 @@ class DateraDriver(san.SanISCSIDriver):
|
|||
'cinder.conf and start the cinder-volume'
|
||||
'service again.'))
|
||||
|
||||
def _get_policies_by_volume_type(self, type_id):
|
||||
"""Get extra_specs and qos_specs of a volume_type.
|
||||
|
||||
This fetches the scoped keys from the volume type. Anything set from
|
||||
qos_specs will override key/values set from extra_specs.
|
||||
"""
|
||||
ctxt = context.get_admin_context()
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
specs = volume_type.get('extra_specs')
|
||||
|
||||
policies = {}
|
||||
for key, value in specs.items():
|
||||
if ':' in key:
|
||||
fields = key.split(':')
|
||||
key = fields[1]
|
||||
policies[key] = value
|
||||
|
||||
qos_specs_id = volume_type.get('qos_specs_id')
|
||||
if qos_specs_id is not None:
|
||||
qos_kvs = qos_specs.get_qos_specs(ctxt, qos_specs_id)['specs']
|
||||
if qos_kvs:
|
||||
policies.update(qos_kvs)
|
||||
return policies
|
||||
|
||||
@_authenticated
|
||||
def _issue_api_request(self, resource_type, method='get', resource=None,
|
||||
body=None, action=None, sensitive=False):
|
||||
|
@ -372,6 +414,12 @@ class DateraDriver(san.SanISCSIDriver):
|
|||
raise exception.NotFound(data['message'])
|
||||
elif response.status_code in [403, 401]:
|
||||
raise exception.NotAuthorized()
|
||||
elif response.status_code == 400 and 'invalidArgs' in data:
|
||||
msg = _('Bad request sent to Datera cluster:'
|
||||
'Invalid args: %(args)s | %(message)s') % {
|
||||
'args': data['invalidArgs']['invalidAttrs'],
|
||||
'message': data['message']}
|
||||
raise exception.Invalid(msg)
|
||||
else:
|
||||
msg = _('Request to Datera cluster returned bad status:'
|
||||
' %(status)s | %(reason)s') % {
|
||||
|
|
Loading…
Reference in New Issue