Merge "3PAR fix create_cloned_volume for larger size"

This commit is contained in:
Jenkins 2016-03-17 02:52:31 +00:00 committed by Gerrit Code Review
commit 4f9c5d47b9
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)