Merge "3PAR fix create_cloned_volume for larger size"
This commit is contained in:
commit
4f9c5d47b9
@ -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…
Reference in New Issue
Block a user