VNX: Update replication for v2.1
Supporting replication v2.1 specification as well as failback capability. Change-Id: Idb2fae7f5caa240f6c347e60953d5dd1cdf3f8c8 Closes-Bug: #1547543
This commit is contained in:
parent
1a8361ed32
commit
ab2a05aab3
@ -62,7 +62,7 @@ class EMCVNXCLIDriverTestData(object):
|
||||
|
||||
base_lun_name = 'volume-1'
|
||||
replication_metadata = {'host': 'host@backendsec#unit_test_pool',
|
||||
'system': 'FNM11111'}
|
||||
'system': 'fake_serial'}
|
||||
test_volume = {
|
||||
'status': 'creating',
|
||||
'name': 'volume-1',
|
||||
@ -1924,6 +1924,7 @@ class EMCVNXCLIDriverISCSITestCase(DriverTestCaseBase):
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'consistencygroup_support': 'True',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'pool_name': 'unit_test_pool',
|
||||
'fast_cache_enabled': True,
|
||||
'fast_support': 'True'}
|
||||
@ -4992,6 +4993,7 @@ class EMCVNXCLIDArrayBasedDriverTestCase(DriverTestCaseBase):
|
||||
'thick_provisioning_support': True,
|
||||
'consistencygroup_support': 'True',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'pool_name': 'unit_test_pool',
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'fast_cache_enabled': True,
|
||||
@ -5011,6 +5013,7 @@ class EMCVNXCLIDArrayBasedDriverTestCase(DriverTestCaseBase):
|
||||
'thick_provisioning_support': True,
|
||||
'consistencygroup_support': 'True',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'pool_name': 'unit_test_pool2',
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'fast_cache_enabled': False,
|
||||
@ -5041,6 +5044,7 @@ class EMCVNXCLIDArrayBasedDriverTestCase(DriverTestCaseBase):
|
||||
'consistencygroup_support': 'False',
|
||||
'pool_name': 'unit_test_pool',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'fast_cache_enabled': 'False',
|
||||
'fast_support': 'False'}
|
||||
@ -5059,6 +5063,7 @@ class EMCVNXCLIDArrayBasedDriverTestCase(DriverTestCaseBase):
|
||||
'thick_provisioning_support': True,
|
||||
'consistencygroup_support': 'False',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'pool_name': 'unit_test_pool2',
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'fast_cache_enabled': 'False',
|
||||
@ -5589,6 +5594,7 @@ class EMCVNXCLIDriverFCTestCase(DriverTestCaseBase):
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'consistencygroup_support': 'True',
|
||||
'replication_enabled': False,
|
||||
'replication_targets': [],
|
||||
'pool_name': 'unit_test_pool',
|
||||
'fast_cache_enabled': True,
|
||||
'fast_support': 'True'}
|
||||
@ -5939,17 +5945,18 @@ class EMCVNXCLIMultiPoolsTestCase(DriverTestCaseBase):
|
||||
class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
def setUp(self):
|
||||
super(EMCVNXCLIDriverReplicationV2TestCase, self).setUp()
|
||||
self.target_device_id = 'fake_serial'
|
||||
self.backend_id = 'fake_serial'
|
||||
self.configuration.replication_device = [{
|
||||
'target_device_id': self.target_device_id,
|
||||
'managed_backend_name': 'host@backend#unit_test_pool',
|
||||
'backend_id': self.backend_id,
|
||||
'san_ip': '192.168.1.2', 'san_login': 'admin',
|
||||
'san_password': 'admin', 'san_secondary_ip': '192.168.2.2',
|
||||
'storage_vnx_authentication_type': 'global',
|
||||
'storage_vnx_security_file_dir': None}]
|
||||
|
||||
def generate_driver(self, conf):
|
||||
return emc_cli_iscsi.EMCCLIISCSIDriver(configuration=conf)
|
||||
def generate_driver(self, conf, active_backend_id=None):
|
||||
return emc_cli_iscsi.EMCCLIISCSIDriver(
|
||||
configuration=conf,
|
||||
active_backend_id=active_backend_id)
|
||||
|
||||
def _build_mirror_name(self, volume_id):
|
||||
return 'mirror_' + volume_id
|
||||
@ -5976,8 +5983,7 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
self.assertTrue(model_update['replication_status'] == 'enabled')
|
||||
self.assertTrue(model_update['replication_driver_data'] ==
|
||||
build_replication_data(self.configuration))
|
||||
self.assertDictMatch({'system': self.target_device_id,
|
||||
'host': rep_volume.host,
|
||||
self.assertDictMatch({'system': self.backend_id,
|
||||
'snapcopy': 'False'},
|
||||
model_update['metadata'])
|
||||
fake_cli.assert_has_calls(
|
||||
@ -6051,72 +6057,14 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
mock.call(*self.testData.MIRROR_DESTROY_CMD(mirror_name),
|
||||
poll=True)])
|
||||
|
||||
def test_enable_replication(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
mirror_name = self._build_mirror_name(rep_volume.id)
|
||||
image_uid = '50:06:01:60:88:60:05:FE'
|
||||
commands = [self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
self.testData.MIRROR_SYNC_IMAGE_CMD(
|
||||
mirror_name, image_uid)]
|
||||
results = [[self.testData.MIRROR_LIST_RESULT(
|
||||
mirror_name, 'Administratively fractured'),
|
||||
self.testData.MIRROR_LIST_RESULT(
|
||||
mirror_name)],
|
||||
SUCCEED]
|
||||
fake_cli = self.driverSetup(commands, results)
|
||||
rep_volume.replication_driver_data = build_replication_data(
|
||||
self.configuration)
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
self.driver.replication_enable(None, rep_volume)
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True),
|
||||
mock.call(*self.testData.MIRROR_SYNC_IMAGE_CMD(
|
||||
mirror_name, image_uid), poll=False),
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=False)])
|
||||
|
||||
def test_enable_already_synced(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
mirror_name = self._build_mirror_name(rep_volume.id)
|
||||
commands = [self.testData.MIRROR_LIST_CMD(mirror_name)]
|
||||
results = [self.testData.MIRROR_LIST_RESULT(mirror_name)]
|
||||
fake_cli = self.driverSetup(commands, results)
|
||||
rep_volume.replication_driver_data = build_replication_data(
|
||||
self.configuration)
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
self.driver.replication_enable(None, rep_volume)
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True)])
|
||||
|
||||
def test_disable_replication(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
mirror_name = self._build_mirror_name(rep_volume.id)
|
||||
image_uid = '50:06:01:60:88:60:05:FE'
|
||||
commands = [self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
self.testData.MIRROR_FRACTURE_IMAGE_CMD(
|
||||
mirror_name, image_uid)]
|
||||
results = [self.testData.MIRROR_LIST_RESULT(mirror_name),
|
||||
SUCCEED]
|
||||
fake_cli = self.driverSetup(commands, results)
|
||||
rep_volume.replication_driver_data = build_replication_data(
|
||||
self.configuration)
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
self.driver.replication_disable(None, rep_volume)
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True),
|
||||
mock.call(*self.testData.MIRROR_FRACTURE_IMAGE_CMD(mirror_name,
|
||||
image_uid), poll=False)])
|
||||
|
||||
@mock.patch(
|
||||
"cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper." +
|
||||
"get_lun_by_name",
|
||||
mock.Mock(return_value={'lun_id': 1}))
|
||||
@mock.patch(
|
||||
"cinder.volume.volume_types."
|
||||
"get_volume_type_extra_specs",
|
||||
mock.Mock(return_value={'replication_enabled': '<is> True'}))
|
||||
def test_failover_replication_from_primary(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
@ -6132,26 +6080,24 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
self.configuration)
|
||||
rep_volume.metadata = self.testData.replication_metadata
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
model_update = self.driver.replication_failover(
|
||||
None, rep_volume,
|
||||
self.target_device_id)
|
||||
back_id, model_update = self.driver.failover_host(
|
||||
None, [rep_volume],
|
||||
self.backend_id)
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True),
|
||||
mock.call(*self.testData.MIRROR_PROMOTE_IMAGE_CMD(mirror_name,
|
||||
image_uid), poll=False)])
|
||||
self.assertEqual(
|
||||
self.configuration.replication_device[0]['managed_backend_name'],
|
||||
model_update['host'])
|
||||
expected = build_provider_location('1', 'lun', rep_volume.name,
|
||||
self.target_device_id)
|
||||
provider_location = model_update['provider_location']
|
||||
# Don't compare the exact string but the set of items: dictionary
|
||||
# items are rendered in a random order because of the hash
|
||||
# randomization
|
||||
self.assertSetEqual(set(expected.split('|')),
|
||||
set(provider_location.split('|')))
|
||||
build_provider_location(
|
||||
'1', 'lun', rep_volume.name,
|
||||
self.backend_id),
|
||||
model_update[0]['updates']['provider_location'])
|
||||
|
||||
@mock.patch(
|
||||
"cinder.volume.volume_types."
|
||||
"get_volume_type_extra_specs",
|
||||
mock.Mock(return_value={'replication_enabled': '<is> True'}))
|
||||
def test_failover_replication_from_secondary(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
@ -6174,14 +6120,49 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
'cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper') \
|
||||
as fake_remote:
|
||||
fake_remote.return_value = self.driver.cli._client
|
||||
self.driver.replication_failover(None, rep_volume,
|
||||
'FNM11111')
|
||||
backend_id, data = self.driver.failover_host(
|
||||
None, [rep_volume], 'default')
|
||||
updates = data[0]['updates']
|
||||
rep_status = updates['replication_status']
|
||||
self.assertEqual('enabled', rep_status)
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True),
|
||||
mock.call(*self.testData.MIRROR_PROMOTE_IMAGE_CMD(mirror_name,
|
||||
image_uid), poll=False)])
|
||||
|
||||
@mock.patch(
|
||||
"cinder.volume.volume_types."
|
||||
"get_volume_type_extra_specs",
|
||||
mock.Mock(return_value={'replication_enabled': '<is> True'}))
|
||||
def test_failover_replication_invalid_backend_id(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
self._build_mirror_name(rep_volume.id)
|
||||
fake_cli = self.driverSetup([], [])
|
||||
rep_volume.replication_driver_data = build_replication_data(
|
||||
self.configuration)
|
||||
rep_volume.metadata = self.testData.replication_metadata
|
||||
driver_data = json.loads(rep_volume.replication_driver_data)
|
||||
driver_data['is_primary'] = False
|
||||
rep_volume.replication_driver_data = json.dumps(driver_data)
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
with mock.patch(
|
||||
'cinder.volume.drivers.emc.emc_vnx_cli.CommandLineHelper') \
|
||||
as fake_remote:
|
||||
fake_remote.return_value = self.driver.cli._client
|
||||
invalid = 'invalid_backend_id'
|
||||
self.assertRaisesRegex(exception.VolumeBackendAPIException,
|
||||
"Invalid secondary_backend_id specified",
|
||||
self.driver.failover_host,
|
||||
None,
|
||||
[rep_volume],
|
||||
invalid)
|
||||
|
||||
@mock.patch(
|
||||
"cinder.volume.volume_types."
|
||||
"get_volume_type_extra_specs",
|
||||
mock.Mock(return_value={'replication_enabled': '<is> True'}))
|
||||
def test_failover_already_promoted(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
@ -6197,11 +6178,12 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
self.configuration)
|
||||
rep_volume.metadata = self.testData.replication_metadata
|
||||
self.driver.cli._mirror._secondary_client.command_execute = fake_cli
|
||||
self.assertRaisesRegex(exception.EMCVnxCLICmdError,
|
||||
'UID of the secondary image '
|
||||
'to be promoted is not local',
|
||||
self.driver.replication_failover,
|
||||
None, rep_volume, self.target_device_id)
|
||||
new_backend_id, model_updates = self.driver.failover_host(
|
||||
None, [rep_volume], self.backend_id)
|
||||
self.assertEqual(rep_volume.id, model_updates[0]['volume_id'])
|
||||
self.assertEqual('error',
|
||||
model_updates[0]['updates']['replication_status'])
|
||||
|
||||
fake_cli.assert_has_calls([
|
||||
mock.call(*self.testData.MIRROR_LIST_CMD(mirror_name),
|
||||
poll=True),
|
||||
@ -6246,18 +6228,20 @@ class EMCVNXCLIDriverReplicationV2TestCase(DriverTestCaseBase):
|
||||
poll=False)]
|
||||
fake_cli.assert_has_calls(expected)
|
||||
|
||||
def test_list_replication_targets(self):
|
||||
rep_volume = EMCVNXCLIDriverTestData.convert_volume(
|
||||
self.testData.test_volume_replication)
|
||||
rep_volume.replication_driver_data = build_replication_data(
|
||||
self.configuration)
|
||||
expect_targets = {'volume_id': rep_volume.id,
|
||||
'targets':
|
||||
[{'type': 'managed',
|
||||
'target_device_id': self.target_device_id}]}
|
||||
def test_build_client_with_invalid_id(self):
|
||||
self.driverSetup([], [])
|
||||
data = self.driver.list_replication_targets(None, rep_volume)
|
||||
self.assertDictMatch(expect_targets, data)
|
||||
self.assertRaisesRegex(
|
||||
exception.VolumeBackendAPIException,
|
||||
'replication_device with backend_id .* is missing.',
|
||||
self.driver.cli._build_client,
|
||||
'invalid_backend_id')
|
||||
|
||||
def test_build_client_with_id(self):
|
||||
self.driverSetup([], [])
|
||||
cli_client = self.driver.cli._build_client(
|
||||
active_backend_id='fake_serial')
|
||||
self.assertEqual('192.168.1.2', cli_client.active_storage_ip)
|
||||
self.assertEqual('192.168.1.2', cli_client.primary_storage_ip)
|
||||
|
||||
VNXError = emc_vnx_cli.VNXError
|
||||
|
||||
|
@ -63,11 +63,11 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCCLIFCDriver, self).__init__(*args, **kwargs)
|
||||
self.cli = emc_vnx_cli.getEMCVnxCli(
|
||||
'FC',
|
||||
configuration=self.configuration)
|
||||
configuration=self.configuration,
|
||||
active_backend_id=kwargs.get('active_backend_id'))
|
||||
self.VERSION = self.cli.VERSION
|
||||
|
||||
def check_for_setup_error(self):
|
||||
@ -301,18 +301,6 @@ class EMCCLIFCDriver(driver.FibreChannelDriver):
|
||||
def backup_use_temp_snapshot(self):
|
||||
return True
|
||||
|
||||
def replication_enable(self, context, volume):
|
||||
"""Enables replication on a replication capable volume."""
|
||||
return self.cli.replication_enable(context, volume)
|
||||
|
||||
def replication_disable(self, context, volume):
|
||||
"""Disables replication on a replication-enabled volume."""
|
||||
return self.cli.replication_disable(context, volume)
|
||||
|
||||
def replication_failover(self, context, volume, secondary):
|
||||
def failover_host(self, context, volumes, secondary_backend_id):
|
||||
"""Failovers volume from primary device to secondary."""
|
||||
return self.cli.replication_failover(context, volume, secondary)
|
||||
|
||||
def list_replication_targets(self, context, volume):
|
||||
"""Returns volume replication info."""
|
||||
return self.cli.list_replication_targets(context, volume)
|
||||
return self.cli.failover_host(context, volumes, secondary_backend_id)
|
||||
|
@ -61,11 +61,11 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(EMCCLIISCSIDriver, self).__init__(*args, **kwargs)
|
||||
self.cli = emc_vnx_cli.getEMCVnxCli(
|
||||
'iSCSI',
|
||||
configuration=self.configuration)
|
||||
configuration=self.configuration,
|
||||
active_backend_id=kwargs.get('active_backend_id'))
|
||||
self.VERSION = self.cli.VERSION
|
||||
|
||||
def check_for_setup_error(self):
|
||||
@ -280,18 +280,6 @@ class EMCCLIISCSIDriver(driver.ISCSIDriver):
|
||||
def backup_use_temp_snapshot(self):
|
||||
return True
|
||||
|
||||
def replication_enable(self, context, volume):
|
||||
"""Enables replication on a replication capable volume."""
|
||||
return self.cli.replication_enable(context, volume)
|
||||
|
||||
def replication_disable(self, context, volume):
|
||||
"""Disables replication on a replication-enabled volume."""
|
||||
return self.cli.replication_disable(context, volume)
|
||||
|
||||
def replication_failover(self, context, volume, secondary):
|
||||
def failover_host(self, context, volumes, secondary_backend_id):
|
||||
"""Failovers volume from primary device to secondary."""
|
||||
return self.cli.replication_failover(context, volume, secondary)
|
||||
|
||||
def list_replication_targets(self, context, volume):
|
||||
"""Returns volume replication info."""
|
||||
return self.cli.list_replication_targets(context, volume)
|
||||
return self.cli.failover_host(context, volumes, secondary_backend_id)
|
||||
|
@ -2124,7 +2124,7 @@ class EMCVnxCliBase(object):
|
||||
tmp_smp_for_backup_prefix = 'tmp-smp-'
|
||||
snap_as_vol_prefix = 'snap-as-vol-'
|
||||
|
||||
def __init__(self, prtcl, configuration=None):
|
||||
def __init__(self, prtcl, configuration=None, active_backend_id=None):
|
||||
self.protocol = prtcl
|
||||
self.configuration = configuration
|
||||
self.max_luns_per_sg = self.configuration.max_luns_per_storage_group
|
||||
@ -2151,7 +2151,8 @@ class EMCVnxCliBase(object):
|
||||
"Initiator auto registration is not enabled. "
|
||||
"Please register initiator manually."))
|
||||
self.hlu_set = set(range(1, self.max_luns_per_sg + 1))
|
||||
self._client = CommandLineHelper(self.configuration)
|
||||
self._client = self._build_client(active_backend_id)
|
||||
self._active_backend_id = active_backend_id
|
||||
# Create connection to the secondary storage device
|
||||
self._mirror = self._build_mirror_view()
|
||||
self.update_enabler_in_volume_stats()
|
||||
@ -2817,12 +2818,16 @@ class EMCVnxCliBase(object):
|
||||
pool_stats['max_over_subscription_ratio'] = (
|
||||
self.max_over_subscription_ratio)
|
||||
# Add replication V2 support
|
||||
targets = []
|
||||
if self._mirror:
|
||||
pool_stats['replication_enabled'] = True
|
||||
pool_stats['replication_count'] = 1
|
||||
pool_stats['replication_type'] = ['sync']
|
||||
for device in self.configuration.replication_device:
|
||||
targets.append(device['backend_id'])
|
||||
else:
|
||||
pool_stats['replication_enabled'] = False
|
||||
pool_stats['replication_targets'] = targets
|
||||
return pool_stats
|
||||
|
||||
def update_enabler_in_volume_stats(self):
|
||||
@ -3947,82 +3952,72 @@ class EMCVnxCliBase(object):
|
||||
|
||||
return specs
|
||||
|
||||
def replication_enable(self, context, volume):
|
||||
"""Enables replication for the volume."""
|
||||
mirror_name = self._construct_mirror_name(volume)
|
||||
mirror_view = self._get_mirror_view(volume)
|
||||
mirror_view.sync_image(mirror_name)
|
||||
|
||||
def replication_disable(self, context, volume):
|
||||
"""Disables replication for the volume."""
|
||||
mirror_name = self._construct_mirror_name(volume)
|
||||
mirror_view = self._get_mirror_view(volume)
|
||||
mirror_view.fracture_image(mirror_name)
|
||||
|
||||
def replication_failover(self, context, volume, secondary):
|
||||
def failover_host(self, context, volumes, secondary_backend_id):
|
||||
"""Fails over the volume back and forth.
|
||||
|
||||
Driver needs to update following info for this volume:
|
||||
1. host: to point to the new host
|
||||
2. provider_location: update serial number and lun id
|
||||
"""
|
||||
rep_data = json.loads(volume['replication_driver_data'])
|
||||
is_primary = rep_data['is_primary']
|
||||
if is_primary:
|
||||
remote_device_id = (
|
||||
self.configuration.replication_device[0]['target_device_id'])
|
||||
volume_update_list = []
|
||||
|
||||
if secondary_backend_id != 'default':
|
||||
rep_status = 'failed-over'
|
||||
backend_id = (
|
||||
self.configuration.replication_device[0]['backend_id'])
|
||||
if secondary_backend_id != backend_id:
|
||||
msg = (_('Invalid secondary_backend_id specified. '
|
||||
'Valid backend id is %s.') % backend_id)
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
else:
|
||||
remote_device_id = self._get_volume_metadata(volume)['system']
|
||||
if secondary != remote_device_id:
|
||||
msg = (_('Invalid secondary specified, choose from %s.')
|
||||
% [remote_device_id])
|
||||
LOG.error(msg)
|
||||
raise exception.VolumeBackendAPIException(data=msg)
|
||||
rep_status = 'enabled'
|
||||
|
||||
mirror_name = self._construct_mirror_name(volume)
|
||||
mirror_view = self._get_mirror_view(volume)
|
||||
remote_client = mirror_view._secondary_client
|
||||
if is_primary:
|
||||
new_host = (
|
||||
self.configuration.replication_device[0][
|
||||
'managed_backend_name'])
|
||||
else:
|
||||
new_host = self._get_volume_metadata(volume)['host']
|
||||
def failover_one(volume, new_status):
|
||||
rep_data = json.loads(volume['replication_driver_data'])
|
||||
is_primary = rep_data['is_primary']
|
||||
mirror_name = self._construct_mirror_name(volume)
|
||||
mirror_view = self._get_mirror_view(volume)
|
||||
remote_client = mirror_view._secondary_client
|
||||
|
||||
rep_data.update({'is_primary': not is_primary})
|
||||
# Transfer ownership to secondary and
|
||||
# update provider_location field
|
||||
provider_location = volume['provider_location']
|
||||
provider_location = self._update_provider_location(
|
||||
provider_location,
|
||||
'system', remote_client.get_array_serial()['array_serial'])
|
||||
self.get_array_serial()
|
||||
provider_location = self._update_provider_location(
|
||||
provider_location,
|
||||
'id',
|
||||
six.text_type(remote_client.get_lun_by_name(volume.name)['lun_id'])
|
||||
)
|
||||
|
||||
mirror_view.promote_image(mirror_name)
|
||||
model_update = {'host': new_host,
|
||||
'replication_driver_data': json.dumps(rep_data),
|
||||
'provider_location': provider_location}
|
||||
return model_update
|
||||
|
||||
def list_replication_targets(self, context, volume):
|
||||
"""Provides replication target(s) for a volume."""
|
||||
targets = {'volume_id': volume.id, 'targets': []}
|
||||
rep_data = json.loads(volume['replication_driver_data'])
|
||||
is_primary = rep_data['is_primary']
|
||||
if is_primary:
|
||||
remote_device_id = (
|
||||
self.configuration.replication_device[0]['target_device_id'])
|
||||
else:
|
||||
remote_device_id = self._get_volume_metadata(volume)['system']
|
||||
|
||||
targets['targets'] = [{'type': 'managed',
|
||||
'target_device_id': remote_device_id}]
|
||||
return targets
|
||||
provider_location = volume['provider_location']
|
||||
try:
|
||||
mirror_view.promote_image(mirror_name)
|
||||
except exception.EMCVnxCLICmdError as ex:
|
||||
msg = _LE(
|
||||
'Failed to failover volume %(volume_id)s '
|
||||
'to %(target)s: %(error)s.')
|
||||
LOG.error(msg, {'volume_id': volume.id,
|
||||
'target': secondary_backend_id,
|
||||
'error': ex},)
|
||||
new_status = 'error'
|
||||
else:
|
||||
rep_data.update({'is_primary': not is_primary})
|
||||
# Transfer ownership to secondary_backend_id and
|
||||
# update provider_location field
|
||||
provider_location = self._update_provider_location(
|
||||
provider_location,
|
||||
'system', remote_client.get_array_serial()['array_serial'])
|
||||
provider_location = self._update_provider_location(
|
||||
provider_location,
|
||||
'id',
|
||||
six.text_type(
|
||||
remote_client.get_lun_by_name(volume.name)['lun_id'])
|
||||
)
|
||||
model_update = {'volume_id': volume.id,
|
||||
'updates':
|
||||
{'replication_driver_data':
|
||||
json.dumps(rep_data),
|
||||
'replication_status': new_status,
|
||||
'provider_location': provider_location}}
|
||||
volume_update_list.append(model_update)
|
||||
for volume in volumes:
|
||||
if self._is_replication_enabled(volume):
|
||||
failover_one(volume, rep_status)
|
||||
else:
|
||||
volume_update_list.append({
|
||||
'volume_id': volume.id,
|
||||
'updates': {'status': 'error'}})
|
||||
return secondary_backend_id, volume_update_list
|
||||
|
||||
def _is_replication_enabled(self, volume):
|
||||
"""Return True if replication extra specs is specified.
|
||||
@ -4045,8 +4040,10 @@ class EMCVnxCliBase(object):
|
||||
'for volume: %s.', volume.id)
|
||||
lun_size = volume['size']
|
||||
mirror_name = self._construct_mirror_name(volume)
|
||||
pool_name = vol_utils.extract_host(volume.host, 'pool')
|
||||
self._mirror.create_mirror_workflow(
|
||||
mirror_name, primary_lun_id, volume.name, lun_size,
|
||||
mirror_name, primary_lun_id, pool_name,
|
||||
volume.name, lun_size,
|
||||
provisioning, tiering)
|
||||
|
||||
LOG.info(_LI('Successfully setup replication for %s.'), volume.id)
|
||||
@ -4055,7 +4052,6 @@ class EMCVnxCliBase(object):
|
||||
self.configuration),
|
||||
'replication_status': 'enabled'})
|
||||
metadata_update = {
|
||||
'host': volume.host,
|
||||
'system': self.get_array_serial()}
|
||||
return rep_update, metadata_update
|
||||
|
||||
@ -4103,6 +4099,35 @@ class EMCVnxCliBase(object):
|
||||
driver_data['is_primary'] = True
|
||||
return json.dumps(driver_data)
|
||||
|
||||
def _build_client(self, active_backend_id=None):
|
||||
"""Builds a client pointing to the right VNX."""
|
||||
if not active_backend_id:
|
||||
return CommandLineHelper(self.configuration)
|
||||
else:
|
||||
configuration = self.configuration
|
||||
if not configuration.replication_device:
|
||||
err_msg = (
|
||||
_('replication_device should be configured '
|
||||
'on backend: %s.') % configuration.config_group)
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
current_target = None
|
||||
for target in configuration.replication_device:
|
||||
if target['backend_id'] == active_backend_id:
|
||||
current_target = target
|
||||
break
|
||||
if not current_target:
|
||||
err_msg = (
|
||||
_('replication_device with backend_id [%s] is missing.')
|
||||
% active_backend_id)
|
||||
LOG.error(err_msg)
|
||||
raise exception.VolumeBackendAPIException(data=err_msg)
|
||||
target_conf = copy.copy(configuration)
|
||||
for key in self.REPLICATION_KEYS:
|
||||
if key in current_target:
|
||||
setattr(target_conf, key, current_target[key])
|
||||
return CommandLineHelper(target_conf)
|
||||
|
||||
def _build_mirror_view(self, volume=None):
|
||||
"""Builds a client for remote storage device.
|
||||
|
||||
@ -4126,19 +4151,13 @@ class EMCVnxCliBase(object):
|
||||
configuration.config_group)
|
||||
return None
|
||||
remote_info = configuration.replication_device[0]
|
||||
|
||||
pool_name = None
|
||||
managed_backend_name = remote_info.get('managed_backend_name')
|
||||
if managed_backend_name:
|
||||
pool_name = vol_utils.extract_host(managed_backend_name, 'pool')
|
||||
# Copy info to replica configuration for remote client
|
||||
replica_conf = copy.copy(configuration)
|
||||
for key in self.REPLICATION_KEYS:
|
||||
if key in remote_info:
|
||||
config.Configuration.__setattr__(replica_conf,
|
||||
key, remote_info[key])
|
||||
setattr(replica_conf, key, remote_info[key])
|
||||
_remote_client = CommandLineHelper(replica_conf)
|
||||
_mirror = MirrorView(self._client, _remote_client, pool_name)
|
||||
_mirror = MirrorView(self._client, _remote_client)
|
||||
return _mirror
|
||||
|
||||
def get_pool(self, volume):
|
||||
@ -4359,9 +4378,10 @@ class EMCVnxCliBase(object):
|
||||
return self.stats
|
||||
|
||||
|
||||
def getEMCVnxCli(prtcl, configuration=None):
|
||||
def getEMCVnxCli(prtcl, configuration=None, active_backend_id=None):
|
||||
configuration.append_config_values(loc_opts)
|
||||
return EMCVnxCliBase(prtcl, configuration=configuration)
|
||||
return EMCVnxCliBase(prtcl, configuration=configuration,
|
||||
active_backend_id=active_backend_id)
|
||||
|
||||
|
||||
class CreateSMPTask(task.Task):
|
||||
@ -4598,7 +4618,7 @@ class MirrorView(object):
|
||||
SYNCHRONIZED_STATE = 'Synchronized'
|
||||
CONSISTENT_STATE = 'Consistent'
|
||||
|
||||
def __init__(self, client, secondary_client, pool_name, mode='sync'):
|
||||
def __init__(self, client, secondary_client, mode='sync'):
|
||||
"""Caller needs to initialize MirrorView via this method.
|
||||
|
||||
:param client: client connecting to primary system
|
||||
@ -4607,7 +4627,6 @@ class MirrorView(object):
|
||||
"""
|
||||
self._client = client
|
||||
self._secondary_client = secondary_client
|
||||
self.pool_name = pool_name
|
||||
if mode not in self.SYNCHRONIZE_MODE:
|
||||
msg = _('Invalid synchronize mode specified, allowed '
|
||||
'mode is %s.') % self.SYNCHRONIZE_MODE
|
||||
@ -4615,12 +4634,13 @@ class MirrorView(object):
|
||||
data=msg)
|
||||
self.mode = '-sync'
|
||||
|
||||
def create_mirror_workflow(self, mirror_name, lun_id,
|
||||
def create_mirror_workflow(self, mirror_name, lun_id, pool_name,
|
||||
lun_name, lun_size, provisioning, tiering):
|
||||
"""Creates mirror view for LUN."""
|
||||
store_spec = {'mirror': self}
|
||||
work_flow = self._get_create_mirror_flow(
|
||||
mirror_name, lun_id, lun_name, lun_size, provisioning, tiering)
|
||||
mirror_name, lun_id, pool_name,
|
||||
lun_name, lun_size, provisioning, tiering)
|
||||
flow_engine = taskflow.engines.load(work_flow, store=store_spec)
|
||||
flow_engine.run()
|
||||
|
||||
@ -4630,13 +4650,13 @@ class MirrorView(object):
|
||||
self.destroy_mirror(mirror_name)
|
||||
self.delete_secondary_lun(lun_name)
|
||||
|
||||
def _get_create_mirror_flow(self, mirror_name, lun_id,
|
||||
def _get_create_mirror_flow(self, mirror_name, lun_id, pool_name,
|
||||
lun_name, lun_size, provisioning, tiering):
|
||||
"""Gets mirror create flow."""
|
||||
flow_name = 'create_mirror_view'
|
||||
work_flow = linear_flow.Flow(flow_name)
|
||||
work_flow.add(MirrorCreateTask(mirror_name, lun_id),
|
||||
MirrorSecLunCreateTask(lun_name, lun_size,
|
||||
MirrorSecLunCreateTask(pool_name, lun_name, lun_size,
|
||||
provisioning, tiering),
|
||||
MirrorAddImageTask(mirror_name))
|
||||
return work_flow
|
||||
@ -4657,11 +4677,12 @@ class MirrorView(object):
|
||||
out=out)
|
||||
return rc
|
||||
|
||||
def create_secondary_lun(self, lun_name, lun_size, provisioning,
|
||||
def create_secondary_lun(self, pool_name, lun_name, lun_size,
|
||||
provisioning,
|
||||
tiering, poll=False):
|
||||
"""Creates secondary LUN in remote device."""
|
||||
data = self._secondary_client.create_lun_with_advance_feature(
|
||||
pool=self.pool_name,
|
||||
pool=pool_name,
|
||||
name=lun_name,
|
||||
size=lun_size,
|
||||
provisioning=provisioning,
|
||||
@ -4886,8 +4907,9 @@ class MirrorSecLunCreateTask(task.Task):
|
||||
|
||||
Reversion strategy: Delete secondary LUN.
|
||||
"""
|
||||
def __init__(self, lun_name, lun_size, provisioning, tiering):
|
||||
def __init__(self, pool_name, lun_name, lun_size, provisioning, tiering):
|
||||
super(MirrorSecLunCreateTask, self).__init__(provides='sec_lun_id')
|
||||
self.pool_name = pool_name
|
||||
self.lun_name = lun_name
|
||||
self.lun_size = lun_size
|
||||
self.provisioning = provisioning
|
||||
@ -4896,7 +4918,8 @@ class MirrorSecLunCreateTask(task.Task):
|
||||
def execute(self, mirror, *args, **kwargs):
|
||||
LOG.debug('%s.execute', self.__class__.__name__)
|
||||
sec_lun_id = mirror.create_secondary_lun(
|
||||
self.lun_name, self.lun_size, self.provisioning, self.tiering)
|
||||
self.pool_name, self.lun_name, self.lun_size,
|
||||
self.provisioning, self.tiering)
|
||||
return sec_lun_id
|
||||
|
||||
def revert(self, result, mirror, *args, **kwargs):
|
||||
|
@ -1,3 +0,0 @@
|
||||
---
|
||||
features:
|
||||
- Replication v2 has been added in VNX Cinder driver
|
@ -0,0 +1,3 @@
|
||||
---
|
||||
features:
|
||||
- Adds v2.1 replication support in VNX Cinder driver.
|
Loading…
Reference in New Issue
Block a user