]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Implement retype in HP LeftHand driver
authorJim Branen <james.branen@hp.com>
Fri, 14 Feb 2014 17:24:53 +0000 (09:24 -0800)
committerJim Branen <james.branen@hp.com>
Fri, 14 Feb 2014 17:24:53 +0000 (09:24 -0800)
This adds the ability to modify the
type of an existing HP LeftHand volume to
another type supported on the same backend.

Main use case for this is changing provisioning,
(full vs. thin), adaptive optimization and data
protections settings for a volume on-demand.

Change-Id: I78e53431708bcb3151c5dc4016e65d1a655e03c6
Implements: blueprint retype-support-for-hplefthand-driver

cinder/tests/test_hplefthand.py
cinder/volume/drivers/san/hp/hp_lefthand_cliq_proxy.py
cinder/volume/drivers/san/hp/hp_lefthand_iscsi.py
cinder/volume/drivers/san/hp/hp_lefthand_rest_proxy.py

index 7d3b395ed6b6c3a1a1b9ab6fc1017c20b43143a3..2f757377c51de4ca639a4f1305344bbd422896ec 100644 (file)
@@ -18,6 +18,7 @@ import mock
 
 from hplefthandclient import exceptions as hpexceptions
 
+from cinder import context
 from cinder import exception
 from cinder.openstack.common import log as logging
 from cinder import test
@@ -974,8 +975,10 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
         volume_with_vt['volume_type_id'] = self.volume_type_id
 
         # get the extra specs of interest from this volume's volume type
-        extra_specs = self.driver.proxy._get_extra_specs(
-            volume_with_vt,
+        volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
+            volume_with_vt)
+        extra_specs = self.driver.proxy._get_lh_extra_specs(
+            volume_extra_specs,
             hp_lefthand_rest_proxy.extra_specs_key_map.keys())
 
         # map the extra specs key/value pairs to key/value pairs
@@ -1000,8 +1003,10 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
                 'hplh:ao': 'true'}}
 
         # get the extra specs of interest from this volume's volume type
-        extra_specs = self.driver.proxy._get_extra_specs(
-            volume_with_vt,
+        volume_extra_specs = self.driver.proxy._get_volume_extra_specs(
+            volume_with_vt)
+        extra_specs = self.driver.proxy._get_lh_extra_specs(
+            volume_extra_specs,
             hp_lefthand_rest_proxy.extra_specs_key_map.keys())
 
         # map the extra specs key/value pairs to key/value pairs
@@ -1012,3 +1017,134 @@ class TestHPLeftHandRESTISCSIDriver(HPLeftHandBaseDriver, test.TestCase):
         # {'isAdaptiveOptimizationEnabled': True}
         # without hplh:data_pl since r-07 is an invalid value
         self.assertDictMatch({'isAdaptiveOptimizationEnabled': True}, optional)
+
+    def test_retype_with_no_LH_extra_specs(self):
+        # setup drive with default configuration
+        # and return the mock HTTP LeftHand client
+        mock_client = self.setup_driver()
+
+        ctxt = context.get_admin_context()
+
+        host = {'host': self.serverName}
+        key_specs_old = {'foo': False, 'bar': 2, 'error': True}
+        key_specs_new = {'foo': True, 'bar': 5, 'error': False}
+        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
+        new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
+
+        diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
+                                                     new_type_ref['id'])
+
+        volume = dict.copy(self.volume)
+        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
+        volume['volume_type'] = old_type
+        volume['host'] = host
+        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
+
+        self.driver.retype(ctxt, volume, new_type, diff, host)
+
+        expected = self.driver_startup_call_stack + [
+            mock.call.getVolumeByName('fakevolume')]
+
+        # validate call chain
+        mock_client.assert_has_calls(expected)
+
+    def test_retype_with_only_LH_extra_specs(self):
+        # setup drive with default configuration
+        # and return the mock HTTP LeftHand client
+        mock_client = self.setup_driver()
+        mock_client.getVolumeByName.return_value = {'id': self.volume_id}
+
+        ctxt = context.get_admin_context()
+
+        host = {'host': self.serverName}
+        key_specs_old = {'hplh:provisioning': 'thin'}
+        key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
+        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
+        new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
+
+        diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
+                                                     new_type_ref['id'])
+
+        volume = dict.copy(self.volume)
+        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
+        volume['volume_type'] = old_type
+        volume['host'] = host
+        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
+
+        self.driver.retype(ctxt, volume, new_type, diff, host)
+
+        expected = self.driver_startup_call_stack + [
+            mock.call.getVolumeByName('fakevolume'),
+            mock.call.modifyVolume(
+                1, {
+                    'isThinProvisioned': False,
+                    'isAdaptiveOptimizationEnabled': True})]
+
+        # validate call chain
+        mock_client.assert_has_calls(expected)
+
+    def test_retype_with_both_extra_specs(self):
+        # setup drive with default configuration
+        # and return the mock HTTP LeftHand client
+        mock_client = self.setup_driver()
+        mock_client.getVolumeByName.return_value = {'id': self.volume_id}
+
+        ctxt = context.get_admin_context()
+
+        host = {'host': self.serverName}
+        key_specs_old = {'hplh:provisioning': 'full', 'foo': 'bar'}
+        key_specs_new = {'hplh:provisioning': 'thin', 'foo': 'foobar'}
+        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
+        new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
+
+        diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
+                                                     new_type_ref['id'])
+
+        volume = dict.copy(self.volume)
+        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
+        volume['volume_type'] = old_type
+        volume['host'] = host
+        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
+
+        self.driver.retype(ctxt, volume, new_type, diff, host)
+
+        expected = self.driver_startup_call_stack + [
+            mock.call.getVolumeByName('fakevolume'),
+            mock.call.modifyVolume(1, {'isThinProvisioned': True})]
+
+        # validate call chain
+        mock_client.assert_has_calls(expected)
+
+    def test_retype_same_extra_specs(self):
+        # setup drive with default configuration
+        # and return the mock HTTP LeftHand client
+        mock_client = self.setup_driver()
+        mock_client.getVolumeByName.return_value = {'id': self.volume_id}
+
+        ctxt = context.get_admin_context()
+
+        host = {'host': self.serverName}
+        key_specs_old = {'hplh:provisioning': 'full', 'hplh:ao': 'true'}
+        key_specs_new = {'hplh:provisioning': 'full', 'hplh:ao': 'false'}
+        old_type_ref = volume_types.create(ctxt, 'old', key_specs_old)
+        new_type_ref = volume_types.create(ctxt, 'new', key_specs_new)
+
+        diff, equal = volume_types.volume_types_diff(ctxt, old_type_ref['id'],
+                                                     new_type_ref['id'])
+
+        volume = dict.copy(self.volume)
+        old_type = volume_types.get_volume_type(ctxt, old_type_ref['id'])
+        volume['volume_type'] = old_type
+        volume['host'] = host
+        new_type = volume_types.get_volume_type(ctxt, new_type_ref['id'])
+
+        self.driver.retype(ctxt, volume, new_type, diff, host)
+
+        expected = self.driver_startup_call_stack + [
+            mock.call.getVolumeByName('fakevolume'),
+            mock.call.modifyVolume(
+                1,
+                {'isAdaptiveOptimizationEnabled': False})]
+
+        # validate call chain
+        mock_client.assert_has_calls(expected)
index 15f7dc0578186a495610d851b5a6494ff27c94e5..5105080a13ec2e70cf09118909581ba5d8315e81 100644 (file)
@@ -448,3 +448,18 @@ class HPLeftHandCLIQProxy(SanISCSIDriver):
 
     def remove_export(self, context, volume):
         pass
+
+    def retype(self, context, volume, new_type, diff, host):
+        """Convert the volume to be of the new type.
+
+        Returns a boolean indicating whether the retype occurred.
+
+        :param ctxt: Context
+        :param volume: A dictionary describing the volume to migrate
+        :param new_type: A dictionary describing the volume type to convert to
+        :param diff: A dictionary with the difference between the two types
+        :param host: A dictionary describing the host to migrate to, where
+                     host['host'] is its name, and host['capabilities'] is a
+                     dictionary of its reported capabilities.
+        """
+        return False
index 65ccf11ef9b4f5ebbb0557b730a431e405ccf99f..7c2b78ed60bf8745cde81574d4c775dc89861539 100644 (file)
@@ -46,9 +46,10 @@ class HPLeftHandISCSIDriver(VolumeDriver):
 
     Version history:
         1.0.0 - Initial driver
+        1.0.1 - Added support for retype
     """
 
-    VERSION = "1.0.0"
+    VERSION = "1.0.1"
 
     def __init__(self, *args, **kwargs):
         super(HPLeftHandISCSIDriver, self).__init__(*args, **kwargs)
@@ -135,3 +136,8 @@ class HPLeftHandISCSIDriver(VolumeDriver):
     @utils.synchronized('lefthand', external=True)
     def remove_export(self, context, volume):
         return self.proxy.remove_export(context, volume)
+
+    @utils.synchronized('lefthand', external=True)
+    def retype(self, context, volume, new_type, diff, host):
+        """Convert the volume to be of the new type."""
+        return self.proxy.retype(context, volume, new_type, diff, host)
index 0bdaab1b78840159d16c3719c0ec63e0386c0920..3465324ac6d54710e1db1381d4b582c25dad4c91 100644 (file)
@@ -83,9 +83,10 @@ class HPLeftHandRESTProxy(ISCSIDriver):
 
     Version history:
         1.0.0 - Initial REST iSCSI proxy
+        1.0.1 - Added support for retype
     """
 
-    VERSION = "1.0.0"
+    VERSION = "1.0.1"
 
     device_stats = {}
 
@@ -131,8 +132,9 @@ class HPLeftHandRESTProxy(ISCSIDriver):
         """Creates a volume."""
         try:
             # get the extra specs of interest from this volume's volume type
-            extra_specs = self._get_extra_specs(
-                volume,
+            volume_extra_specs = self._get_volume_extra_specs(volume)
+            extra_specs = self._get_lh_extra_specs(
+                volume_extra_specs,
                 extra_specs_key_map.keys())
 
             # map the extra specs key/value pairs to key/value pairs
@@ -285,19 +287,24 @@ class HPLeftHandRESTProxy(ISCSIDriver):
         except Exception as ex:
             raise exception.VolumeBackendAPIException(str(ex))
 
-    def _get_extra_specs(self, volume, valid_keys):
-        """Get extra specs of interest (valid_keys) from volume type."""
+    def _get_volume_extra_specs(self, volume):
+        """Get extra specs from a volume."""
         extra_specs = {}
         type_id = volume.get('volume_type_id', None)
         if type_id is not None:
             ctxt = context.get_admin_context()
             volume_type = volume_types.get_volume_type(ctxt, type_id)
-            specs = volume_type.get('extra_specs')
-            for key, value in specs.iteritems():
-                if key in valid_keys:
-                    extra_specs[key] = value
+            extra_specs = volume_type.get('extra_specs')
         return extra_specs
 
+    def _get_lh_extra_specs(self, extra_specs, valid_keys):
+        """Get LeftHand extra_specs (valid_keys only)."""
+        extra_specs_of_interest = {}
+        for key, value in extra_specs.iteritems():
+            if key in valid_keys:
+                extra_specs_of_interest[key] = value
+        return extra_specs_of_interest
+
     def _map_extra_specs(self, extra_specs):
         """Map the extra spec key/values to LeftHand key/values."""
         client_options = {}
@@ -361,3 +368,53 @@ class HPLeftHandRESTProxy(ISCSIDriver):
 
     def remove_export(self, context, volume):
         pass
+
+    def retype(self, ctxt, volume, new_type, diff, host):
+        """Convert the volume to be of the new type.
+
+        Returns a boolean indicating whether the retype occurred.
+
+        :param ctxt: Context
+        :param volume: A dictionary describing the volume to retype
+        :param new_type: A dictionary describing the volume type to convert to
+        :param diff: A dictionary with the difference between the two types
+        :param host: A dictionary describing the host, where
+                     host['host'] is its name, and host['capabilities'] is a
+                     dictionary of its reported capabilities.
+        """
+        LOG.debug(_('enter: retype: id=%(id)s, new_type=%(new_type)s,'
+                    'diff=%(diff)s, host=%(host)s') % {'id': volume['id'],
+                                                       'new_type': new_type,
+                                                       'diff': diff,
+                                                       'host': host})
+        try:
+            volume_info = self.client.getVolumeByName(volume['name'])
+        except hpexceptions.HTTPNotFound:
+            raise exception.VolumeNotFound(volume_id=volume['id'])
+
+        try:
+            # pick out the LH extra specs
+            new_extra_specs = dict(new_type).get('extra_specs')
+            lh_extra_specs = self._get_lh_extra_specs(
+                new_extra_specs,
+                extra_specs_key_map.keys())
+
+            LOG.debug(_('LH specs=%(specs)s') % {'specs': lh_extra_specs})
+
+            # only set the ones that have changed
+            changed_extra_specs = {}
+            for key, value in lh_extra_specs.iteritems():
+                (old, new) = diff['extra_specs'][key]
+                if old != new:
+                    changed_extra_specs[key] = value
+
+            # map extra specs to LeftHand options
+            options = self._map_extra_specs(changed_extra_specs)
+            if len(options) > 0:
+                self.client.modifyVolume(volume_info['id'], options)
+            return True
+
+        except Exception as ex:
+            LOG.warning("%s" % str(ex))
+
+        return False