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)
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
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"
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
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 = {}
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()
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)
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)