backup of active 3par ISCSI bootable volume fails

1.Backup of ISCSI volume with chap enabled on it, fails
because it choose online copy code path where copy
volume is background process on 3par and we do not
wait for process to complete and setting of any attribute
on cloned volume failed during online copy of data.
This patch ensures it choses non online copy code path
for above scenario.

2.Backup of attached volume fails with snapCPG error
Sometime, during online clone operation, we set snapCPG and
userCPG on cloned volume but when we get cpg of
cloned volume we get an error as these attributes
do not set for cloned volume on 3par.
This patch ensure if cpg is not available for volume
from 3par it will take default value from 'host' attribute
from volume param.

Change-Id: I8dab8fd4e56c38557c46e4ae9a01fb6fead2a2a8
Closes-Bug: #1644238
Closes-Bug: #1646396
(cherry picked from commit 6543c0e13d)
This commit is contained in:
stack 2016-11-23 06:27:13 -08:00 committed by Kushal Wathore
parent 412a97ecfa
commit 7e59cad656
2 changed files with 262 additions and 11 deletions

View File

@ -1743,6 +1743,83 @@ class HPE3PARBaseDriver(object):
expected +
self.standard_logout)
def test_get_cpg_with_volume_return_usercpg(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,
'userCPG': HPE3PAR_CPG2}
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
volume = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'display_name': 'Foo Volume',
'size': 2,
'host': volume_utils.append_host(self.FAKE_HOST,
HPE3PAR_CPG2)}
common = self.driver._login()
user_cpg = common.get_cpg(volume)
common = hpecommon.HPE3PARCommon(None)
vol_name = common._get_3par_vol_name(volume['id'])
self.assertEqual(HPE3PAR_CPG2, user_cpg)
expected = [mock.call.getVolume(vol_name)]
mock_client.assert_has_calls(
self.standard_login +
expected)
def test_get_cpg_with_volume_return_snapcpg(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,
'snapCPG': HPE3PAR_CPG2}
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
volume = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'display_name': 'Foo Volume',
'size': 2,
'host': volume_utils.append_host(self.FAKE_HOST,
HPE3PAR_CPG2)}
common = self.driver._login()
snap_cpg = common.get_cpg(volume, allowSnap=True)
common = hpecommon.HPE3PARCommon(None)
vol_name = common._get_3par_vol_name(volume['id'])
self.assertEqual(HPE3PAR_CPG2, snap_cpg)
expected = [mock.call.getVolume(vol_name)]
mock_client.assert_has_calls(
self.standard_login +
expected)
def test_get_cpg_with_volume_return_no_cpg(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}
with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client:
mock_create_client.return_value = mock_client
volume = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'display_name': 'Foo Volume',
'size': 2,
'host': volume_utils.append_host(self.FAKE_HOST,
HPE3PAR_CPG2)}
common = self.driver._login()
cpg_name = common.get_cpg(volume)
common = hpecommon.HPE3PARCommon(None)
vol_name = common._get_3par_vol_name(volume['id'])
self.assertEqual(HPE3PAR_CPG2, cpg_name)
expected = [mock.call.getVolume(vol_name)]
mock_client.assert_has_calls(
self.standard_login +
expected)
def test_create_cloned_volume(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
@ -1762,7 +1839,7 @@ class HPE3PARBaseDriver(object):
'source_volid': HPE3PARBaseDriver.VOLUME_ID}
src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'size': 2}
'size': 2, 'status': 'available'}
model_update = self.driver.create_cloned_volume(volume, src_vref)
self.assertIsNone(model_update)
@ -1787,6 +1864,153 @@ class HPE3PARBaseDriver(object):
expected +
self.standard_logout)
def test_backup_iscsi_volume_with_chap_disabled(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}
mock_client.copyVolume.return_value = {'taskid': 1}
mock_client.getVolumeMetaData.side_effect = hpeexceptions.HTTPNotFound
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': 2,
'host': volume_utils.append_host(self.FAKE_HOST,
HPE3PAR_CPG2)}
src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'size': 2, 'status': 'backing-up'}
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(src_vref['id'])
# snapshot name is random
snap_name = mock.ANY
optional = mock.ANY
expected = [
mock.call.createSnapshot(snap_name, vol_name, optional),
mock.call.getVolume(snap_name),
mock.call.copyVolume(
snap_name,
'osv-0DM4qZEVSKON-AAAAAAAAA',
HPE3PAR_CPG2,
{'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
'tdvv': False, 'online': True})]
mock_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
def test_create_clone_iscsi_volume_with_chap_disabled(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
config = self.setup_configuration()
config.hpe3par_iscsi_chap_enabled = True
mock_client = self.setup_driver(config=config)
mock_client.getVolume.return_value = {'name': mock.ANY}
mock_client.copyVolume.return_value = {'taskid': 1}
mock_client.getVolumeMetaData.side_effect = hpeexceptions.HTTPNotFound
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': 2,
'host': volume_utils.append_host(self.FAKE_HOST,
HPE3PAR_CPG2)}
src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'size': 2, 'status': 'available'}
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(src_vref['id'])
# snapshot name is random
snap_name = mock.ANY
optional = mock.ANY
expected = [
mock.call.getVolumeMetaData(vol_name,
'HPQ-cinder-CHAP-name'),
mock.call.createSnapshot(snap_name, vol_name, optional),
mock.call.getVolume(snap_name),
mock.call.copyVolume(
snap_name,
'osv-0DM4qZEVSKON-AAAAAAAAA',
HPE3PAR_CPG2,
{'snapCPG': 'OpenStackCPGSnap', 'tpvv': True,
'tdvv': False, 'online': True})]
mock_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
def test_backup_iscsi_volume_with_chap_enabled(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
config = self.setup_configuration()
config.hpe3par_iscsi_chap_enabled = True
mock_client = self.setup_driver(config=config)
mock_client.getVolume.return_value = {'name': mock.ANY}
task_id = 1
mock_client.copyVolume.return_value = {'taskid': task_id}
mock_client.getVolumeMetaData.return_value = {
'value': 'random-key'}
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': 5, 'status': 'backing-up'}
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.getVolumeMetaData(src_vol_name,
'HPQ-cinder-CHAP-name'),
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)
def test_create_cloned_volume_offline_copy(self):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
@ -1808,7 +2032,7 @@ class HPE3PARBaseDriver(object):
'source_volid': HPE3PARBaseDriver.VOLUME_ID}
src_vref = {'id': HPE3PARBaseDriver.VOLUME_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'size': 2}
'size': 2, 'status': 'available'}
model_update = self.driver.create_cloned_volume(volume, src_vref)
self.assertIsNone(model_update)
@ -1846,7 +2070,7 @@ class HPE3PARBaseDriver(object):
src_vref = {'id': HPE3PARBaseDriver.CLONE_ID,
'name': HPE3PARBaseDriver.VOLUME_NAME,
'size': 2}
'size': 2, 'status': 'available'}
volume = self.volume_qos.copy()
host = "TEST_HOST"
pool = "TEST_POOL"

View File

@ -244,10 +244,13 @@ class HPE3PARCommon(object):
3.0.24 - Fix terminate connection on failover
3.0.25 - Fix delete volume when online clone is active. bug #1349639
3.0.26 - Fix concurrent snapshot delete conflict. bug #1600104
3.0.27 - Fix snapCPG error during backup of attached volume.
Bug #1646396 and also ,Fix backup of attached ISCSI
and CHAP enabled volume.bug #1644238.
"""
VERSION = "3.0.26"
VERSION = "3.0.27"
stats = {}
@ -1626,11 +1629,18 @@ class HPE3PARCommon(object):
def get_cpg(self, volume, allowSnap=False):
volume_name = self._get_3par_vol_name(volume['id'])
vol = self.client.getVolume(volume_name)
# Search for 'userCPG' in the get volume REST API,
# if found return userCPG , else search for snapCPG attribute
# when allowSnap=True. For the cases where 3PAR REST call for
# get volume doesn't have either userCPG or snapCPG ,
# take the default value of cpg from 'host' attribute from volume param
LOG.debug("get volume response is: %s", vol)
if 'userCPG' in vol:
return vol['userCPG']
elif allowSnap:
elif allowSnap and 'snapCPG' in vol:
return vol['snapCPG']
return None
else:
return volume_utils.extract_host(volume['host'], 'pool')
def _get_3par_vol_comment(self, volume_name):
vol = self.client.getVolume(volume_name)
@ -1983,13 +1993,31 @@ class HPE3PARCommon(object):
try:
vol_name = self._get_3par_vol_name(volume['id'])
src_vol_name = self._get_3par_vol_name(src_vref['id'])
back_up_process = False
vol_chap_enabled = False
# if the sizes of the 2 volumes are the same
# Check whether a volume is ISCSI and CHAP enabled on it.
if self._client_conf['hpe3par_iscsi_chap_enabled']:
try:
vol_chap_enabled = self.client.getVolumeMetaData(
src_vol_name, 'HPQ-cinder-CHAP-name')['value']
except hpeexceptions.HTTPNotFound:
LOG.debug("CHAP is not enabled on volume %(vol)s ",
{'vol': src_vref['id']})
vol_chap_enabled = False
# Check whether a process is a backup
if str(src_vref['status']) == 'backing-up':
back_up_process = True
# if the sizes of the 2 volumes are the same and except backup
# process for ISCSI volume with chap enabled on it.
# 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.")
if volume['size'] == src_vref['size'] and not (
back_up_process and vol_chap_enabled):
LOG.debug("Creating a clone of volume, using online copy.")
# create a temporary snapshot
snapshot = self._create_temp_snapshot(src_vref)
@ -2016,8 +2044,7 @@ class HPE3PARCommon(object):
# 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.")
LOG.debug("Creating a clone of volume, using non-online copy.")
# we first have to create the destination volume
model_update = self.create_volume(volume)