Merge "3PAR: Added Replication Feature In Retype Flow"
This commit is contained in:
commit
9da1945871
@ -1697,6 +1697,293 @@ class HPE3PARBaseDriver(object):
|
|||||||
]
|
]
|
||||||
mock_client.assert_has_calls(expected + self.standard_logout)
|
mock_client.assert_has_calls(expected + self.standard_logout)
|
||||||
|
|
||||||
|
@mock.patch.object(volume_types, 'get_volume_type')
|
||||||
|
def test_retype_non_rep_type_to_rep_type(self, _mock_volume_types):
|
||||||
|
|
||||||
|
conf = self.setup_configuration()
|
||||||
|
self.replication_targets[0]['replication_mode'] = 'periodic'
|
||||||
|
conf.replication_device = self.replication_targets
|
||||||
|
mock_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = (
|
||||||
|
{'id': self.CLIENT_ID})
|
||||||
|
mock_client.getRemoteCopyGroup.side_effect = (
|
||||||
|
hpeexceptions.HTTPNotFound)
|
||||||
|
mock_client.getCPG.return_value = {'domain': None}
|
||||||
|
mock_replicated_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = {
|
||||||
|
'id': self.REPLICATION_CLIENT_ID,
|
||||||
|
'serialNumber': '1234567'
|
||||||
|
}
|
||||||
|
mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
|
||||||
|
mock_client.getTask.return_value = self.STATUS_DONE
|
||||||
|
|
||||||
|
_mock_volume_types.return_value = {
|
||||||
|
'name': 'replicated',
|
||||||
|
'extra_specs': {
|
||||||
|
'replication_enabled': '<is> True',
|
||||||
|
'replication:mode': 'periodic',
|
||||||
|
'replication:sync_period': '900',
|
||||||
|
'volume_type': self.volume_type_replicated}}
|
||||||
|
|
||||||
|
mock_client.getVolume.return_value = {
|
||||||
|
'name': mock.ANY,
|
||||||
|
'snapCPG': mock.ANY,
|
||||||
|
'comment': "{'display_name': 'Foo Volume'}",
|
||||||
|
'provisioningType': mock.ANY,
|
||||||
|
'userCPG': 'OpenStackCPG',
|
||||||
|
'snapCPG': 'OpenStackCPGSnap'}
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_client') as mock_create_client, \
|
||||||
|
mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_replication_client') as mock_replication_client:
|
||||||
|
mock_create_client.return_value = mock_client
|
||||||
|
mock_replication_client.return_value = mock_replicated_client
|
||||||
|
|
||||||
|
retyped = self.driver.retype(
|
||||||
|
self.ctxt,
|
||||||
|
self.volume,
|
||||||
|
self.volume_type_replicated,
|
||||||
|
None,
|
||||||
|
self.RETYPE_HOST)
|
||||||
|
self.assertTrue(retyped)
|
||||||
|
backend_id = self.replication_targets[0]['backend_id']
|
||||||
|
expected = [
|
||||||
|
mock.call.createRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
[{'userCPG': HPE3PAR_CPG_REMOTE,
|
||||||
|
'targetName': backend_id,
|
||||||
|
'mode': PERIODIC_MODE,
|
||||||
|
'snapCPG': HPE3PAR_CPG_REMOTE}],
|
||||||
|
{'localUserCPG': HPE3PAR_CPG,
|
||||||
|
'localSnapCPG': HPE3PAR_CPG_SNAP}),
|
||||||
|
mock.call.addVolumeToRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
self.VOLUME_3PAR_NAME,
|
||||||
|
[{'secVolumeName': self.VOLUME_3PAR_NAME,
|
||||||
|
'targetName': backend_id}],
|
||||||
|
optional={'volumeAutoCreation': True}),
|
||||||
|
mock.call.modifyRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
{'targets': [{'syncPeriod': SYNC_PERIOD,
|
||||||
|
'targetName': backend_id}]}),
|
||||||
|
mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
|
||||||
|
mock_client.assert_has_calls(expected + self.standard_logout)
|
||||||
|
|
||||||
|
@mock.patch.object(volume_types, 'get_volume_type')
|
||||||
|
def test_retype_rep_type_to_non_rep_type(self, _mock_volume_types):
|
||||||
|
|
||||||
|
conf = self.setup_configuration()
|
||||||
|
self.replication_targets[0]['replication_mode'] = 'periodic'
|
||||||
|
conf.replication_device = self.replication_targets
|
||||||
|
mock_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = (
|
||||||
|
{'id': self.CLIENT_ID})
|
||||||
|
mock_client.getRemoteCopyGroup.side_effect = (
|
||||||
|
hpeexceptions.HTTPNotFound)
|
||||||
|
mock_client.getCPG.return_value = {'domain': None}
|
||||||
|
mock_replicated_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = {
|
||||||
|
'id': self.REPLICATION_CLIENT_ID,
|
||||||
|
'serialNumber': '1234567'
|
||||||
|
}
|
||||||
|
mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
|
||||||
|
mock_client.getTask.return_value = self.STATUS_DONE
|
||||||
|
|
||||||
|
volume_1 = {'name': self.VOLUME_NAME,
|
||||||
|
'id': self.VOLUME_ID,
|
||||||
|
'display_name': 'Foo Volume',
|
||||||
|
'replication_status': 'disabled',
|
||||||
|
'provider_location': self.CLIENT_ID,
|
||||||
|
'size': 2,
|
||||||
|
'host': self.FAKE_CINDER_HOST,
|
||||||
|
'volume_type': 'replicated',
|
||||||
|
'volume_type_id': 'gold'}
|
||||||
|
|
||||||
|
volume_type = {'name': 'replicated',
|
||||||
|
'deleted': False,
|
||||||
|
'updated_at': None,
|
||||||
|
'deleted_at': None,
|
||||||
|
'extra_specs': {'replication_enabled': 'False'},
|
||||||
|
'id': 'silver'}
|
||||||
|
|
||||||
|
def get_side_effect(*args):
|
||||||
|
data = {'value': None}
|
||||||
|
if args[1] == 'gold':
|
||||||
|
data['value'] = {
|
||||||
|
'name': 'replicated',
|
||||||
|
'id': 'gold',
|
||||||
|
'extra_specs': {
|
||||||
|
'replication_enabled': '<is> True',
|
||||||
|
'replication:mode': 'periodic',
|
||||||
|
'replication:sync_period': '900',
|
||||||
|
'volume_type': self.volume_type_replicated}}
|
||||||
|
elif args[1] == 'silver':
|
||||||
|
data['value'] = {'name': 'silver',
|
||||||
|
'deleted': False,
|
||||||
|
'updated_at': None,
|
||||||
|
'extra_specs': {
|
||||||
|
'replication_enabled': 'False'},
|
||||||
|
'deleted_at': None,
|
||||||
|
'id': 'silver'}
|
||||||
|
return data['value']
|
||||||
|
|
||||||
|
_mock_volume_types.side_effect = get_side_effect
|
||||||
|
|
||||||
|
mock_client.getVolume.return_value = {
|
||||||
|
'name': mock.ANY,
|
||||||
|
'snapCPG': mock.ANY,
|
||||||
|
'comment': "{'display_name': 'Foo Volume'}",
|
||||||
|
'provisioningType': mock.ANY,
|
||||||
|
'userCPG': 'OpenStackCPG',
|
||||||
|
'snapCPG': 'OpenStackCPGSnap'}
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_client') as mock_create_client, \
|
||||||
|
mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_replication_client') as mock_replication_client:
|
||||||
|
mock_create_client.return_value = mock_client
|
||||||
|
mock_replication_client.return_value = mock_replicated_client
|
||||||
|
|
||||||
|
retyped = self.driver.retype(
|
||||||
|
self.ctxt, volume_1, volume_type, None, self.RETYPE_HOST)
|
||||||
|
self.assertTrue(retyped)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
mock.call.stopRemoteCopy(self.RCG_3PAR_NAME),
|
||||||
|
mock.call.removeVolumeFromRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
self.VOLUME_3PAR_NAME,
|
||||||
|
removeFromTarget=True),
|
||||||
|
mock.call.removeRemoteCopyGroup(self.RCG_3PAR_NAME)]
|
||||||
|
|
||||||
|
mock_client.assert_has_calls(
|
||||||
|
self.get_id_login +
|
||||||
|
self.standard_logout +
|
||||||
|
self.standard_login +
|
||||||
|
expected +
|
||||||
|
self.standard_logout, any_order =True)
|
||||||
|
|
||||||
|
@mock.patch.object(volume_types, 'get_volume_type')
|
||||||
|
def test_retype_rep_type_to_rep_type(self, _mock_volume_types):
|
||||||
|
|
||||||
|
conf = self.setup_configuration()
|
||||||
|
self.replication_targets[0]['replication_mode'] = 'periodic'
|
||||||
|
conf.replication_device = self.replication_targets
|
||||||
|
mock_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = (
|
||||||
|
{'id': self.CLIENT_ID})
|
||||||
|
mock_client.getRemoteCopyGroup.side_effect = (
|
||||||
|
hpeexceptions.HTTPNotFound)
|
||||||
|
mock_client.getCPG.return_value = {'domain': None}
|
||||||
|
mock_replicated_client = self.setup_driver(config=conf)
|
||||||
|
mock_client.getStorageSystemInfo.return_value = {
|
||||||
|
'id': self.REPLICATION_CLIENT_ID,
|
||||||
|
'serialNumber': '1234567'
|
||||||
|
}
|
||||||
|
mock_client.modifyVolume.return_value = ("anyResponse", {'taskid': 1})
|
||||||
|
mock_client.getTask.return_value = self.STATUS_DONE
|
||||||
|
|
||||||
|
volume_1 = {'name': self.VOLUME_NAME,
|
||||||
|
'id': self.VOLUME_ID,
|
||||||
|
'display_name': 'Foo Volume',
|
||||||
|
'replication_status': 'disabled',
|
||||||
|
'provider_location': self.CLIENT_ID,
|
||||||
|
'size': 2,
|
||||||
|
'host': self.FAKE_CINDER_HOST,
|
||||||
|
'volume_type': 'replicated',
|
||||||
|
'volume_type_id': 'gold'}
|
||||||
|
|
||||||
|
volume_type = {'name': 'replicated',
|
||||||
|
'deleted': False,
|
||||||
|
'updated_at': None,
|
||||||
|
'deleted_at': None,
|
||||||
|
'extra_specs': {'replication_enabled': '<is> True'},
|
||||||
|
'id': 'silver'}
|
||||||
|
|
||||||
|
def get_side_effect(*args):
|
||||||
|
data = {'value': None}
|
||||||
|
if args[1] == 'gold':
|
||||||
|
data['value'] = {
|
||||||
|
'name': 'replicated',
|
||||||
|
'id': 'gold',
|
||||||
|
'extra_specs': {
|
||||||
|
'replication_enabled': '<is> True',
|
||||||
|
'replication:mode': 'periodic',
|
||||||
|
'replication:sync_period': '900',
|
||||||
|
'volume_type': self.volume_type_replicated}}
|
||||||
|
elif args[1] == 'silver':
|
||||||
|
data['value'] = {
|
||||||
|
'name': 'silver',
|
||||||
|
'deleted': False,
|
||||||
|
'updated_at': None,
|
||||||
|
'extra_specs': {
|
||||||
|
'replication_enabled': '<is> True',
|
||||||
|
'replication:mode': 'periodic',
|
||||||
|
'replication:sync_period': '1500',
|
||||||
|
'volume_type': self.volume_type_replicated},
|
||||||
|
'deleted_at': None,
|
||||||
|
'id': 'silver'}
|
||||||
|
return data['value']
|
||||||
|
|
||||||
|
_mock_volume_types.side_effect = get_side_effect
|
||||||
|
|
||||||
|
mock_client.getVolume.return_value = {
|
||||||
|
'name': mock.ANY,
|
||||||
|
'snapCPG': mock.ANY,
|
||||||
|
'comment': "{'display_name': 'Foo Volume'}",
|
||||||
|
'provisioningType': mock.ANY,
|
||||||
|
'userCPG': 'OpenStackCPG',
|
||||||
|
'snapCPG': 'OpenStackCPGSnap'}
|
||||||
|
|
||||||
|
with mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_client') as mock_create_client, \
|
||||||
|
mock.patch.object(
|
||||||
|
hpecommon.HPE3PARCommon,
|
||||||
|
'_create_replication_client') as mock_replication_client:
|
||||||
|
mock_create_client.return_value = mock_client
|
||||||
|
mock_replication_client.return_value = mock_replicated_client
|
||||||
|
|
||||||
|
backend_id = self.replication_targets[0]['backend_id']
|
||||||
|
retyped = self.driver.retype(
|
||||||
|
self.ctxt, volume_1, volume_type, None, self.RETYPE_HOST)
|
||||||
|
self.assertTrue(retyped)
|
||||||
|
|
||||||
|
expected = [
|
||||||
|
mock.call.stopRemoteCopy(self.RCG_3PAR_NAME),
|
||||||
|
mock.call.removeVolumeFromRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
self.VOLUME_3PAR_NAME,
|
||||||
|
removeFromTarget=True),
|
||||||
|
mock.call.removeRemoteCopyGroup(self.RCG_3PAR_NAME),
|
||||||
|
mock.call.createRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
[{'userCPG': HPE3PAR_CPG_REMOTE,
|
||||||
|
'targetName': backend_id,
|
||||||
|
'mode': PERIODIC_MODE,
|
||||||
|
'snapCPG': HPE3PAR_CPG_REMOTE}],
|
||||||
|
{'localUserCPG': HPE3PAR_CPG,
|
||||||
|
'localSnapCPG': HPE3PAR_CPG_SNAP}),
|
||||||
|
mock.call.addVolumeToRemoteCopyGroup(
|
||||||
|
self.RCG_3PAR_NAME,
|
||||||
|
self.VOLUME_3PAR_NAME,
|
||||||
|
[{'secVolumeName': self.VOLUME_3PAR_NAME,
|
||||||
|
'targetName': backend_id}],
|
||||||
|
optional={'volumeAutoCreation': True}),
|
||||||
|
mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
|
||||||
|
|
||||||
|
mock_client.assert_has_calls(
|
||||||
|
self.get_id_login +
|
||||||
|
self.standard_logout +
|
||||||
|
self.standard_login +
|
||||||
|
expected +
|
||||||
|
self.standard_logout, any_order =True)
|
||||||
|
|
||||||
@mock.patch.object(volume_types, 'get_volume_type')
|
@mock.patch.object(volume_types, 'get_volume_type')
|
||||||
def test_retype_qos_spec(self, _mock_volume_types):
|
def test_retype_qos_spec(self, _mock_volume_types):
|
||||||
_mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_1
|
_mock_volume_types.return_value = self.RETYPE_VOLUME_TYPE_1
|
||||||
|
@ -256,10 +256,11 @@ class HPE3PARCommon(object):
|
|||||||
3.0.31 - Enable HPE-3PAR Compression Feature.
|
3.0.31 - Enable HPE-3PAR Compression Feature.
|
||||||
3.0.32 - Add consistency group capability to generic volume group
|
3.0.32 - Add consistency group capability to generic volume group
|
||||||
in HPE-3APR
|
in HPE-3APR
|
||||||
|
3.0.33 - Added replication feature in retype flow. bug #1680313
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
VERSION = "3.0.32"
|
VERSION = "3.0.33"
|
||||||
|
|
||||||
stats = {}
|
stats = {}
|
||||||
|
|
||||||
@ -2878,7 +2879,8 @@ class HPE3PARCommon(object):
|
|||||||
retype_flow.add(
|
retype_flow.add(
|
||||||
ModifyVolumeTask(action),
|
ModifyVolumeTask(action),
|
||||||
ModifySpecsTask(action),
|
ModifySpecsTask(action),
|
||||||
TuneVolumeTask(action))
|
TuneVolumeTask(action),
|
||||||
|
ReplicateVolumeTask(action))
|
||||||
|
|
||||||
taskflow.engines.run(
|
taskflow.engines.run(
|
||||||
retype_flow,
|
retype_flow,
|
||||||
@ -3435,7 +3437,8 @@ class HPE3PARCommon(object):
|
|||||||
|
|
||||||
return replication_targets
|
return replication_targets
|
||||||
|
|
||||||
def _do_volume_replication_setup(self, volume):
|
def _do_volume_replication_setup(self, volume, retype=False,
|
||||||
|
dist_type_id=None):
|
||||||
"""This function will do or ensure the following:
|
"""This function will do or ensure the following:
|
||||||
|
|
||||||
-Create volume on main array (already done in create_volume)
|
-Create volume on main array (already done in create_volume)
|
||||||
@ -3462,6 +3465,10 @@ class HPE3PARCommon(object):
|
|||||||
# Grab the extra_spec entries for replication and make sure they
|
# Grab the extra_spec entries for replication and make sure they
|
||||||
# are set correctly.
|
# are set correctly.
|
||||||
volume_type = self._get_volume_type(volume["volume_type_id"])
|
volume_type = self._get_volume_type(volume["volume_type_id"])
|
||||||
|
if retype and dist_type_id is not None:
|
||||||
|
dist_type = self._get_volume_type(dist_type_id)
|
||||||
|
extra_specs = dist_type.get("extra_specs")
|
||||||
|
else:
|
||||||
extra_specs = volume_type.get("extra_specs")
|
extra_specs = volume_type.get("extra_specs")
|
||||||
replication_mode = extra_specs.get(
|
replication_mode = extra_specs.get(
|
||||||
self.EXTRA_SPEC_REP_MODE, self.DEFAULT_REP_MODE)
|
self.EXTRA_SPEC_REP_MODE, self.DEFAULT_REP_MODE)
|
||||||
@ -3570,7 +3577,8 @@ class HPE3PARCommon(object):
|
|||||||
LOG.error(msg)
|
LOG.error(msg)
|
||||||
raise exception.VolumeBackendAPIException(data=msg)
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
|
||||||
def _do_volume_replication_destroy(self, volume, rcg_name=None):
|
def _do_volume_replication_destroy(self, volume, rcg_name=None,
|
||||||
|
retype=False):
|
||||||
"""This will completely remove all traces of a remote copy group.
|
"""This will completely remove all traces of a remote copy group.
|
||||||
|
|
||||||
It should be used when deleting a replication enabled volume
|
It should be used when deleting a replication enabled volume
|
||||||
@ -3606,6 +3614,7 @@ class HPE3PARCommon(object):
|
|||||||
|
|
||||||
# Delete volume on the main array.
|
# Delete volume on the main array.
|
||||||
try:
|
try:
|
||||||
|
if not retype:
|
||||||
self.client.deleteVolume(vol_name)
|
self.client.deleteVolume(vol_name)
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@ -3659,6 +3668,49 @@ class HPE3PARCommon(object):
|
|||||||
initial_delay=self.initial_delay).wait()
|
initial_delay=self.initial_delay).wait()
|
||||||
|
|
||||||
|
|
||||||
|
class ReplicateVolumeTask(flow_utils.CinderTask):
|
||||||
|
|
||||||
|
"""Task to replicate a volume.
|
||||||
|
|
||||||
|
This is a task for adding/removing the replication feature to volume.
|
||||||
|
It is intended for use during retype(). This task has no revert.
|
||||||
|
# TODO(sumit): revert back to original volume extra-spec
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, action, **kwargs):
|
||||||
|
super(ReplicateVolumeTask, self).__init__(addons=[action])
|
||||||
|
|
||||||
|
def execute(self, common, volume, new_type_id):
|
||||||
|
|
||||||
|
new_replicated_type = False
|
||||||
|
|
||||||
|
if new_type_id:
|
||||||
|
new_volume_type = common._get_volume_type(new_type_id)
|
||||||
|
|
||||||
|
extra_specs = new_volume_type.get('extra_specs', None)
|
||||||
|
if extra_specs and 'replication_enabled' in extra_specs:
|
||||||
|
rep_val = extra_specs['replication_enabled']
|
||||||
|
new_replicated_type = (rep_val == "<is> True")
|
||||||
|
|
||||||
|
if common._volume_of_replicated_type(volume) and new_replicated_type:
|
||||||
|
# Retype from replication enabled to replication enable.
|
||||||
|
common._do_volume_replication_destroy(volume, retype=True)
|
||||||
|
common._do_volume_replication_setup(
|
||||||
|
volume,
|
||||||
|
retype=True,
|
||||||
|
dist_type_id=new_type_id)
|
||||||
|
elif (not common._volume_of_replicated_type(volume)
|
||||||
|
and new_replicated_type):
|
||||||
|
# Retype from replication disabled to replication enable.
|
||||||
|
common._do_volume_replication_setup(
|
||||||
|
volume,
|
||||||
|
retype=True,
|
||||||
|
dist_type_id=new_type_id)
|
||||||
|
elif common._volume_of_replicated_type(volume):
|
||||||
|
# Retype from replication enabled to replication disable.
|
||||||
|
common._do_volume_replication_destroy(volume, retype=True)
|
||||||
|
|
||||||
|
|
||||||
class ModifyVolumeTask(flow_utils.CinderTask):
|
class ModifyVolumeTask(flow_utils.CinderTask):
|
||||||
|
|
||||||
"""Task to change a volume's snapCPG and comment.
|
"""Task to change a volume's snapCPG and comment.
|
||||||
|
Loading…
Reference in New Issue
Block a user