]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
VNX: Update replication for v2.1
authorpeter_wang <peter.wang13@emc.com>
Wed, 24 Feb 2016 02:57:28 +0000 (10:57 +0800)
committerpeter_wang <peter.wang13@emc.com>
Thu, 10 Mar 2016 10:23:02 +0000 (05:23 -0500)
Supporting replication v2.1 specification as well
as failback capability.

Change-Id: Idb2fae7f5caa240f6c347e60953d5dd1cdf3f8c8
Closes-Bug: #1547543

cinder/tests/unit/test_emc_vnx.py
cinder/volume/drivers/emc/emc_cli_fc.py
cinder/volume/drivers/emc/emc_cli_iscsi.py
cinder/volume/drivers/emc/emc_vnx_cli.py
releasenotes/notes/vnx-replication-v2-2afc4ac0c2ecfa60.yaml [deleted file]
releasenotes/notes/vnx-replication-v2.1-4d89935547183cc9.yaml [new file with mode: 0644]

index a3a00fc133659870938910ba0b0928d5b4095c77..537e593aee8c5d6b3ff07f7f39fb486d2b79a591 100644 (file)
@@ -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([], [])
+        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([], [])
-        data = self.driver.list_replication_targets(None, rep_volume)
-        self.assertDictMatch(expect_targets, data)
+        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
 
index b2a6025225c6037a240d3950decd4af16c7efca4..2d32e9dcd56d5c10c9ffda0cd0d509d71e045812 100644 (file)
@@ -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)
index eb33b940b42b06d3d4d1f7d8245be996a0f448fe..c495cf7bc6a0e08ddda953ac68c8dfc12c5a3b62 100644 (file)
@@ -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)
index 592f7ba76e3d2da7fcda263df5d2839bbbd1b181..aec668c298d7fd0b0b7e8fb69c5ea9ac875cfcaf 100644 (file)
@@ -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'])
-        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)
-
-        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'])
+        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:
-            new_host = self._get_volume_metadata(volume)['host']
-
-        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
+            rep_status = 'enabled'
 
-    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']
+        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
 
-        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 (file)
index faa7925..0000000
+++ /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 (file)
index 0000000..eebfd94
--- /dev/null
@@ -0,0 +1,3 @@
+---
+features:
+  - Adds v2.1 replication support in VNX Cinder driver.