CGSNAPSHOT_BASE_NAME = 'oss-6Rxe1druToSHJByeMeeh8g'
CLIENT_ID = "12345"
REPLICATION_CLIENT_ID = "54321"
+ REPLICATION_BACKEND_ID = 'target'
# fake host on the 3par
FAKE_HOST = 'fakehost'
FAKE_CINDER_HOST = 'fakehost@foo#' + HPE3PAR_CPG
- FAKE_FAILOVER_HOST = 'fakefailover@foo#destfakepool'
USER_ID = '2689d9a913974c008b1d859013f23607'
PROJECT_ID = 'fac88235b9d64685a3530f73e490348f'
VOLUME_ID_SNAP = '761fc5e5-5191-4ec7-aeba-33e36de44156'
'volume_type': 'replicated',
'volume_type_id': VOLUME_TYPE_ID_REPLICATED}
- replication_targets = [{'target_device_id': 'target',
+ replication_targets = [{'backend_id': REPLICATION_BACKEND_ID,
'cpg_map': HPE3PAR_CPG_MAP,
'hpe3par_api_url': 'https://1.1.1.1/api/v1',
'hpe3par_username': HPE3PAR_USER_NAME,
'san_password': HPE3PAR_USER_PASS,
'san_ssh_port': HPE3PAR_SAN_SSH_PORT,
'ssh_conn_timeout': HPE3PAR_SAN_SSH_CON_TIMEOUT,
- 'san_private_key': HPE3PAR_SAN_SSH_PRIVATE,
- 'managed_backend_name': FAKE_FAILOVER_HOST}]
-
- list_rep_targets = [{'target_device_id': 'target'}]
-
- replication_devs_unmgd = [{'target_device_id': 'target',
- 'cpg_map': HPE3PAR_CPG_MAP,
- 'hpe3par_api_url': 'https://1.1.1.1/api/v1',
- 'hpe3par_username': HPE3PAR_USER_NAME,
- 'hpe3par_password': HPE3PAR_USER_PASS,
- 'san_ip': HPE3PAR_SAN_IP,
- 'san_login': HPE3PAR_USER_NAME,
- 'san_password': HPE3PAR_USER_PASS,
- 'san_ssh_port': HPE3PAR_SAN_SSH_PORT,
- 'ssh_conn_timeout': HPE3PAR_SAN_SSH_CON_TIMEOUT,
- 'san_private_key': HPE3PAR_SAN_SSH_PRIVATE,
- 'managed_backend_name': None}]
+ 'san_private_key': HPE3PAR_SAN_SSH_PRIVATE}]
+
+ list_rep_targets = [{'backend_id': 'target'}]
volume_encrypted = {'name': VOLUME_NAME,
'id': VOLUME_ID,
self.assertIsNone(return_model)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_create_volume_replicated_managed_periodic(self,
- _mock_volume_types):
+ def test_create_volume_replicated_periodic(self, _mock_volume_types):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
conf = self.setup_configuration()
"qos": {},
"type": "OpenStack"})
- target_device_id = self.replication_targets[0]['target_device_id']
+ backend_id = self.replication_targets[0]['backend_id']
expected = [
mock.call.createVolume(
self.VOLUME_3PAR_NAME,
mock.call.createRemoteCopyGroup(
self.RCG_3PAR_NAME,
[{'userCPG': HPE3PAR_CPG_REMOTE,
- 'targetName': target_device_id,
+ 'targetName': backend_id,
'mode': PERIODIC_MODE,
'snapCPG': HPE3PAR_CPG_REMOTE}],
{'localUserCPG': HPE3PAR_CPG,
self.RCG_3PAR_NAME,
self.VOLUME_3PAR_NAME,
[{'secVolumeName': self.VOLUME_3PAR_NAME,
- 'targetName': target_device_id}],
+ 'targetName': backend_id}],
optional={'volumeAutoCreation': True}),
mock.call.modifyRemoteCopyGroup(
self.RCG_3PAR_NAME,
{'targets': [{'syncPeriod': SYNC_PERIOD,
- 'targetName': target_device_id}]}),
+ 'targetName': backend_id}]}),
mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
mock_client.assert_has_calls(
self.get_id_login +
return_model)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_create_volume_replicated_managed_sync(self,
- _mock_volume_types):
+ def test_delete_volume_replicated_failedover(self, _mock_volume_types):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
conf = self.setup_configuration()
- self.replication_targets[0]['replication_mode'] = 'sync'
+ 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_replicated_client.getStorageSystemInfo.return_value = (
- {'id': self.REPLICATION_CLIENT_ID})
-
- _mock_volume_types.return_value = {
- 'name': 'replicated',
- 'extra_specs': {
- 'replication_enabled': '<is> True',
- 'replication:mode': 'sync',
- 'volume_type': self.volume_type_replicated}}
-
- 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
-
- return_model = self.driver.create_volume(self.volume_replicated)
- comment = Comment({
- "volume_type_name": "replicated",
- "display_name": "Foo Volume",
- "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
- "volume_type_id": "be9181f1-4040-46f2-8298-e7532f2bf9db",
- "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
- "qos": {},
- "type": "OpenStack"})
-
- target_device_id = self.replication_targets[0]['target_device_id']
- expected = [
- mock.call.createVolume(
- self.VOLUME_3PAR_NAME,
- HPE3PAR_CPG,
- 2048, {
- 'comment': comment,
- 'tpvv': True,
- 'tdvv': False,
- 'snapCPG': HPE3PAR_CPG_SNAP}),
- mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.createRemoteCopyGroup(
- self.RCG_3PAR_NAME,
- [{'userCPG': HPE3PAR_CPG_REMOTE,
- 'targetName': target_device_id,
- 'mode': SYNC_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': target_device_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)
- self.assertEqual({'replication_status': 'enabled',
- 'provider_location': self.CLIENT_ID},
- return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_create_volume_replicated_unmanaged_periodic(self,
- _mock_volume_types):
- # setup_mock_client drive with default configuration
- # and return the mock HTTP 3PAR client
- conf = self.setup_configuration()
- self.replication_devs_unmgd[0]['replication_mode'] = 'periodic'
- conf.replication_device = self.replication_devs_unmgd
- 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.getRemoteCopyGroup.return_value = (
+ {'targets': [{'targetName': 'tgt'}]})
mock_client.getCPG.return_value = {'domain': None}
mock_replicated_client = self.setup_driver(config=conf)
mock_replicated_client.getStorageSystemInfo.return_value = (
_mock_volume_types.return_value = {
'name': 'replicated',
'extra_specs': {
- 'cpg': HPE3PAR_CPG,
- 'snap_cpg': HPE3PAR_CPG_SNAP,
'replication_enabled': '<is> True',
'replication:mode': 'periodic',
'replication:sync_period': '900',
mock_create_client.return_value = mock_client
mock_replication_client.return_value = mock_replicated_client
- return_model = self.driver.create_volume(self.volume_replicated)
- comment = Comment({
- "volume_type_name": "replicated",
- "display_name": "Foo Volume",
- "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
- "volume_type_id": "be9181f1-4040-46f2-8298-e7532f2bf9db",
- "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
- "qos": {},
- "type": "OpenStack"})
+ volume = self.volume_replicated.copy()
+ volume['replication_status'] = 'failed-over'
+ self.driver.delete_volume(volume)
- target_device_id = self.replication_targets[0]['target_device_id']
+ rcg_name = self.RCG_3PAR_NAME + ".r" + self.CLIENT_ID
expected = [
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.createVolume(
- self.VOLUME_3PAR_NAME,
- HPE3PAR_CPG,
- 2048, {
- 'comment': comment,
- 'tpvv': True,
- 'tdvv': False,
- 'snapCPG': HPE3PAR_CPG_SNAP}),
- mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.createRemoteCopyGroup(
- self.RCG_3PAR_NAME,
- [{'userCPG': HPE3PAR_CPG_REMOTE,
- 'targetName': target_device_id,
- 'mode': PERIODIC_MODE,
- 'snapCPG': HPE3PAR_CPG_REMOTE}],
- {'localUserCPG': HPE3PAR_CPG,
- 'localSnapCPG': HPE3PAR_CPG_SNAP}),
- mock.call.addVolumeToRemoteCopyGroup(
- self.RCG_3PAR_NAME,
+ mock.call.getRemoteCopyGroup(rcg_name),
+ mock.call.toggleRemoteCopyConfigMirror(
+ 'tgt',
+ mirror_config=False),
+ mock.call.stopRemoteCopy(rcg_name),
+ mock.call.removeVolumeFromRemoteCopyGroup(
+ rcg_name,
self.VOLUME_3PAR_NAME,
- [{'secVolumeName': self.VOLUME_3PAR_NAME,
- 'targetName': target_device_id}],
- optional={'volumeAutoCreation': True}),
- mock.call.modifyRemoteCopyGroup(
- self.RCG_3PAR_NAME,
- {'targets': [{'syncPeriod': SYNC_PERIOD,
- 'targetName': target_device_id}]}),
- mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
+ removeFromTarget=True),
+ mock.call.removeRemoteCopyGroup(rcg_name),
+ mock.call.deleteVolume(self.VOLUME_3PAR_NAME),
+ mock.call.toggleRemoteCopyConfigMirror(
+ 'tgt',
+ mirror_config=True)]
mock_client.assert_has_calls(
self.get_id_login +
self.standard_logout +
self.standard_login +
expected +
self.standard_logout)
- self.assertEqual({'replication_status': 'enabled',
- 'provider_location': self.CLIENT_ID},
- return_model)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_create_volume_replicated_unmanaged_sync(self,
- _mock_volume_types):
+ def test_create_volume_replicated_sync(self, _mock_volume_types):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
conf = self.setup_configuration()
- self.replication_devs_unmgd[0]['replication_mode'] = 'sync'
- conf.replication_device = self.replication_devs_unmgd
+ self.replication_targets[0]['replication_mode'] = 'sync'
+ 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.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_replicated_client.getStorageSystemInfo.return_value = (
_mock_volume_types.return_value = {
'name': 'replicated',
'extra_specs': {
- 'cpg': HPE3PAR_CPG,
- 'snap_cpg': HPE3PAR_CPG_SNAP,
'replication_enabled': '<is> True',
'replication:mode': 'sync',
'volume_type': self.volume_type_replicated}}
"qos": {},
"type": "OpenStack"})
- target_device_id = self.replication_targets[0]['target_device_id']
+ backend_id = self.replication_targets[0]['backend_id']
expected = [
- mock.call.getCPG(HPE3PAR_CPG),
mock.call.createVolume(
self.VOLUME_3PAR_NAME,
HPE3PAR_CPG,
'snapCPG': HPE3PAR_CPG_SNAP}),
mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
mock.call.getCPG(HPE3PAR_CPG),
- mock.call.getCPG(HPE3PAR_CPG),
mock.call.createRemoteCopyGroup(
self.RCG_3PAR_NAME,
[{'userCPG': HPE3PAR_CPG_REMOTE,
- 'targetName': target_device_id,
+ 'targetName': backend_id,
'mode': SYNC_MODE,
'snapCPG': HPE3PAR_CPG_REMOTE}],
{'localUserCPG': HPE3PAR_CPG,
self.RCG_3PAR_NAME,
self.VOLUME_3PAR_NAME,
[{'secVolumeName': self.VOLUME_3PAR_NAME,
- 'targetName': target_device_id}],
+ 'targetName': backend_id}],
optional={'volumeAutoCreation': True}),
mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
mock_client.assert_has_calls(
self.standard_logout)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_enable_not_in_rcopy(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
- 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_replicated_client.getStorageSystemInfo.return_value = (
- {'id': self.REPLICATION_CLIENT_ID})
-
- _mock_volume_types.return_value = {
- 'name': 'replicated',
- 'extra_specs': {
- 'cpg': HPE3PAR_CPG,
- 'snap_cpg': HPE3PAR_CPG_SNAP,
- 'replication_enabled': '<is> True',
- 'replication:mode': 'periodic',
- 'replication:sync_period': '900',
- 'volume_type': self.volume_type_replicated}}
-
- 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
-
- return_model = self.driver.replication_enable(
- context.get_admin_context(),
- self.volume_replicated)
-
- target_device_id = self.replication_targets[0]['target_device_id']
- expected = [
- mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.getCPG(HPE3PAR_CPG),
- mock.call.createRemoteCopyGroup(
- self.RCG_3PAR_NAME,
- [{'userCPG': HPE3PAR_CPG_REMOTE,
- 'targetName': target_device_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': target_device_id}],
- optional={'volumeAutoCreation': True}),
- mock.call.modifyRemoteCopyGroup(
- self.RCG_3PAR_NAME,
- {'targets': [{'syncPeriod': SYNC_PERIOD,
- 'targetName': target_device_id}]}),
- 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)
- self.assertEqual({'replication_status': 'enabled',
- 'provider_location': self.CLIENT_ID},
- return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_enable_in_rcopy(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
+ def test_failover_host(self, _mock_volume_types):
+ # periodic vs. sync is not relevant when conducting a failover. We
+ # will just use periodic.
conf = self.setup_configuration()
self.replication_targets[0]['replication_mode'] = 'periodic'
conf.replication_device = self.replication_targets
'_create_replication_client') as mock_replication_client:
mock_create_client.return_value = mock_client
mock_replication_client.return_value = mock_replicated_client
+ valid_backend_id = (
+ self.replication_targets[0]['backend_id'])
+ invalid_backend_id = 'INVALID'
- return_model = self.driver.replication_enable(
- context.get_admin_context(),
- self.volume_replicated)
-
- expected = [
- mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
- 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)
- self.assertEqual({'replication_status': 'enabled',
- 'provider_location': self.CLIENT_ID},
- return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_enable_non_replicated_type(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
- 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_volume_types.return_value = {
- 'name': 'NOT_replicated',
- 'extra_specs': {
- 'volume_type': self.volume_type}}
-
- with mock.patch.object(
- hpecommon.HPE3PARCommon,
- '_create_client') as mock_create_client:
- mock_create_client.return_value = mock_client
-
+ volumes = [self.volume_replicated]
+ # Test invalid secondary target.
self.assertRaises(
exception.VolumeBackendAPIException,
- self.driver.replication_enable,
+ self.driver.failover_host,
context.get_admin_context(),
- self.volume_replicated)
+ volumes,
+ invalid_backend_id)
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_disable(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
- 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_replicated_client = self.setup_driver(config=conf)
- mock_replicated_client.getStorageSystemInfo.return_value = (
- {'id': self.REPLICATION_CLIENT_ID})
-
- _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}}
-
- 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
-
- return_model = self.driver.replication_disable(
+ # Test no secondary target.
+ self.assertRaises(
+ exception.VolumeBackendAPIException,
+ self.driver.failover_host,
context.get_admin_context(),
- self.volume_replicated)
-
- expected = [
- mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
- mock_client.assert_has_calls(
- self.get_id_login +
- self.standard_logout +
- self.standard_login +
- expected +
- self.standard_logout)
- self.assertEqual({'replication_status': 'disabled'},
- return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_disable_fail(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
- 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.stopRemoteCopy.side_effect = (
- Exception("Error: Remote Copy could not be stopped."))
- mock_client.getStorageSystemInfo.return_value = (
- {'id': self.CLIENT_ID})
- mock_replicated_client = self.setup_driver(config=conf)
- mock_replicated_client.getStorageSystemInfo.return_value = (
- {'id': self.REPLICATION_CLIENT_ID})
-
- _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}}
-
- 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
+ volumes,
+ None)
- return_model = self.driver.replication_disable(
+ # Test a successful failover.
+ expected_model = (self.REPLICATION_BACKEND_ID,
+ [{'updates': {'replication_status':
+ 'failed-over'},
+ 'volume_id': self.VOLUME_ID}])
+ return_model = self.driver.failover_host(
context.get_admin_context(),
- self.volume_replicated)
-
+ volumes,
+ valid_backend_id)
expected = [
mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
mock_client.assert_has_calls(
self.standard_login +
expected +
self.standard_logout)
- self.assertEqual({'replication_status': 'disable_failed'},
- return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_disable_non_replicated_type(self, _mock_volume_types):
- # Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
- # We will use managed and periodic as the default.
- 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_volume_types.return_value = {
- 'name': 'NOT_replicated',
- 'extra_specs': {
- 'volume_type': self.volume_type}}
-
- with mock.patch.object(
- hpecommon.HPE3PARCommon,
- '_create_client') as mock_create_client:
- mock_create_client.return_value = mock_client
-
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_disable,
- context.get_admin_context(),
- self.volume_replicated)
+ self.assertEqual(expected_model, return_model)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_list_replication_targets(self, _mock_volume_types):
+ def test_replication_failback_ready(self, _mock_volume_types):
# Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
+ # failing back a volume.
# We will use managed and periodic as the default.
- target_device_id = self.replication_targets[0]['target_device_id']
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.getRemoteCopyGroup.return_value = (
- {'targets': [{'targetName': target_device_id}]})
mock_client.getStorageSystemInfo.return_value = (
{'id': self.CLIENT_ID})
- mock_client.getCPG.return_value = {'domain': None}
mock_replicated_client = self.setup_driver(config=conf)
mock_replicated_client.getStorageSystemInfo.return_value = (
{'id': self.REPLICATION_CLIENT_ID})
mock_create_client.return_value = mock_client
mock_replication_client.return_value = mock_replicated_client
- return_model = self.driver.list_replication_targets(
+ # Test a successful fail-back.
+ volume = self.volume_replicated.copy()
+ volume['replication_status'] = 'failed-over'
+ return_model = self.driver.failover_host(
context.get_admin_context(),
- self.volume_replicated)
-
- expected = [
- mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME)]
- mock_client.assert_has_calls(
- self.get_id_login +
- self.standard_logout +
- self.standard_login +
- expected +
- self.standard_logout)
-
- targets = self.list_rep_targets
- self.assertEqual({'volume_id': self.volume_replicated['id'],
- 'targets': targets},
- return_model)
+ [volume],
+ 'default')
+ expected_model = (None,
+ [{'updates': {'replication_status':
+ 'available'},
+ 'volume_id': self.VOLUME_ID}])
+ self.assertEqual(expected_model, return_model)
@mock.patch.object(volume_types, 'get_volume_type')
- def test_list_replication_targets_non_replicated_type(self,
- _mock_volume_types):
+ def test_replication_failback_not_ready(self, _mock_volume_types):
# Managed vs. unmanaged and periodic vs. sync are not relevant when
- # enabling/disabling replication and listing replication targets.
+ # failing back a volume.
# We will use managed and periodic as the default.
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_volume_types.return_value = {
- 'name': 'NOT_replicated',
- 'extra_specs': {
- 'volume_type': self.volume_type}}
-
- with mock.patch.object(
- hpecommon.HPE3PARCommon,
- '_create_client') as mock_create_client:
- mock_create_client.return_value = mock_client
-
- return_model = self.driver.list_replication_targets(
- context.get_admin_context(),
- self.volume_replicated)
-
- mock_client.assert_has_calls(
- self.get_id_login +
- self.standard_logout +
- self.standard_login +
- self.standard_logout)
-
- self.assertEqual([], return_model)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_failover_managed(self, _mock_volume_types):
- # periodic vs. sync is not relevant when conducting a failover. We
- # will just use periodic.
- provider_location = self.CLIENT_ID + ":" + self.REPLICATION_CLIENT_ID
- 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_replicated_client = self.setup_driver(config=conf)
- mock_replicated_client.getStorageSystemInfo.return_value = (
- {'id': self.REPLICATION_CLIENT_ID})
-
- _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}}
-
- 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
- valid_target_device_id = (
- self.replication_targets[0]['target_device_id'])
- invalid_target_device_id = 'INVALID'
-
- # test invalid secondary target
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
- context.get_admin_context(),
- self.volume_replicated,
- invalid_target_device_id)
-
- # test no secondary target
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
- context.get_admin_context(),
- self.volume_replicated,
- None)
-
- # test a successful failover
- volume = self.volume_replicated
- volume['provider_location'] = self.CLIENT_ID
- return_model = self.driver.replication_failover(
- context.get_admin_context(),
- volume,
- valid_target_device_id)
- expected = [
- mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
- mock_client.assert_has_calls(
- self.get_id_login +
- self.standard_logout +
- self.standard_login +
- expected +
- self.standard_logout)
- self.assertEqual({'replication_status': 'inactive',
- 'provider_location': provider_location,
- 'host': self.FAKE_FAILOVER_HOST},
- return_model)
-
- # test a unsuccessful failover
- mock_replicated_client.recoverRemoteCopyGroupFromDisaster.\
- side_effect = (
- exception.VolumeBackendAPIException(
- "Error: Failover was unsuccessful."))
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
- context.get_admin_context(),
- self.volume_replicated,
- valid_target_device_id)
-
- @mock.patch.object(volume_types, 'get_volume_type')
- def test_replication_failover_unmanaged(self, _mock_volume_types):
- # periodic vs. sync is not relevant when conducting a failover. We
- # will just use periodic.
- provider_location = self.CLIENT_ID + ":" + self.REPLICATION_CLIENT_ID
- conf = self.setup_configuration()
- self.replication_devs_unmgd[0]['replication_mode'] = 'periodic'
- conf.replication_device = self.replication_devs_unmgd
- mock_client = self.setup_driver(config=conf)
mock_client.getStorageSystemInfo.return_value = (
{'id': self.CLIENT_ID})
mock_replicated_client = self.setup_driver(config=conf)
hpecommon.HPE3PARCommon,
'_create_replication_client') as mock_replication_client:
mock_create_client.return_value = mock_client
+ mock_client.getRemoteCopyGroup.side_effect = (
+ exception.VolumeBackendAPIException(
+ "Error: Remote Copy Group not Ready."))
mock_replication_client.return_value = mock_replicated_client
- valid_target_device_id = (
- self.replication_targets[0]['target_device_id'])
- invalid_target_device_id = 'INVALID'
-
- # test invalid secondary target
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
- context.get_admin_context(),
- self.volume_replicated,
- invalid_target_device_id)
-
- # test no secondary target
- self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
- context.get_admin_context(),
- self.volume_replicated,
- None)
- # test a successful failover
- volume = self.volume_replicated
- volume['provider_location'] = self.CLIENT_ID
- return_model = self.driver.replication_failover(
- context.get_admin_context(),
- volume,
- valid_target_device_id)
- expected = [
- mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
- mock_client.assert_has_calls(
- self.get_id_login +
- self.standard_logout +
- self.standard_login +
- expected +
- self.standard_logout)
- self.assertEqual({'replication_status': 'inactive',
- 'provider_location': provider_location},
- return_model)
+ # Test an unsuccessful fail-back.
+ volume = self.volume_replicated.copy()
+ volume['replication_status'] = 'failed-over'
- # test a unsuccessful failover
- mock_replicated_client.recoverRemoteCopyGroupFromDisaster.\
- side_effect = (
- exception.VolumeBackendAPIException(
- "Error: Failover was unsuccessful."))
self.assertRaises(
- exception.VolumeBackendAPIException,
- self.driver.replication_failover,
+ exception.VolumeDriverException,
+ self.driver.failover_host,
context.get_admin_context(),
- self.volume_replicated,
- valid_target_device_id)
+ [volume],
+ 'default')
class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
3.0.12 - Remove client version checks for replication
3.0.13 - Support creating a cg from a source cg
3.0.14 - Comparison of WWNs now handles case difference. bug #1546453
+ 3.0.15 - Update replication to version 2.1
"""
- VERSION = "3.0.14"
+ VERSION = "3.0.15"
stats = {}
EXTRA_SPEC_REP_MODE = "replication:mode"
EXTRA_SPEC_REP_SYNC_PERIOD = "replication:sync_period"
RC_ACTION_CHANGE_TO_PRIMARY = 7
+ DEFAULT_REP_MODE = 'periodic'
+ DEFAULT_SYNC_PERIOD = 900
+ RC_GROUP_STARTED = 3
+ SYNC_STATUS_COMPLETED = 3
+ FAILBACK_VALUE = 'default'
# License values for reported capabilities
PRIORITY_OPT_LIC = "Priority Optimization"
hpe3par_valid_keys = ['cpg', 'snap_cpg', 'provisioning', 'persona', 'vvs',
'flash_cache']
- def __init__(self, config):
+ def __init__(self, config, active_backend_id=None):
self.config = config
self.client = None
self.uuid = uuid.uuid4()
self._client_conf = {}
self._replication_targets = []
self._replication_enabled = False
+ self._active_backend_id = active_backend_id
def get_version(self):
return self.VERSION
if client is not None:
client.logout()
- def do_setup(self, context, volume=None, timeout=None, stats=None):
+ def do_setup(self, context, timeout=None, stats=None):
if hpe3parclient is None:
msg = _('You must install hpe3parclient before using 3PAR'
' drivers. Run "pip install python-3parclient" to'
# to communicate with the 3PAR array. It will contain either
# the values for the primary array or secondary array in the
# case of a fail-over.
- self._get_3par_config(volume)
+ self._get_3par_config()
self.client = self._create_client(timeout=timeout)
wsapi_version = self.client.getWsApiVersion()
self.API_VERSION = wsapi_version['build']
if self.client:
self.client_login()
try:
- cpg_names = self.config.hpe3par_cpg
+ cpg_names = self._client_conf['hpe3par_cpg']
for cpg_name in cpg_names:
self.validate_cpg(cpg_name)
valid_licenses, self.REMOTE_COPY_LIC,
"Replication")
- for cpg_name in self.config.hpe3par_cpg:
+ for cpg_name in self._client_conf['hpe3par_cpg']:
try:
cpg = self.client.getCPG(cpg_name)
if (self.API_VERSION >= SRSTATLD_API_VERSION):
'vendor_name': 'Hewlett Packard Enterprise',
'volume_backend_name': None,
'array_id': info['id'],
+ 'replication_enabled': self._replication_enabled,
+ 'replication_targets': self._get_replication_targets(),
'pools': pools}
def _check_license_enabled(self, valid_licenses,
# Default to pool extracted from host.
# If that doesn't work use the 1st CPG in the config as the default.
- default_cpg = pool or self.config.hpe3par_cpg[0]
+ default_cpg = pool or self._client_conf['hpe3par_cpg'][0]
cpg = self._get_key_value(hpe3par_keys, 'cpg', default_cpg)
if cpg is not default_cpg:
flash_cache = self.get_flash_cache_policy(hpe3par_keys)
if qos or vvs_name or flash_cache is not None:
- cpg_names = self._get_key_value(hpe3par_keys, 'cpg',
- self.config.hpe3par_cpg)
+ cpg_names = self._get_key_value(
+ hpe3par_keys, 'cpg', self._client_conf['hpe3par_cpg'])
try:
self._add_volume_to_volume_set(volume, volume_name,
cpg_names[0], vvs_name,
return existing_vluns
# v2 replication methods
- def replication_enable(self, context, volume):
- """Enable replication on a replication capable volume."""
- if not self._volume_of_replicated_type(volume):
- msg = _("Unable to enable volume replication because volume is "
- "not of replicated type.")
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- model_update = {"provider_location": self.client.id}
- # If replication is not enabled and the volume is of replicated type,
- # we treat this as an error.
- if not self._replication_enabled:
- msg = _LE("Enabling replication failed because replication is "
- "not properly configured.")
- LOG.error(msg)
- model_update['replication_status'] = "error"
- else:
- if self._do_volume_replication_setup(volume):
- model_update['replication_status'] = "enabled"
- else:
- model_update['replication_status'] = "error"
-
- return model_update
-
- def replication_disable(self, context, volume):
- """Disable replication on the specified volume."""
- if not self._volume_of_replicated_type(volume):
- msg = _("Unable to disable volume replication because volume is "
- "not of replicated type.")
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- model_update = {}
- # If replication is not enabled and the volume is of replicated type,
- # we treat this as an error.
- if self._replication_enabled:
- model_update['replication_status'] = 'disabled'
- rcg_name = self._get_3par_rcg_name(volume['id'])
- vol_name = self._get_3par_vol_name(volume['id'])
-
- try:
- self.client.stopRemoteCopy(rcg_name)
- except Exception as ex:
- msg = (_LE("There was a problem disabling replication on "
- "volume '%(name)s': %(error)s") %
- {'name': vol_name,
- 'error': six.text_type(ex)})
- LOG.error(msg)
- model_update['replication_status'] = 'disable_failed'
- else:
- msg = _LE("Disabling replication failed because replication is "
- "not properly configured.")
- LOG.error(msg)
- model_update['replication_status'] = 'error'
-
- return model_update
-
- def replication_failover(self, context, volume, secondary):
+ def failover_host(self, context, volumes, secondary_backend_id):
"""Force failover to a secondary replication target."""
- if not self._volume_of_replicated_type(volume):
- msg = _("Unable to failover because volume is not of "
- "replicated type.")
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- # If replication is not enabled and the volume is of replicated type,
- # we treat this as an error.
+ # Ensure replication is enabled before we try and failover.
if not self._replication_enabled:
msg = _LE("Issuing a fail-over failed because replication is "
"not properly configured.")
LOG.error(msg)
- model_update = {"replication_status": "error"}
- return model_update
-
- failover_target = None
- for target in self._replication_targets:
- if target['target_device_id'] == secondary:
- failover_target = target
- break
-
- if not failover_target:
- msg = _("A valid secondary target MUST be specified in order "
- "to failover.")
- LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
- if self.client is not None and failover_target['id'] == self.client.id:
- msg = _("The failover array cannot be the primary array.")
- LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
-
- try:
- # Try and stop remote-copy on main array.
- rcg_name = self._get_3par_rcg_name(volume['id'])
- self.client.stopRemoteCopy(rcg_name)
- except Exception:
- pass
+ # Check to see if the user requested to failback.
+ if secondary_backend_id == self.FAILBACK_VALUE:
+ volume_update_list = self._replication_failback(volumes)
+ target_id = None
+ else:
+ # Find the failover target.
+ failover_target = None
+ for target in self._replication_targets:
+ if target['backend_id'] == secondary_backend_id:
+ failover_target = target
+ break
+ if not failover_target:
+ msg = _("A valid secondary target MUST be specified in order "
+ "to failover.")
+ LOG.error(msg)
+ raise exception.VolumeBackendAPIException(data=msg)
- try:
- # Failover to secondary array.
- remote_rcg_name = self._get_3par_remote_rcg_name(
- volume['id'], volume['provider_location'])
- cl = self._create_replication_client(failover_target)
- cl.recoverRemoteCopyGroupFromDisaster(
- remote_rcg_name, self.RC_ACTION_CHANGE_TO_PRIMARY)
- new_location = volume['provider_location'] + ":" + (
- failover_target['id'])
-
- model_update = {"provider_location": new_location,
- "replication_status": "inactive"}
- if failover_target['managed_backend_name']:
- # We want to update the volumes host if our target is managed.
- model_update['host'] = failover_target['managed_backend_name']
+ target_id = failover_target['backend_id']
+ # For each volume, if it is replicated, we want to fail it over.
+ volume_update_list = []
+ for volume in volumes:
+ if self._volume_of_replicated_type(volume):
+ try:
+ # Try and stop remote-copy on main array. We eat the
+ # exception here because when an array goes down, the
+ # groups will stop automatically.
+ rcg_name = self._get_3par_rcg_name(volume['id'])
+ self.client.stopRemoteCopy(rcg_name)
+ except Exception:
+ pass
- except Exception as ex:
- msg = _("There was a problem with the failover (%s) and it was "
- "unsuccessful.") % six.text_type(ex)
+ try:
+ # Failover to secondary array.
+ remote_rcg_name = self._get_3par_remote_rcg_name(
+ volume['id'], volume['provider_location'])
+ cl = self._create_replication_client(failover_target)
+ cl.recoverRemoteCopyGroupFromDisaster(
+ remote_rcg_name, self.RC_ACTION_CHANGE_TO_PRIMARY)
+ volume_update_list.append(
+ {'volume_id': volume['id'],
+ 'updates': {'replication_status': 'failed-over'}})
+ except Exception as ex:
+ msg = (_LE("There was a problem with the failover "
+ "(%(error)s) and it was unsuccessful. "
+ "Volume '%(volume)s will not be available "
+ "on the failed over target."),
+ {'error': six.text_type(ex),
+ 'volume': volume['id']})
+ LOG.error(msg)
+ volume_update_list.append(
+ {'volume_id': volume['id'],
+ 'updates': {'replication_status': 'error'}})
+ finally:
+ self._destroy_replication_client(cl)
+ else:
+ # If the volume is not of replicated type, we need to
+ # force the status into error state so a user knows they
+ # do not have access to the volume.
+ volume_update_list.append(
+ {'volume_id': volume['id'],
+ 'updates': {'status': 'error'}})
+
+ return target_id, volume_update_list
+
+ def _replication_failback(self, volumes):
+ # Make sure the proper steps on the backend have been completed before
+ # we allow a fail-over.
+ if not self._is_host_ready_for_failback(volumes):
+ msg = _("The host is not ready to be failed back. Please "
+ "resynchronize the volumes and resume replication on the "
+ "3PAR backends.")
LOG.error(msg)
- raise exception.VolumeBackendAPIException(data=msg)
- finally:
- self._destroy_replication_client(cl)
+ raise exception.VolumeDriverException(data=msg)
- return model_update
+ # Update the volumes status to available.
+ volume_update_list = []
+ for volume in volumes:
+ if self._volume_of_replicated_type(volume):
+ volume_update_list.append(
+ {'volume_id': volume['id'],
+ 'updates': {'replication_status': 'available'}})
+ else:
+ # Upon failing back, we can move the non-replicated volumes
+ # back into available state.
+ volume_update_list.append(
+ {'volume_id': volume['id'],
+ 'updates': {'status': 'available'}})
- def list_replication_targets(self, context, volume):
- """Provides a means to obtain replication targets for a volume.
+ return volume_update_list
- This will query all enabled targets on a 3PAR backend and cross
- reference them with all entries in cinder.conf. It will return
- only those that appear on both, aka enabled replication targets.
- """
- if not self._volume_of_replicated_type(volume):
- return []
+ def _is_host_ready_for_failback(self, volumes):
+ """Checks to make sure the volume has been synchronized
- allowed_names = []
- # If the primary target is offline we can not ask it what targets are
- # available. Our only option is to list all cinder.conf entries.
+ This ensures that all the remote copy targets have been restored
+ to their natural direction, and all of the volumes have been
+ fully synchronized.
+ """
try:
- rcg_name = self._get_3par_rcg_name(volume['id'])
- rcg = self.client.getRemoteCopyGroup(rcg_name)
- rcg_targets = rcg['targets']
- for target in rcg_targets:
- allowed_names.append(target['targetName'])
+ for volume in volumes:
+ if self._volume_of_replicated_type(volume):
+ location = volume.get('provider_location')
+ remote_rcg_name = self._get_3par_remote_rcg_name(
+ volume['id'],
+ location)
+ rcg = self.client.getRemoteCopyGroup(remote_rcg_name)
+
+ # Make sure all targets are in their natural direction.
+ targets = rcg['targets']
+ for target in targets:
+ if target['roleReversed'] or (
+ target['state'] != self.RC_GROUP_STARTED):
+ return False
+
+ # Make sure all volumes are fully synced.
+ volumes = rcg['volumes']
+ for volume in volumes:
+ remote_volumes = volume['remoteVolumes']
+ for remote_volume in remote_volumes:
+ if remote_volume['syncStatus'] != (
+ self.SYNC_STATUS_COMPLETED):
+ return False
except Exception:
- LOG.warning(_LW("The primary array is currently unreachable. All "
- "targets returned from list_replication_targets "
- "are pulled directly from cinder.conf and are not "
- "guarenteed to be available because they could "
- "not be verified with the primary array."))
-
- replication_targets = []
- volume_type = self._get_volume_type(volume["volume_type_id"])
- extra_specs = volume_type.get("extra_specs")
- replication_mode = extra_specs.get(self.EXTRA_SPEC_REP_MODE)
- replication_mode_num = self._get_remote_copy_mode_num(
- replication_mode)
-
- for target in self._replication_targets:
- if not allowed_names and replication_mode_num == (
- target['replication_mode']) or (
- target['target_device_id'] in allowed_names):
- list_vals = {'target_device_id': target['target_device_id']}
- replication_targets.append(list_vals)
+ # If there was a problem, we will return false so we can
+ # log an error in the parent function.
+ return False
- return {'volume_id': volume['id'],
- 'targets': replication_targets}
+ return True
def _do_replication_setup(self):
replication_targets = []
# Format hpe3par_iscsi_chap_enabled as a bool
remote_array['hpe3par_iscsi_chap_enabled'] = (
dev.get('hpe3par_iscsi_chap_enabled') == 'True')
- array_name = remote_array['target_device_id']
+ array_name = remote_array['backend_id']
# Make sure we can log into the array, that it has been
# correctly configured, and its API version meets the
LOG.warning(msg)
elif not self._is_valid_replication_array(remote_array):
msg = (_LW("'%s' is not a valid replication array. "
- "In order to be valid, target_device_id, "
+ "In order to be valid, backend_id, "
"replication_mode, "
"hpe3par_api_url, hpe3par_username, "
"hpe3par_password, cpg_map, san_ip, "
def _is_valid_replication_array(self, target):
required_flags = ['hpe3par_api_url', 'hpe3par_username',
'hpe3par_password', 'san_ip', 'san_login',
- 'san_password', 'target_device_id',
+ 'san_password', 'backend_id',
'replication_mode', 'cpg_map']
try:
self.check_replication_flags(target, required_flags)
ret_mode = self.PERIODIC
return ret_mode
- def _get_3par_config(self, volume):
+ def _get_3par_config(self):
self._do_replication_setup()
conf = None
- if self._replication_enabled and volume:
- provider_location = volume.get('provider_location')
- if provider_location:
- if volume.get('replication_status') == 'failed-over':
- _, provider_location = provider_location.split(':')
-
- for target in self._replication_targets:
- if target['id'] == provider_location:
- conf = target
- break
+ if self._replication_enabled:
+ for target in self._replication_targets:
+ if target['backend_id'] == self._active_backend_id:
+ conf = target
+ break
self._build_3par_config(conf)
def _build_3par_config(self, conf=None):
with unmanaged replication.
"""
if conf:
+ self._client_conf['hpe3par_cpg'] = self._generate_hpe3par_cpgs(
+ conf.get('cpg_map'))
self._client_conf['hpe3par_username'] = (
conf.get('hpe3par_username'))
self._client_conf['hpe3par_password'] = (
conf.get('iscsi_ip_address'))
self._client_conf['iscsi_port'] = conf.get('iscsi_port')
else:
+ self._client_conf['hpe3par_cpg'] = (
+ self.config.hpe3par_cpg)
self._client_conf['hpe3par_username'] = (
self.config.hpe3par_username)
self._client_conf['hpe3par_password'] = (
return ret_target_cpg
+ def _generate_hpe3par_cpgs(self, cpg_map):
+ hpe3par_cpgs = []
+ cpg_pairs = cpg_map.split(' ')
+ for cpg_pair in cpg_pairs:
+ cpgs = cpg_pair.split(':')
+ hpe3par_cpgs.append(cpgs[1])
+
+ return hpe3par_cpgs
+
+ def _get_replication_targets(self):
+ replication_targets = []
+ for target in self._replication_targets:
+ replication_targets.append(target['backend_id'])
+
+ return replication_targets
+
def _do_volume_replication_setup(self, volume):
"""This function will do or ensure the following:
# are set correctly.
volume_type = self._get_volume_type(volume["volume_type_id"])
extra_specs = volume_type.get("extra_specs")
- replication_mode = extra_specs.get(self.EXTRA_SPEC_REP_MODE)
+ replication_mode = extra_specs.get(
+ self.EXTRA_SPEC_REP_MODE, self.DEFAULT_REP_MODE)
replication_mode_num = self._get_remote_copy_mode_num(
replication_mode)
replication_sync_period = extra_specs.get(
- self.EXTRA_SPEC_REP_SYNC_PERIOD)
+ self.EXTRA_SPEC_REP_SYNC_PERIOD, self.DEFAULT_SYNC_PERIOD)
if replication_sync_period:
replication_sync_period = int(replication_sync_period)
if not self._is_replication_mode_correct(replication_mode,
if target['replication_mode'] == replication_mode_num:
cpg = self._get_cpg_from_cpg_map(target['cpg_map'],
local_cpg)
- rcg_target = {'targetName': target['target_device_id'],
+ rcg_target = {'targetName': target['backend_id'],
'mode': replication_mode_num,
'snapCPG': cpg,
'userCPG': cpg}
rcg_targets.append(rcg_target)
- sync_target = {'targetName': target['target_device_id'],
+ sync_target = {'targetName': target['backend_id'],
'syncPeriod': replication_sync_period}
sync_targets.append(sync_target)
for target in self._replication_targets:
# Only add targets that match the volumes replication mode.
if target['replication_mode'] == replication_mode_num:
- rcg_target = {'targetName': target['target_device_id'],
+ rcg_target = {'targetName': target['backend_id'],
'secVolumeName': vol_name}
rcg_targets.append(rcg_target)
optional = {'volumeAutoCreation': True}
pass
def _delete_replicated_failed_over_volume(self, volume):
- old_location, new_location = volume['provider_location'].split(':')
- rcg_name = self._get_3par_remote_rcg_name(volume['id'], old_location)
+ location = volume.get('provider_location')
+ rcg_name = self._get_3par_remote_rcg_name(volume['id'], location)
targets = self.client.getRemoteCopyGroup(rcg_name)['targets']
# When failed over, we want to temporarily disable config mirroring
# in order to be allowed to delete the volume and remote copy group
3.0.3 - Adds v2 unmanaged replication support
3.0.4 - Adding manage/unmanage snapshot support
3.0.5 - Optimize array ID retrieval
+ 3.0.6 - Update replication to version 2.1
"""
- VERSION = "3.0.5"
+ VERSION = "3.0.6"
def __init__(self, *args, **kwargs):
super(HPE3PARFCDriver, self).__init__(*args, **kwargs)
+ self._active_backend_id = kwargs.get('active_backend_id', None)
self.configuration.append_config_values(hpecommon.hpe3par_opts)
self.configuration.append_config_values(san.san_opts)
self.lookup_service = fczm_utils.create_lookup_service()
def _init_common(self):
- return hpecommon.HPE3PARCommon(self.configuration)
+ return hpecommon.HPE3PARCommon(self.configuration,
+ self._active_backend_id)
- def _login(self, volume=None, timeout=None):
+ def _login(self, timeout=None):
common = self._init_common()
# If replication is enabled and we cannot login, we do not want to
# raise an exception so a failover can still be executed.
try:
- common.do_setup(None, volume=volume, timeout=timeout,
- stats=self._stats)
+ common.do_setup(None, timeout=timeout, stats=self._stats)
common.client_login()
except Exception:
if common._replication_enabled:
pass
def create_volume(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
return common.create_volume(volume)
finally:
self._logout(common)
def create_cloned_volume(self, volume, src_vref):
- common = self._login(volume)
+ common = self._login()
try:
return common.create_cloned_volume(volume, src_vref)
finally:
self._logout(common)
def delete_volume(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
common.delete_volume(volume)
finally:
TODO: support using the size from the user.
"""
- common = self._login(volume)
+ common = self._login()
try:
return common.create_volume_from_snapshot(volume, snapshot)
finally:
self._logout(common)
def create_snapshot(self, snapshot):
- common = self._login(snapshot['volume'])
+ common = self._login()
try:
common.create_snapshot(snapshot)
finally:
self._logout(common)
def delete_snapshot(self, snapshot):
- common = self._login(snapshot['volume'])
+ common = self._login()
try:
common.delete_snapshot(snapshot)
finally:
* Create a VLUN for that HOST with the volume we want to export.
"""
- common = self._login(volume)
+ common = self._login()
try:
# we have to make sure we have a host
host = self._create_host(common, volume, connector)
@fczm_utils.RemoveFCZone
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance."""
- common = self._login(volume)
+ common = self._login()
try:
hostname = common._safe_hostname(connector['host'])
common.terminate_connection(volume, hostname,
pass
def extend_volume(self, volume, new_size):
- common = self._login(volume)
+ common = self._login()
try:
common.extend_volume(volume, new_size)
finally:
self._logout(common)
def manage_existing(self, volume, existing_ref):
- common = self._login(volume)
+ common = self._login()
try:
return common.manage_existing(volume, existing_ref)
finally:
self._logout(common)
def manage_existing_get_size(self, volume, existing_ref):
- common = self._login(volume)
+ common = self._login()
try:
return common.manage_existing_get_size(volume, existing_ref)
finally:
self._logout(common)
def unmanage(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
common.unmanage(volume)
finally:
def attach_volume(self, context, volume, instance_uuid, host_name,
mountpoint):
- common = self._login(volume)
+ common = self._login()
try:
common.attach_volume(volume, instance_uuid)
finally:
self._logout(common)
def detach_volume(self, context, volume, attachment=None):
- common = self._login(volume)
+ common = self._login()
try:
common.detach_volume(volume, attachment)
finally:
def retype(self, context, volume, new_type, diff, host):
"""Convert the volume to be of the new type."""
- common = self._login(volume)
+ common = self._login()
try:
return common.retype(volume, new_type, diff, host)
finally:
"to a host with storage_protocol=%s.", protocol)
return False, None
- common = self._login(volume)
+ common = self._login()
try:
return common.migrate_volume(volume, host)
finally:
def update_migrated_volume(self, context, volume, new_volume,
original_volume_status):
"""Update the name of the migrated volume to it's new ID."""
- common = self._login(volume)
+ common = self._login()
try:
return common.update_migrated_volume(context, volume, new_volume,
original_volume_status)
self._logout(common)
def get_pool(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
return common.get_cpg(volume)
except hpeexceptions.HTTPNotFound:
finally:
self._logout(common)
- def replication_enable(self, context, volume):
- """Enable replication on a replication capable volume."""
- common = self._login(volume)
- try:
- return common.replication_enable(context, volume)
- finally:
- self._logout(common)
-
- def replication_disable(self, context, volume):
- """Disable replication on the specified volume."""
- common = self._login(volume)
- try:
- return common.replication_disable(context, volume)
- finally:
- self._logout(common)
-
- def replication_failover(self, context, volume, secondary):
+ def failover_host(self, context, volumes, secondary_backend_id):
"""Force failover to a secondary replication target."""
- common = self._login(volume, timeout=30)
- try:
- return common.replication_failover(context, volume, secondary)
- finally:
- self._logout(common)
-
- def list_replication_targets(self, context, volume):
- """Provides a means to obtain replication targets for a volume."""
- common = self._login(volume, timeout=30)
+ common = self._login(timeout=30)
try:
- return common.list_replication_targets(context, volume)
+ # Update the active_backend_id in the driver and return it.
+ active_backend_id, volume_updates = common.failover_host(
+ context, volumes, secondary_backend_id)
+ self._active_backend_id = active_backend_id
+ return active_backend_id, volume_updates
finally:
self._logout(common)
3.0.5 - Adds v2 unmanaged replication support
3.0.6 - Adding manage/unmanage snapshot support
3.0.7 - Optimize array ID retrieval
+ 3.0.8 - Update replication to version 2.1
"""
- VERSION = "3.0.7"
+ VERSION = "3.0.8"
def __init__(self, *args, **kwargs):
super(HPE3PARISCSIDriver, self).__init__(*args, **kwargs)
+ self._active_backend_id = kwargs.get('active_backend_id', None)
self.configuration.append_config_values(hpecommon.hpe3par_opts)
self.configuration.append_config_values(san.san_opts)
def _init_common(self):
- return hpecommon.HPE3PARCommon(self.configuration)
+ return hpecommon.HPE3PARCommon(self.configuration,
+ self._active_backend_id)
- def _login(self, volume=None, timeout=None):
+ def _login(self, timeout=None):
common = self._init_common()
# If replication is enabled and we cannot login, we do not want to
# raise an exception so a failover can still be executed.
try:
- common.do_setup(None, volume=volume, timeout=timeout,
- stats=self._stats)
+ common.do_setup(None, timeout=timeout, stats=self._stats)
common.client_login()
except Exception:
if common._replication_enabled:
pass
def create_volume(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
return common.create_volume(volume)
finally:
def create_cloned_volume(self, volume, src_vref):
"""Clone an existing volume."""
- common = self._login(volume)
+ common = self._login()
try:
return common.create_cloned_volume(volume, src_vref)
finally:
self._logout(common)
def delete_volume(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
common.delete_volume(volume)
finally:
TODO: support using the size from the user.
"""
- common = self._login(volume)
+ common = self._login()
try:
return common.create_volume_from_snapshot(volume, snapshot)
finally:
self._logout(common)
def create_snapshot(self, snapshot):
- common = self._login(snapshot['volume'])
+ common = self._login()
try:
common.create_snapshot(snapshot)
finally:
self._logout(common)
def delete_snapshot(self, snapshot):
- common = self._login(snapshot['volume'])
+ common = self._login()
try:
common.delete_snapshot(snapshot)
finally:
* Create a host on the 3par
* create vlun on the 3par
"""
- common = self._login(volume)
+ common = self._login()
try:
# If the volume has been failed over, we need to reinitialize
# iSCSI ports so they represent the new array.
def terminate_connection(self, volume, connector, **kwargs):
"""Driver entry point to unattach a volume from an instance."""
- common = self._login(volume)
+ common = self._login()
try:
hostname = common._safe_hostname(connector['host'])
common.terminate_connection(
return model_update
def create_export(self, context, volume, connector):
- common = self._login(volume)
+ common = self._login()
try:
return self._do_export(common, volume)
finally:
Also retrieves CHAP credentials, if present on the volume
"""
- common = self._login(volume)
+ common = self._login()
try:
vol_name = common._get_3par_vol_name(volume['id'])
common.client.getVolume(vol_name)
return current_least_used_nsp
def extend_volume(self, volume, new_size):
- common = self._login(volume)
+ common = self._login()
try:
common.extend_volume(volume, new_size)
finally:
self._logout(common)
def manage_existing(self, volume, existing_ref):
- common = self._login(volume)
+ common = self._login()
try:
return common.manage_existing(volume, existing_ref)
finally:
self._logout(common)
def manage_existing_get_size(self, volume, existing_ref):
- common = self._login(volume)
+ common = self._login()
try:
return common.manage_existing_get_size(volume, existing_ref)
finally:
self._logout(common)
def unmanage(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
common.unmanage(volume)
finally:
def attach_volume(self, context, volume, instance_uuid, host_name,
mountpoint):
- common = self._login(volume)
+ common = self._login()
try:
common.attach_volume(volume, instance_uuid)
finally:
self._logout(common)
def detach_volume(self, context, volume, attachment=None):
- common = self._login(volume)
+ common = self._login()
try:
common.detach_volume(volume, attachment)
finally:
def retype(self, context, volume, new_type, diff, host):
"""Convert the volume to be of the new type."""
- common = self._login(volume)
+ common = self._login()
try:
return common.retype(volume, new_type, diff, host)
finally:
"to a host with storage_protocol=%s.", protocol)
return False, None
- common = self._login(volume)
+ common = self._login()
try:
return common.migrate_volume(volume, host)
finally:
def update_migrated_volume(self, context, volume, new_volume,
original_volume_status):
"""Update the name of the migrated volume to it's new ID."""
- common = self._login(volume)
+ common = self._login()
try:
return common.update_migrated_volume(context, volume, new_volume,
original_volume_status)
self._logout(common)
def get_pool(self, volume):
- common = self._login(volume)
+ common = self._login()
try:
return common.get_cpg(volume)
except hpeexceptions.HTTPNotFound:
finally:
self._logout(common)
- def replication_enable(self, context, volume):
- """Enable replication on a replication capable volume."""
- common = self._login(volume)
- try:
- return common.replication_enable(context, volume)
- finally:
- self._logout(common)
-
- def replication_disable(self, context, volume):
- """Disable replication on the specified volume."""
- common = self._login(volume)
- try:
- return common.replication_disable(context, volume)
- finally:
- self._logout(common)
-
- def replication_failover(self, context, volume, secondary):
+ def failover_host(self, context, volumes, secondary_backend_id):
"""Force failover to a secondary replication target."""
- common = self._login(volume, timeout=30)
- try:
- return common.replication_failover(context, volume, secondary)
- finally:
- self._logout(common)
-
- def list_replication_targets(self, context, volume):
- """Provides a means to obtain replication targets for a volume."""
- common = self._login(volume, timeout=30)
+ common = self._login(timeout=30)
try:
- return common.list_replication_targets(context, volume)
+ # Update the active_backend_id in the driver and return it.
+ active_backend_id, volume_updates = common.failover_host(
+ context, volumes, secondary_backend_id)
+ self._active_backend_id = active_backend_id
+ return active_backend_id, volume_updates
finally:
self._logout(common)