]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add volume type support to Datera
authorMike Perez <thingee@gmail.com>
Fri, 14 Aug 2015 23:19:28 +0000 (16:19 -0700)
committerMike Perez <thingee@gmail.com>
Fri, 14 Aug 2015 23:21:28 +0000 (16:21 -0700)
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

cinder/tests/unit/volume/drivers/test_datera.py
cinder/volume/drivers/datera.py

index 8c429601404e59e0e56eb82a5267beaba4a65d6d..09160b069f16b51b47980a1500cc1ccacf78cbe5 100644 (file)
@@ -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
 
 
index fa6ff7ae9e0ada1685c7faf305ca412117a0f962..e221dd65258017cc2529290a1233dec2acefb35b 100644 (file)
@@ -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') % {