3PAR fix create_cloned_volume for larger size

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
This commit is contained in:
Walter A. Boring IV 2016-03-11 02:28:08 -08:00
parent d66fd66618
commit fc00a865fe
3 changed files with 132 additions and 39 deletions

View 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

View 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"

View 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)
src_vol_name = self._get_3par_vol_name(src_vref['id'])
type_info = self.get_volume_settings_from_type(volume)
# 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)
# 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'])
type_info = self.get_volume_settings_from_type(volume)
cpg = type_info['cpg']
# v2 replication check
replication_flag = False
if self._volume_of_replicated_type(volume) and (
self._do_volume_replication_setup(volume)):
replication_flag = True
# 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'])
return self._get_model_update(volume['host'], cpg,
replication=replication_flag,
provider_location=self.client.id)
# 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']
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 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()
task_status = self._wait_for_task_completion(task_id)
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']}
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)