From ab2a05aab3b5cb19c656808d137a3c69ffe6e741 Mon Sep 17 00:00:00 2001 From: peter_wang Date: Wed, 24 Feb 2016 10:57:28 +0800 Subject: [PATCH] VNX: Update replication for v2.1 Supporting replication v2.1 specification as well as failback capability. Change-Id: Idb2fae7f5caa240f6c347e60953d5dd1cdf3f8c8 Closes-Bug: #1547543 --- cinder/tests/unit/test_emc_vnx.py | 186 +++++++--------- cinder/volume/drivers/emc/emc_cli_fc.py | 20 +- cinder/volume/drivers/emc/emc_cli_iscsi.py | 20 +- cinder/volume/drivers/emc/emc_vnx_cli.py | 207 ++++++++++-------- .../vnx-replication-v2-2afc4ac0c2ecfa60.yaml | 3 - ...vnx-replication-v2.1-4d89935547183cc9.yaml | 3 + 6 files changed, 211 insertions(+), 228 deletions(-) delete mode 100644 releasenotes/notes/vnx-replication-v2-2afc4ac0c2ecfa60.yaml create mode 100644 releasenotes/notes/vnx-replication-v2.1-4d89935547183cc9.yaml diff --git a/cinder/tests/unit/test_emc_vnx.py b/cinder/tests/unit/test_emc_vnx.py index a3a00fc13..537e593ae 100644 --- a/cinder/tests/unit/test_emc_vnx.py +++ b/cinder/tests/unit/test_emc_vnx.py @@ -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': ' 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': ' 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': ' 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': ' 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 diff --git a/cinder/volume/drivers/emc/emc_cli_fc.py b/cinder/volume/drivers/emc/emc_cli_fc.py index b2a602522..2d32e9dcd 100644 --- a/cinder/volume/drivers/emc/emc_cli_fc.py +++ b/cinder/volume/drivers/emc/emc_cli_fc.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_cli_iscsi.py b/cinder/volume/drivers/emc/emc_cli_iscsi.py index eb33b940b..c495cf7bc 100644 --- a/cinder/volume/drivers/emc/emc_cli_iscsi.py +++ b/cinder/volume/drivers/emc/emc_cli_iscsi.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_vnx_cli.py b/cinder/volume/drivers/emc/emc_vnx_cli.py index 592f7ba76..aec668c29 100644 --- a/cinder/volume/drivers/emc/emc_vnx_cli.py +++ b/cinder/volume/drivers/emc/emc_vnx_cli.py @@ -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): diff --git a/releasenotes/notes/vnx-replication-v2-2afc4ac0c2ecfa60.yaml b/releasenotes/notes/vnx-replication-v2-2afc4ac0c2ecfa60.yaml deleted file mode 100644 index faa792596..000000000 --- a/releasenotes/notes/vnx-replication-v2-2afc4ac0c2ecfa60.yaml +++ /dev/null @@ -1,3 +0,0 @@ ---- -features: - - Replication v2 has been added in VNX Cinder driver diff --git a/releasenotes/notes/vnx-replication-v2.1-4d89935547183cc9.yaml b/releasenotes/notes/vnx-replication-v2.1-4d89935547183cc9.yaml new file mode 100644 index 000000000..eebfd94f4 --- /dev/null +++ b/releasenotes/notes/vnx-replication-v2.1-4d89935547183cc9.yaml @@ -0,0 +1,3 @@ +--- +features: + - Adds v2.1 replication support in VNX Cinder driver.