]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR migrate without losing type settings
authorMark Sturdevant <mark.sturdevant@hp.com>
Mon, 1 Sep 2014 21:33:12 +0000 (14:33 -0700)
committerMark Sturdevant <mark.sturdevant@hp.com>
Fri, 10 Oct 2014 17:55:04 +0000 (10:55 -0700)
Leverage retype to fix migrate.  It was losing type settings.

Closes-Bug: 1356608
Change-Id: Ic4d5233533abf65e4784b0e89f5793a6cd1ce521

cinder/tests/test_hp3par.py
cinder/volume/drivers/san/hp/hp_3par_common.py
cinder/volume/drivers/san/hp/hp_3par_fc.py
cinder/volume/drivers/san/hp/hp_3par_iscsi.py

index a547bed3ba2f5baa28494494e12e178c0e17530f..78f2d94701228d415cfeaf0b9597659df84558d6 100644 (file)
@@ -981,14 +981,19 @@ class HP3PARBaseDriver(object):
                 'status': 1},
             'getCPG.return_value': {},
             'copyVolume.return_value': {'taskid': 1},
-            'getVolume.return_value': {}
+            'getVolume.return_value': self.RETYPE_VOLUME_INFO_1
         }
 
         mock_client = self.setup_driver(mock_conf=conf)
 
+        mock_client.getVolume.return_value = self.MANAGE_VOLUME_INFO
+        mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
+        mock_client.getTask.return_value = self.STATUS_DONE
+
         volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
                   'id': HP3PARBaseDriver.CLONE_ID,
                   'display_name': 'Foo Volume',
+                  'volume_type_id': None,
                   'size': 2,
                   'status': 'available',
                   'host': HP3PARBaseDriver.FAKE_HOST,
@@ -997,7 +1002,7 @@ class HP3PARBaseDriver(object):
         volume_name_3par = self.driver.common._encode_name(volume['id'])
 
         loc_info = 'HP3PARDriver:1234:CPG-FC1'
-        host = {'host': 'stack@3parfc1',
+        host = {'host': 'stack@3parfc1#CPG-FC1',
                 'capabilities': {'location_info': loc_info}}
 
         result = self.driver.migrate_volume(context.get_admin_context(),
@@ -1006,18 +1011,89 @@ class HP3PARBaseDriver(object):
         self.assertEqual((True, None), result)
 
         osv_matcher = 'osv-' + volume_name_3par
-        omv_matcher = 'omv-' + volume_name_3par
 
         expected = [
-            mock.call.login(HP3PAR_USER_NAME, HP3PAR_USER_PASS),
-            mock.call.getStorageSystemInfo(),
-            mock.call.getCPG(HP3PAR_CPG),
-            mock.call.getCPG('CPG-FC1'),
-            mock.call.copyVolume(osv_matcher, omv_matcher, mock.ANY, mock.ANY),
+            mock.call.modifyVolume(
+                osv_matcher,
+                {'comment': '{"qos": {}, "display_name": "Foo Volume"}',
+                 'snapCPG': 'CPG-FC1'}),
+            mock.call.modifyVolume(osv_matcher,
+                                   {'action': 6,
+                                    'userCPG': 'CPG-FC1',
+                                    'conversionOperation': 1,
+                                    'tuneOperation': 1}),
+            mock.call.getTask(mock.ANY),
+            mock.call.logout()
+        ]
+
+        mock_client.assert_has_calls(expected)
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_migrate_volume_with_type(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_2
+
+        conf = {
+            'getStorageSystemInfo.return_value': {
+                'serialNumber': '1234'},
+            'getTask.return_value': {
+                'status': 1},
+            'getCPG.return_value': {},
+            'copyVolume.return_value': {'taskid': 1},
+            'getVolume.return_value': self.RETYPE_VOLUME_INFO_1
+        }
+
+        mock_client = self.setup_driver(mock_conf=conf)
+
+        mock_client.getVolume.return_value = self.MANAGE_VOLUME_INFO
+        mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
+        mock_client.getTask.return_value = self.STATUS_DONE
+
+        display_name = 'Foo Volume'
+        volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
+                  'id': HP3PARBaseDriver.CLONE_ID,
+                  'display_name': display_name,
+                  "volume_type_id": self.RETYPE_VOLUME_TYPE_2['id'],
+                  'size': 2,
+                  'status': 'available',
+                  'host': HP3PARBaseDriver.FAKE_HOST,
+                  'source_volid': HP3PARBaseDriver.VOLUME_ID}
+
+        volume_name_3par = self.driver.common._encode_name(volume['id'])
+
+        loc_info = 'HP3PARDriver:1234:CPG-FC1'
+        host = {'host': 'stack@3parfc1#CPG-FC1',
+                'capabilities': {'location_info': loc_info}}
+
+        result = self.driver.migrate_volume(context.get_admin_context(),
+                                            volume, host)
+        self.assertIsNotNone(result)
+        expected_host = volume_utils.append_host(
+            "stack@3parfc1",
+            self.RETYPE_VOLUME_TYPE_2['extra_specs']['cpg'])
+        self.assertEqual((True, {'host': expected_host}), result)
+
+        osv_matcher = 'osv-' + volume_name_3par
+
+        expected_comment = {
+            "display_name": display_name,
+            "volume_type_id": self.RETYPE_VOLUME_TYPE_2['id'],
+            "volume_type_name": self.RETYPE_VOLUME_TYPE_2['name'],
+            "vvs": self.RETYPE_VOLUME_TYPE_2['extra_specs']['vvs']
+        }
+        expected = [
+            mock.call.modifyVolume(
+                osv_matcher,
+                {'comment': self.CommentMatcher(self.assertEqual,
+                                                expected_comment),
+                 'snapCPG': self.RETYPE_VOLUME_TYPE_2
+                 ['extra_specs']['snap_cpg']}),
+            mock.call.modifyVolume(
+                osv_matcher,
+                {'action': 6,
+                 'userCPG': self.RETYPE_VOLUME_TYPE_2['extra_specs']['cpg'],
+                 'conversionOperation': 1,
+                 'tuneOperation': 1}),
             mock.call.getTask(mock.ANY),
-            mock.call.getVolume(osv_matcher),
-            mock.call.deleteVolume(osv_matcher),
-            mock.call.modifyVolume(omv_matcher, {'newName': osv_matcher}),
             mock.call.logout()
         ]
 
@@ -1034,6 +1110,7 @@ class HP3PARBaseDriver(object):
         volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
                   'id': HP3PARBaseDriver.CLONE_ID,
                   'display_name': 'Foo Volume',
+                  'volume_type_id': None,
                   'size': 2,
                   'status': 'available',
                   'host': HP3PARBaseDriver.FAKE_HOST,
@@ -1048,40 +1125,71 @@ class HP3PARBaseDriver(object):
         self.assertIsNotNone(result)
         self.assertEqual((False, None), result)
 
-    def test_migrate_volume_diff_domain(self):
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_migrate_volume_diff_domain(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.volume_type
+
         conf = {
             'getStorageSystemInfo.return_value': {
                 'serialNumber': '1234'},
             'getTask.return_value': {
                 'status': 1},
-            'getCPG.side_effect':
-            lambda x: {'OpenStackCPG': {'domain': 'OpenStack'}}.get(x, {})
+            'getCPG.return_value': {},
+            'copyVolume.return_value': {'taskid': 1},
+            'getVolume.return_value': self.RETYPE_VOLUME_INFO_1
         }
 
-        self.setup_driver(mock_conf=conf)
+        mock_client = self.setup_driver(mock_conf=conf)
+
+        mock_client.getVolume.return_value = self.MANAGE_VOLUME_INFO
+        mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
+        mock_client.getTask.return_value = self.STATUS_DONE
 
         volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
                   'id': HP3PARBaseDriver.CLONE_ID,
                   'display_name': 'Foo Volume',
+                  'volume_type_id': None,
                   'size': 2,
                   'status': 'available',
                   'host': HP3PARBaseDriver.FAKE_HOST,
                   'source_volid': HP3PARBaseDriver.VOLUME_ID}
 
+        volume_name_3par = self.driver.common._encode_name(volume['id'])
+
         loc_info = 'HP3PARDriver:1234:CPG-FC1'
-        host = {'host': 'stack@3parfc1',
+        host = {'host': 'stack@3parfc1#CPG-FC1',
                 'capabilities': {'location_info': loc_info}}
 
         result = self.driver.migrate_volume(context.get_admin_context(),
                                             volume, host)
         self.assertIsNotNone(result)
-        self.assertEqual((False, None), result)
+        self.assertEqual((True, None), result)
 
-    def test_migrate_volume_attached(self):
+        osv_matcher = 'osv-' + volume_name_3par
 
-        mock_client = self.setup_driver()
+        expected = [
+            mock.call.modifyVolume(
+                osv_matcher,
+                {'comment': '{"qos": {}, "display_name": "Foo Volume"}',
+                 'snapCPG': 'CPG-FC1'}),
+            mock.call.modifyVolume(osv_matcher,
+                                   {'action': 6,
+                                    'userCPG': 'CPG-FC1',
+                                    'conversionOperation': 1,
+                                    'tuneOperation': 1}),
+            mock.call.getTask(mock.ANY),
+            mock.call.logout()
+        ]
+
+        mock_client.assert_has_calls(expected)
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_migrate_volume_attached(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_1
+        mock_client = self.setup_driver(mock_conf=self.RETYPE_CONF)
 
         volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
+                  'volume_type_id': None,
                   'id': HP3PARBaseDriver.CLONE_ID,
                   'display_name': 'Foo Volume',
                   'size': 2,
@@ -1090,18 +1198,70 @@ class HP3PARBaseDriver(object):
                   'source_volid': HP3PARBaseDriver.VOLUME_ID}
 
         volume_name_3par = self.driver.common._encode_name(volume['id'])
+        osv_matcher = 'osv-' + volume_name_3par
 
-        mock_client.getVLUNs.return_value = {
-            'members': [{'volumeName': 'osv-' + volume_name_3par}]}
+        loc_info = 'HP3PARDriver:1234567:CPG-FC1'
+
+        protocol = "FC"
+        if self.properties['driver_volume_type'] == "iscsi":
+            protocol = "iSCSI"
 
-        loc_info = 'HP3PARDriver:1234:CPG-FC1'
         host = {'host': 'stack@3parfc1',
-                'capabilities': {'location_info': loc_info}}
+                'capabilities': {'location_info': loc_info,
+                                 'storage_protocol': protocol}}
+
+        result = self.driver.migrate_volume(context.get_admin_context(),
+                                            volume, host)
+
+        new_comment = {"qos": {},
+                       "retype_test": "test comment"}
+        expected = [
+            mock.call.modifyVolume(osv_matcher,
+                                   {'comment': self.CommentMatcher(
+                                       self.assertEqual, new_comment),
+                                    'snapCPG': 'OpenStackCPGSnap'}),
+            mock.call.modifyVolume(osv_matcher,
+                                   {'action': 6,
+                                    'userCPG': 'OpenStackCPG',
+                                    'conversionOperation': 1,
+                                    'tuneOperation': 1}),
+            mock.call.getTask(1),
+            mock.call.logout()
+        ]
+        mock_client.assert_has_calls(expected)
+
+        self.assertIsNotNone(result)
+        self.assertEqual((True, {'host': 'stack@3parfc1#OpenStackCPG'}),
+                         result)
+
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_migrate_volume_attached_diff_protocol(self, _mock_volume_types):
+        _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_1
+        mock_client = self.setup_driver(mock_conf=self.RETYPE_CONF)
+
+        protocol = "OTHER"
+
+        volume = {'name': HP3PARBaseDriver.VOLUME_NAME,
+                  'volume_type_id': None,
+                  'id': HP3PARBaseDriver.CLONE_ID,
+                  'display_name': 'Foo Volume',
+                  'size': 2,
+                  'status': 'in-use',
+                  'host': HP3PARBaseDriver.FAKE_HOST,
+                  'source_volid': HP3PARBaseDriver.VOLUME_ID}
+
+        loc_info = 'HP3PARDriver:1234567:CPG-FC1'
+        host = {'host': 'stack@3parfc1',
+                'capabilities': {'location_info': loc_info,
+                                 'storage_protocol': protocol}}
 
         result = self.driver.migrate_volume(context.get_admin_context(),
                                             volume, host)
+
         self.assertIsNotNone(result)
         self.assertEqual((False, None), result)
+        expected = []
+        mock_client.assert_has_calls(expected)
 
     def test_attach_volume(self):
 
@@ -1962,6 +2122,7 @@ class HP3PARBaseDriver(object):
                 'volume_type': self.volume_type}}
 
         volume = {'display_name': None,
+                  'host': 'stack1@3pariscsi#POOL1',
                   'volume_type': 'gold',
                   'volume_type_id': 'bcfa9fa4-54a0-4340-a3d8-bfcf19aea65e',
                   'id': '007dbfce-7579-40bc-8f90-a20b3902283e'}
@@ -1971,7 +2132,8 @@ class HP3PARBaseDriver(object):
         mock_client.getTask.return_value = self.STATUS_DONE
         mock_client.getCPG.side_effect = [
             {'domain': 'domain1'},
-            {'domain': 'domain2'}
+            {'domain': 'domain2'},
+            {'domain': 'domain3'},
         ]
 
         unm_matcher = self.driver.common._get_3par_unm_name(self.volume['id'])
@@ -1989,6 +2151,7 @@ class HP3PARBaseDriver(object):
             mock.call.getVolume(unm_matcher),
             mock.call.modifyVolume(
                 unm_matcher, {'newName': osv_matcher, 'comment': mock.ANY}),
+            mock.call.getCPG('POOL1'),
             mock.call.getVolume(osv_matcher),
             mock.call.getCPG('testUserCpg0'),
             mock.call.getCPG('OpenStackCPG'),
@@ -3499,8 +3662,9 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase):
         self.setup_driver()
         volume = {'host': 'test-host@3pariscsi#pool_foo',
                   'id': 'd03338a9-9115-48a3-8dfc-35cdfcdc15a7'}
+        pool = volume_utils.extract_host(volume['host'], 'pool')
         model = self.driver.common.get_volume_settings_from_type_id('gold-id',
-                                                                    volume)
+                                                                    pool)
         self.assertEqual(model['cpg'], 'pool_foo')
 
     def test_get_model_update(self):
index 1f0157883cd72cd16e8610846d7dd1bdb2a07d6c..f4c75f651353fdc3c86cec4f18e9b035cafe0282 100644 (file)
@@ -42,6 +42,8 @@ import pprint
 import re
 import uuid
 
+import six
+
 from cinder.openstack.common import importutils
 hp3parclient = importutils.try_import("hp3parclient")
 if hp3parclient:
@@ -153,10 +155,11 @@ class HP3PARCommon(object):
         2.0.22 - HP 3PAR drivers should not claim to have 'infinite' space
         2.0.23 - Increase the hostname size from 23 to 31  Bug #1371242
         2.0.24 - Add pools (hp3par_cpg now accepts a list of CPGs)
+        2.0.25 - Migrate without losing type settings bug #1356608
 
     """
 
-    VERSION = "2.0.24"
+    VERSION = "2.0.25"
 
     stats = {}
 
@@ -943,33 +946,23 @@ class HP3PARCommon(object):
                 qos = self._get_qos_by_volume_type(volume_type)
         return hp3par_keys, qos, volume_type, vvs_name
 
-    def get_volume_settings_from_type_id(self, type_id, volume):
+    def get_volume_settings_from_type_id(self, type_id, pool):
         """Get 3PAR volume settings given a type_id.
 
         Combines type info and config settings to return a dictionary
         describing the 3PAR volume settings.  Does some validation (CPG).
-        Uses volume['host'] to determine default cpg (when not specified in
-        volume type specs).
+        Uses pool as the default cpg (when not specified in volume type specs).
 
-        :param type_id:
+        :param type_id: id of type to get settings for
+        :param pool: CPG to use if type does not have one set
         :return: dict
         """
 
         hp3par_keys, qos, volume_type, vvs_name = self.get_type_info(type_id)
 
-        # Default to 1st configured CPG unless we can extract pool from host.
-        default_cpg = self.config.hp3par_cpg[0]
-        try:
-            pool = volume_utils.extract_host(volume['host'], 'pool')
-            if pool:
-                default_cpg = pool
-                LOG.debug("Default CPG from volume['host'] is (%s)" %
-                          default_cpg)
-            else:
-                LOG.debug("Default CPG from volume['host'] not found")
-        except Exception as ex:
-            LOG.debug("Default CPG from volume['host'] not found due to (%s)" %
-                      ex)
+        # Default to pool extracted from host.
+        # If that doesn't work use the 1st CPG in the config as the default.
+        default_cpg = pool or self.config.hp3par_cpg[0]
 
         cpg = self._get_key_value(hp3par_keys, 'cpg', default_cpg)
         if cpg not in self.config.hp3par_cpg:
@@ -1011,7 +1004,7 @@ class HP3PARCommon(object):
                 'vvs_name': vvs_name, 'qos': qos,
                 'tpvv': tpvv, 'volume_type': volume_type}
 
-    def get_volume_settings_from_type(self, volume):
+    def get_volume_settings_from_type(self, volume, host=None):
         """Get 3PAR volume settings given a volume.
 
         Combines type info and config settings to return a dictionary
@@ -1019,13 +1012,19 @@ class HP3PARCommon(object):
         persona).
 
         :param volume:
+        :param host: Optional host to use for default pool.
         :return: dict
         """
 
         type_id = volume.get('volume_type_id', None)
 
-        volume_settings = self.get_volume_settings_from_type_id(type_id,
-                                                                volume)
+        pool = None
+        if host:
+            pool = volume_utils.extract_host(host['host'], 'pool')
+        else:
+            pool = volume_utils.extract_host(volume['host'], 'pool')
+
+        volume_settings = self.get_volume_settings_from_type_id(type_id, pool)
 
         # check for valid persona even if we don't use it until
         # attach time, this will give the end user notice that the
@@ -1430,58 +1429,31 @@ class HP3PARCommon(object):
                      host['host'] is its name, and host['capabilities'] is a
                      dictionary of its reported capabilities.
         :returns (False, None) if the driver does not support migration,
-                 (True, None) if successful
+                 (True, model_update) if successful
 
         """
 
-        dbg = {'id': volume['id'], 'host': host['host']}
+        dbg = {'id': volume['id'],
+               'host': host['host'],
+               'status': volume['status']}
         LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s.' % dbg)
 
-        false_ret = (False, None)
-
-        # Make sure volume is not attached
-        if volume['status'] != 'available':
-            LOG.debug('Volume is attached: migrate_volume: '
-                      'id=%(id)s, host=%(host)s.' % dbg)
-            return false_ret
-
-        if 'location_info' not in host['capabilities']:
-            return false_ret
-
-        info = host['capabilities']['location_info']
-        try:
-            (dest_type, dest_id, dest_cpg) = info.split(':')
-        except ValueError:
-            return false_ret
-
-        sys_info = self.client.getStorageSystemInfo()
-        if not (dest_type == 'HP3PARDriver' and
-                dest_id == sys_info['serialNumber']):
-            LOG.debug('Dest does not match: migrate_volume: '
-                      'id=%(id)s, host=%(host)s.' % dbg)
-            return false_ret
-
-        type_info = self.get_volume_settings_from_type(volume)
+        ret = False, None
 
-        if dest_cpg == type_info['cpg']:
-            LOG.debug('CPGs are the same: migrate_volume: '
-                      'id=%(id)s, host=%(host)s.' % dbg)
-            return false_ret
-
-        # Check to make sure CPGs are in the same domain
-        src_domain = self.get_domain(type_info['cpg'])
-        dst_domain = self.get_domain(dest_cpg)
-        if src_domain != dst_domain:
-            LOG.debug('CPGs in different domains: migrate_volume: '
-                      'id=%(id)s, host=%(host)s.' % dbg)
-            return false_ret
+        if volume['status'] in ['available', 'in-use']:
+            volume_type = None
+            if volume['volume_type_id']:
+                volume_type = self._get_volume_type(volume['volume_type_id'])
 
-        self._convert_to_base_volume(volume, new_cpg=dest_cpg)
+            try:
+                ret = self.retype(volume, volume_type, None, host)
+            except Exception as e:
+                LOG.info(_('3PAR driver cannot perform migration. '
+                           'Retype exception: %s') % six.text_type(e))
 
-        # TODO(Ramy) When volume retype is available,
-        # use that to change the type
         LOG.debug('leave: migrate_volume: id=%(id)s, host=%(host)s.' % dbg)
-        return (True, None)
+        LOG.debug('migrate_volume result: %s, %s' % ret)
+        return ret
 
     def _convert_to_base_volume(self, volume, new_cpg=None):
         try:
@@ -1709,7 +1681,7 @@ class HP3PARCommon(object):
                        {'status': status, 'volume_name': volume_name})
                 raise exception.VolumeBackendAPIException(msg)
 
-    def _retype_pre_checks(self, host, new_persona,
+    def _retype_pre_checks(self, volume, host, new_persona,
                            old_cpg, new_cpg,
                            new_snap_cpg):
         """Test retype parameters before making retype changes.
@@ -1759,7 +1731,7 @@ class HP3PARCommon(object):
 
         action = "volume:retype"
 
-        self._retype_pre_checks(host, new_persona,
+        self._retype_pre_checks(volume, host, new_persona,
                                 old_cpg, new_cpg,
                                 new_snap_cpg)
 
@@ -1803,10 +1775,18 @@ class HP3PARCommon(object):
         """
         volume_id = volume['id']
         volume_name = self._get_3par_vol_name(volume_id)
-        new_type_name = new_type['name']
-        new_type_id = new_type['id']
+        new_type_name = None
+        new_type_id = None
+        if new_type:
+            new_type_name = new_type['name']
+            new_type_id = new_type['id']
+        pool = None
+        if host:
+            pool = volume_utils.extract_host(host['host'], 'pool')
+        else:
+            pool = volume_utils.extract_host(volume['host'], 'pool')
         new_volume_settings = self.get_volume_settings_from_type_id(
-            new_type_id, volume)
+            new_type_id, pool)
         new_cpg = new_volume_settings['cpg']
         new_snap_cpg = new_volume_settings['snap_cpg']
         new_tpvv = new_volume_settings['tpvv']
@@ -1854,8 +1834,8 @@ class HP3PARCommon(object):
                        volume-type is not used here. This method uses None.
         :param new_type: A dictionary describing the volume type to convert to
         """
-        none_type_settings = self.get_volume_settings_from_type_id(
-            None, volume)
+        pool = volume_utils.extract_host(volume['host'], 'pool')
+        none_type_settings = self.get_volume_settings_from_type_id(None, pool)
         return self._retype_from_old_to_new(volume, new_type,
                                             none_type_settings, None)
 
@@ -1878,7 +1858,7 @@ class HP3PARCommon(object):
                                                       'new_type': new_type,
                                                       'diff': diff,
                                                       'host': host})
-        old_volume_settings = self.get_volume_settings_from_type(volume)
+        old_volume_settings = self.get_volume_settings_from_type(volume, host)
         return self._retype_from_old_to_new(volume, new_type,
                                             old_volume_settings, host)
 
@@ -1944,8 +1924,17 @@ class ModifyVolumeTask(flow_utils.CinderTask):
             comment_dict['qos'] = new_qos
         else:
             comment_dict['qos'] = {}
-        comment_dict['volume_type_name'] = new_type_name
-        comment_dict['volume_type_id'] = new_type_id
+
+        if new_type_name:
+            comment_dict['volume_type_name'] = new_type_name
+        else:
+            comment_dict.pop('volume_type_name', None)
+
+        if new_type_id:
+            comment_dict['volume_type_id'] = new_type_id
+        else:
+            comment_dict.pop('volume_type_id', None)
+
         return comment_dict
 
     def execute(self, common, volume_name, old_snap_cpg, new_snap_cpg,
index 38101065c4c90751f36a3ca821d696b45c1ffd11..1c5adadac84646cad914ee2f7ea55d1d07a1e6a6 100644 (file)
@@ -71,10 +71,11 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
                 is present.  bug #1360001
         2.0.8 - Fixing missing login/logout around attach/detach bug #1367429
         2.0.9 - Add support for pools with model update
+        2.0.10 - Migrate without losing type settings bug #1356608
 
     """
 
-    VERSION = "2.0.9"
+    VERSION = "2.0.10"
 
     def __init__(self, *args, **kwargs):
         super(HP3PARFCDriver, self).__init__(*args, **kwargs)
@@ -460,6 +461,14 @@ class HP3PARFCDriver(cinder.volume.driver.FibreChannelDriver):
 
     @utils.synchronized('3par', external=True)
     def migrate_volume(self, context, volume, host):
+
+        if volume['status'] == 'in-use':
+            protocol = host['capabilities']['storage_protocol']
+            if protocol != 'FC':
+                LOG.debug("3PAR FC driver cannot migrate in-use volume "
+                          "to a host with storage_protocol=%s." % protocol)
+                return False, None
+
         self.common.client_login()
         try:
             return self.common.migrate_volume(volume, host)
index 3500d9e7103e85ccf37cba00dc3c78481b40ed96..b2e7ae2eb9cc05b79171c0ab685c1bc4a8c77609 100644 (file)
@@ -75,10 +75,11 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
                 and hp3parclient 3.1.0.
         2.0.6 - Fixing missing login/logout around attach/detach bug #1367429
         2.0.7 - Add support for pools with model update
+        2.0.8 - Migrate without losing type settings bug #1356608
 
     """
 
-    VERSION = "2.0.7"
+    VERSION = "2.0.8"
 
     def __init__(self, *args, **kwargs):
         super(HP3PARISCSIDriver, self).__init__(*args, **kwargs)
@@ -667,6 +668,14 @@ class HP3PARISCSIDriver(cinder.volume.driver.ISCSIDriver):
 
     @utils.synchronized('3par', external=True)
     def migrate_volume(self, context, volume, host):
+
+        if volume['status'] == 'in-use':
+            protocol = host['capabilities']['storage_protocol']
+            if protocol != 'iSCSI':
+                LOG.debug("3PAR ISCSI driver cannot migrate in-use volume "
+                          "to a host with storage_protocol=%s." % protocol)
+                return False, None
+
         self.common.client_login()
         try:
             return self.common.migrate_volume(volume, host)