]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR fix create_cloned_volume for larger size
authorWalter A. Boring IV <walter.boring@hpe.com>
Fri, 11 Mar 2016 10:28:08 +0000 (02:28 -0800)
committerWalter A. Boring IV <walter.boring@hpe.com>
Wed, 16 Mar 2016 10:39:12 +0000 (03:39 -0700)
This patch fixes the 3PAR driver's create_cloned_volume
call in the case where a larger size for the volume was
specified.  When the new volume size is the same as the
source volume, we still use an 'online' copy, which is
3PAR speak for background copy.  When the new volume size
is larger than the source volume, we have to do an offline
copy, which entails creating a fresh volume with the requested
size, and then copying the bits from the old volume to the new.
This copy happens on the 3PAR itself and can take some time to
complete.

This patch also updates the minimum client version to 4.2.0

Change-Id: Ie2da441b32017f38e743189060209084e1f20974
Closes-bug: #1554740

cinder/tests/unit/fake_hpe_3par_client.py
cinder/tests/unit/test_hpe3par.py
cinder/volume/drivers/hpe/hpe_3par_common.py

index 56c5dd76d0f5c7e2677a8aacfdc8f0984fbc206e..b4567efd5f42c80c5116b9c8037919c6598a33b2 100644 (file)
@@ -22,7 +22,7 @@ import mock
 from cinder.tests.unit import fake_hpe_client_exceptions as hpeexceptions
 
 hpe3par = mock.Mock()
-hpe3par.version = "4.1.0"
+hpe3par.version = "4.2.0"
 hpe3par.exceptions = hpeexceptions
 
 sys.modules['hpe3parclient'] = hpe3par
index 463ce3ee203b8b6a7593a901763b27dcfb159188..3370684fe164e98fc01bc25f2badf7deb64d1fb2 100644 (file)
@@ -1740,7 +1740,8 @@ class HPE3PARBaseDriver(object):
                                                        HPE3PAR_CPG2),
                       'source_volid': HPE3PARBaseDriver.VOLUME_ID}
             src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
-                        'name': HPE3PARBaseDriver.VOLUME_NAME}
+                        'name': HPE3PARBaseDriver.VOLUME_NAME,
+                        'size': 2}
             model_update = self.driver.create_cloned_volume(volume, src_vref)
             self.assertIsNone(model_update)
 
@@ -1765,6 +1766,53 @@ class HPE3PARBaseDriver(object):
                 expected +
                 self.standard_logout)
 
+    def test_create_cloned_volume_offline_copy(self):
+        # setup_mock_client drive with default configuration
+        # and return the mock HTTP 3PAR client
+        mock_client = self.setup_driver()
+        mock_client.getVolume.return_value = {'name': mock.ANY}
+        task_id = 1
+        mock_client.copyVolume.return_value = {'taskid': task_id}
+        mock_client.getTask.return_value = {'status': 1}
+        with mock.patch.object(hpecommon.HPE3PARCommon,
+                               '_create_client') as mock_create_client:
+            mock_create_client.return_value = mock_client
+
+            volume = {'name': HPE3PARBaseDriver.VOLUME_NAME,
+                      'id': HPE3PARBaseDriver.CLONE_ID,
+                      'display_name': 'Foo Volume',
+                      'size': 5,
+                      'host': volume_utils.append_host(self.FAKE_HOST,
+                                                       HPE3PAR_CPG2),
+                      'source_volid': HPE3PARBaseDriver.VOLUME_ID}
+            src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
+                        'name': HPE3PARBaseDriver.VOLUME_NAME,
+                        'size': 2}
+            model_update = self.driver.create_cloned_volume(volume, src_vref)
+            self.assertIsNone(model_update)
+
+            common = hpecommon.HPE3PARCommon(None)
+            vol_name = common._get_3par_vol_name(volume['id'])
+            src_vol_name = common._get_3par_vol_name(src_vref['id'])
+            optional = {'priority': 1}
+            comment = mock.ANY
+
+            expected = [
+                mock.call.createVolume(vol_name, 'fakepool',
+                                       5120, comment),
+                mock.call.copyVolume(
+                    src_vol_name,
+                    vol_name,
+                    None,
+                    optional=optional),
+                mock.call.getTask(task_id),
+            ]
+
+            mock_client.assert_has_calls(
+                self.standard_login +
+                expected +
+                self.standard_logout)
+
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_create_cloned_qos_volume(self, _mock_volume_types):
         _mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_2
@@ -1776,7 +1824,8 @@ class HPE3PARBaseDriver(object):
             mock_create_client.return_value = mock_client
 
             src_vref = {'id': HPE3PARBaseDriver.CLONE_ID,
-                        'name': HPE3PARBaseDriver.VOLUME_NAME}
+                        'name': HPE3PARBaseDriver.VOLUME_NAME,
+                        'size': 2}
             volume = self.volume_qos.copy()
             host = "TEST_HOST"
             pool = "TEST_POOL"
index 92a3f67e7f3a3539fbb95ed5a78033ac489d4dc2..889cab5b97da1057fe2576a43142e4df96f9941b 100644 (file)
@@ -71,7 +71,7 @@ from taskflow.patterns import linear_flow
 
 LOG = logging.getLogger(__name__)
 
-MIN_CLIENT_VERSION = '4.1.0'
+MIN_CLIENT_VERSION = '4.2.0'
 DEDUP_API_VERSION = 30201120
 FLASH_CACHE_API_VERSION = 30201200
 SRSTATLD_API_VERSION = 30201200
@@ -230,10 +230,11 @@ class HPE3PARCommon(object):
         3.0.15 - Update replication to version 2.1
         3.0.16 - Use same LUN ID for each VLUN path #1551994
         3.0.17 - Don't fail on clearing 3PAR object volume key. bug #1546392
+        3.0.18 - create_cloned_volume account for larger size.  bug #1554740
 
     """
 
-    VERSION = "3.0.17"
+    VERSION = "3.0.18"
 
     stats = {}
 
@@ -1971,28 +1972,62 @@ class HPE3PARCommon(object):
     def create_cloned_volume(self, volume, src_vref):
         try:
             vol_name = self._get_3par_vol_name(volume['id'])
-            # create a temporary snapshot
-            snapshot = self._create_temp_snapshot(src_vref)
-
-            type_info = self.get_volume_settings_from_type(volume)
+            src_vol_name = self._get_3par_vol_name(src_vref['id'])
+
+            # if the sizes of the 2 volumes are the same
+            # we can do an online copy, which is a background process
+            # on the 3PAR that makes the volume instantly available.
+            # We can't resize a volume, while it's being copied.
+            if volume['size'] == src_vref['size']:
+                LOG.debug("Creating a clone of same size, using online copy.")
+                # create a temporary snapshot
+                snapshot = self._create_temp_snapshot(src_vref)
+
+                type_info = self.get_volume_settings_from_type(volume)
+                cpg = type_info['cpg']
 
-            # make the 3PAR copy the contents.
-            # can't delete the original until the copy is done.
-            cpg = type_info['cpg']
-            self._copy_volume(snapshot['name'], vol_name, cpg=cpg,
-                              snap_cpg=type_info['snap_cpg'],
-                              tpvv=type_info['tpvv'],
-                              tdvv=type_info['tdvv'])
+                # make the 3PAR copy the contents.
+                # can't delete the original until the copy is done.
+                self._copy_volume(snapshot['name'], vol_name, cpg=cpg,
+                                  snap_cpg=type_info['snap_cpg'],
+                                  tpvv=type_info['tpvv'],
+                                  tdvv=type_info['tdvv'])
+
+                # v2 replication check
+                replication_flag = False
+                if self._volume_of_replicated_type(volume) and (
+                   self._do_volume_replication_setup(volume)):
+                    replication_flag = True
+
+                return self._get_model_update(volume['host'], cpg,
+                                              replication=replication_flag,
+                                              provider_location=self.client.id)
+            else:
+                # The size of the new volume is different, so we have to
+                # copy the volume and wait.  Do the resize after the copy
+                # is complete.
+                LOG.debug("Clone a volume with a different target size. "
+                          "Using non-online copy.")
+
+                # we first have to create the destination volume
+                model_update = self.create_volume(volume)
+
+                optional = {'priority': 1}
+                body = self.client.copyVolume(src_vol_name, vol_name, None,
+                                              optional=optional)
+                task_id = body['taskid']
 
-            # v2 replication check
-            replication_flag = False
-            if self._volume_of_replicated_type(volume) and (
-               self._do_volume_replication_setup(volume)):
-                replication_flag = True
+                task_status = self._wait_for_task_completion(task_id)
+                if task_status['status'] is not self.client.TASK_DONE:
+                    dbg = {'status': task_status, 'id': volume['id']}
+                    msg = _('Copy volume task failed: create_cloned_volume '
+                            'id=%(id)s, status=%(status)s.') % dbg
+                    raise exception.CinderException(msg)
+                else:
+                    LOG.debug('Copy volume completed: create_cloned_volume: '
+                              'id=%s.', volume['id'])
 
-            return self._get_model_update(volume['host'], cpg,
-                                          replication=replication_flag,
-                                          provider_location=self.client.id)
+                return model_update
 
         except hpeexceptions.HTTPForbidden:
             raise exception.NotAuthorized()
@@ -2384,6 +2419,28 @@ class HPE3PARCommon(object):
 
         return {'_name_id': name_id, 'provider_location': provider_location}
 
+    def _wait_for_task_completion(self, task_id):
+        """This waits for a 3PAR background task complete or fail.
+
+        This looks for a task to get out of the 'active' state.
+        """
+        # Wait for the physical copy task to complete
+        def _wait_for_task(task_id):
+            status = self.client.getTask(task_id)
+            LOG.debug("3PAR Task id %(id)s status = %(status)s",
+                      {'id': task_id,
+                       'status': status['status']})
+            if status['status'] is not self.client.TASK_ACTIVE:
+                self._task_status = status
+                raise loopingcall.LoopingCallDone()
+
+        self._task_status = None
+        timer = loopingcall.FixedIntervalLoopingCall(
+            _wait_for_task, task_id)
+        timer.start(interval=1).wait()
+
+        return self._task_status
+
     def _convert_to_base_volume(self, volume, new_cpg=None):
         try:
             type_info = self.get_volume_settings_from_type(volume)
@@ -2405,23 +2462,10 @@ class HPE3PARCommon(object):
             LOG.debug('Copy volume scheduled: convert_to_base_volume: '
                       'id=%s.', volume['id'])
 
-            # Wait for the physical copy task to complete
-            def _wait_for_task(task_id):
-                status = self.client.getTask(task_id)
-                LOG.debug("3PAR Task id %(id)s status = %(status)s",
-                          {'id': task_id,
-                           'status': status['status']})
-                if status['status'] is not self.client.TASK_ACTIVE:
-                    self._task_status = status
-                    raise loopingcall.LoopingCallDone()
-
-            self._task_status = None
-            timer = loopingcall.FixedIntervalLoopingCall(
-                _wait_for_task, task_id)
-            timer.start(interval=1).wait()
-
-            if self._task_status['status'] is not self.client.TASK_DONE:
-                dbg = {'status': self._task_status, 'id': volume['id']}
+            task_status = self._wait_for_task_completion(task_id)
+
+            if task_status['status'] is not self.client.TASK_DONE:
+                dbg = {'status': task_status, 'id': volume['id']}
                 msg = _('Copy volume task failed: convert_to_base_volume: '
                         'id=%(id)s, status=%(status)s.') % dbg
                 raise exception.CinderException(msg)