From: Mike Perez Date: Fri, 14 Aug 2015 23:19:28 +0000 (-0700) Subject: Add volume type support to Datera X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=5a246962c3eeeb40996d1d57578035d96a00a8e2;p=openstack-build%2Fcinder-build.git 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 --- diff --git a/cinder/tests/unit/volume/drivers/test_datera.py b/cinder/tests/unit/volume/drivers/test_datera.py index 8c4296014..09160b069 100644 --- a/cinder/tests/unit/volume/drivers/test_datera.py +++ b/cinder/tests/unit/volume/drivers/test_datera.py @@ -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 diff --git a/cinder/volume/drivers/datera.py b/cinder/volume/drivers/datera.py index fa6ff7ae9..e221dd652 100644 --- a/cinder/volume/drivers/datera.py +++ b/cinder/volume/drivers/datera.py @@ -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) - - if result['status'] == 'available': - return - self._wait_for_resource(resource['id'], resource_type) + 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) + + 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') % {