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:
parent
d66fd66618
commit
fc00a865fe
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user