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
|
from cinder.tests.unit import fake_hpe_client_exceptions as hpeexceptions
|
||||||
|
|
||||||
hpe3par = mock.Mock()
|
hpe3par = mock.Mock()
|
||||||
hpe3par.version = "4.1.0"
|
hpe3par.version = "4.2.0"
|
||||||
hpe3par.exceptions = hpeexceptions
|
hpe3par.exceptions = hpeexceptions
|
||||||
|
|
||||||
sys.modules['hpe3parclient'] = hpe3par
|
sys.modules['hpe3parclient'] = hpe3par
|
||||||
|
@ -1740,7 +1740,8 @@ class HPE3PARBaseDriver(object):
|
|||||||
HPE3PAR_CPG2),
|
HPE3PAR_CPG2),
|
||||||
'source_volid': HPE3PARBaseDriver.VOLUME_ID}
|
'source_volid': HPE3PARBaseDriver.VOLUME_ID}
|
||||||
src_vref = {'id': 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)
|
model_update = self.driver.create_cloned_volume(volume, src_vref)
|
||||||
self.assertIsNone(model_update)
|
self.assertIsNone(model_update)
|
||||||
|
|
||||||
@ -1765,6 +1766,53 @@ class HPE3PARBaseDriver(object):
|
|||||||
expected +
|
expected +
|
||||||
self.standard_logout)
|
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')
|
@mock.patch.object(volume_types, 'get_volume_type')
|
||||||
def test_create_cloned_qos_volume(self, _mock_volume_types):
|
def test_create_cloned_qos_volume(self, _mock_volume_types):
|
||||||
_mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_2
|
_mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_2
|
||||||
@ -1776,7 +1824,8 @@ class HPE3PARBaseDriver(object):
|
|||||||
mock_create_client.return_value = mock_client
|
mock_create_client.return_value = mock_client
|
||||||
|
|
||||||
src_vref = {'id': HPE3PARBaseDriver.CLONE_ID,
|
src_vref = {'id': HPE3PARBaseDriver.CLONE_ID,
|
||||||
'name': HPE3PARBaseDriver.VOLUME_NAME}
|
'name': HPE3PARBaseDriver.VOLUME_NAME,
|
||||||
|
'size': 2}
|
||||||
volume = self.volume_qos.copy()
|
volume = self.volume_qos.copy()
|
||||||
host = "TEST_HOST"
|
host = "TEST_HOST"
|
||||||
pool = "TEST_POOL"
|
pool = "TEST_POOL"
|
||||||
|
@ -71,7 +71,7 @@ from taskflow.patterns import linear_flow
|
|||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MIN_CLIENT_VERSION = '4.1.0'
|
MIN_CLIENT_VERSION = '4.2.0'
|
||||||
DEDUP_API_VERSION = 30201120
|
DEDUP_API_VERSION = 30201120
|
||||||
FLASH_CACHE_API_VERSION = 30201200
|
FLASH_CACHE_API_VERSION = 30201200
|
||||||
SRSTATLD_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.15 - Update replication to version 2.1
|
||||||
3.0.16 - Use same LUN ID for each VLUN path #1551994
|
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.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 = {}
|
stats = {}
|
||||||
|
|
||||||
@ -1971,14 +1972,22 @@ class HPE3PARCommon(object):
|
|||||||
def create_cloned_volume(self, volume, src_vref):
|
def create_cloned_volume(self, volume, src_vref):
|
||||||
try:
|
try:
|
||||||
vol_name = self._get_3par_vol_name(volume['id'])
|
vol_name = self._get_3par_vol_name(volume['id'])
|
||||||
|
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
|
# create a temporary snapshot
|
||||||
snapshot = self._create_temp_snapshot(src_vref)
|
snapshot = self._create_temp_snapshot(src_vref)
|
||||||
|
|
||||||
type_info = self.get_volume_settings_from_type(volume)
|
type_info = self.get_volume_settings_from_type(volume)
|
||||||
|
cpg = type_info['cpg']
|
||||||
|
|
||||||
# make the 3PAR copy the contents.
|
# make the 3PAR copy the contents.
|
||||||
# can't delete the original until the copy is done.
|
# can't delete the original until the copy is done.
|
||||||
cpg = type_info['cpg']
|
|
||||||
self._copy_volume(snapshot['name'], vol_name, cpg=cpg,
|
self._copy_volume(snapshot['name'], vol_name, cpg=cpg,
|
||||||
snap_cpg=type_info['snap_cpg'],
|
snap_cpg=type_info['snap_cpg'],
|
||||||
tpvv=type_info['tpvv'],
|
tpvv=type_info['tpvv'],
|
||||||
@ -1993,6 +2002,32 @@ class HPE3PARCommon(object):
|
|||||||
return self._get_model_update(volume['host'], cpg,
|
return self._get_model_update(volume['host'], cpg,
|
||||||
replication=replication_flag,
|
replication=replication_flag,
|
||||||
provider_location=self.client.id)
|
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:
|
except hpeexceptions.HTTPForbidden:
|
||||||
raise exception.NotAuthorized()
|
raise exception.NotAuthorized()
|
||||||
@ -2384,6 +2419,28 @@ class HPE3PARCommon(object):
|
|||||||
|
|
||||||
return {'_name_id': name_id, 'provider_location': provider_location}
|
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):
|
def _convert_to_base_volume(self, volume, new_cpg=None):
|
||||||
try:
|
try:
|
||||||
type_info = self.get_volume_settings_from_type(volume)
|
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: '
|
LOG.debug('Copy volume scheduled: convert_to_base_volume: '
|
||||||
'id=%s.', volume['id'])
|
'id=%s.', volume['id'])
|
||||||
|
|
||||||
# Wait for the physical copy task to complete
|
task_status = self._wait_for_task_completion(task_id)
|
||||||
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
|
if task_status['status'] is not self.client.TASK_DONE:
|
||||||
timer = loopingcall.FixedIntervalLoopingCall(
|
dbg = {'status': task_status, 'id': volume['id']}
|
||||||
_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']}
|
|
||||||
msg = _('Copy volume task failed: convert_to_base_volume: '
|
msg = _('Copy volume task failed: convert_to_base_volume: '
|
||||||
'id=%(id)s, status=%(status)s.') % dbg
|
'id=%(id)s, status=%(status)s.') % dbg
|
||||||
raise exception.CinderException(msg)
|
raise exception.CinderException(msg)
|
||||||
|
Loading…
Reference in New Issue
Block a user