]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR: Update replication to v2.1
authorAlex O'Rourke <alex.orourke@hpe.com>
Wed, 3 Feb 2016 16:11:58 +0000 (16:11 +0000)
committerAlex O'Rourke <alex.orourke@hpe.com>
Tue, 1 Mar 2016 23:43:34 +0000 (15:43 -0800)
This patch updates replication to match the v2.1 spec. This makes it
so an entire backend can be replicated, and upon failover, all
replicated volumes will be failed over together.

Both sync and periodic replication modes are supported. Each
replication_device entry should have a replication_mode value set
to sync|periodic.

A volume type extra_spec value of replication:mode
should also be set. If replication:mode is periodic,
replication:sync_period should be set as well. Which replication_device
entry(s) are used is determined by the value of replication:mode set for
each volume type. NOTE: If no extra_specs are provided, periodic mode is
defaulted with a replication period of 900 seconds.

cinder.conf should have the replication config group:

[3parfcrep]
hpe3par_api_url = http://10.10.10.10:8008/api/v1
hpe3par_username = user
hpe3par_password = pass
hpe3par_debug = False
san_ip = 10.10.10.10
san_login = user
san_password = pass
volume_backend_name = 3parfcrep
hpe3par_cpg = REMOTE_COPY_CPG2
volume_driver = cinder.volume.drivers.hpe.hpe_3par_fc.HPE3PARFCDriver
replication_device = backend_id:eos16,
                     replication_mode:periodic,
                     cpg_map:REMOTE_COPY_CPG2:REMOTE_COPY_DEST2,
                     hpe3par_api_url:http://11.11.11.11:8008/api/v1,
                     hpe3par_username:user,
                     hpe3par_password:pass,
                     san_ip:11.11.11.11,
                     san_login:user,
                     san_password:pass

If we are working with iSCSI, the replication device needs to
contain entries for the hpe3par_iscsi_ips as such:

[3pariscsirep]
hpe3par_api_url = https://10.10.10.10:8080/api/v1
hpe3par_username = user
hpe3par_password = pass
hpe3par_debug = False
hpe3par_iscsi_ips = 10.50.50.50,10.50.50.51
san_ip = 10.10.10.10
san_login = user
san_password = pass
volume_backend_name = 3pariscsirep
hpe3par_cpg = REMOTE_COPY_CPG2
iscsi_ip_address = 10.50.50.50
volume_driver = cinder.volume.drivers.hpe.hpe_3par_iscsi.HPE3PARISCSIDriver
replication_device = backend_id:eos16,
                     replication_mode:periodic,
                     cpg_map:REMOTE_COPY_CPG2:REMOTE_COPY_DEST2,
                     hpe3par_api_url:https://11.11.11.11:8080/api/v1,
                     hpe3par_username:user,
                     hpe3par_password:pass,
                     san_ip:11.11.11.11,
                     san_login:user,
                     san_password:pass,
                     hpe3par_iscsi_ips:11.51.51.100

Closes-Bug: #1542078
Change-Id: Ia161b257278958c6a158d1239a77fc443c2985f0

cinder/tests/unit/test_hpe3par.py
cinder/volume/drivers/hpe/hpe_3par_common.py
cinder/volume/drivers/hpe/hpe_3par_fc.py
cinder/volume/drivers/hpe/hpe_3par_iscsi.py
releasenotes/notes/replication-v2.1-3par-b3f780a109f9195c.yaml [new file with mode: 0644]

index 3eabe366f0f879ecf439d57a4f45082a69767b45..747b17952ba9edc9e06f1fea6df2c05c502e8dcc 100644 (file)
@@ -112,10 +112,10 @@ class HPE3PARBaseDriver(object):
     CGSNAPSHOT_BASE_NAME = 'oss-6Rxe1druToSHJByeMeeh8g'
     CLIENT_ID = "12345"
     REPLICATION_CLIENT_ID = "54321"
+    REPLICATION_BACKEND_ID = 'target'
     # fake host on the 3par
     FAKE_HOST = 'fakehost'
     FAKE_CINDER_HOST = 'fakehost@foo#' + HPE3PAR_CPG
-    FAKE_FAILOVER_HOST = 'fakefailover@foo#destfakepool'
     USER_ID = '2689d9a913974c008b1d859013f23607'
     PROJECT_ID = 'fac88235b9d64685a3530f73e490348f'
     VOLUME_ID_SNAP = '761fc5e5-5191-4ec7-aeba-33e36de44156'
@@ -170,7 +170,7 @@ class HPE3PARBaseDriver(object):
                          'volume_type': 'replicated',
                          'volume_type_id': VOLUME_TYPE_ID_REPLICATED}
 
-    replication_targets = [{'target_device_id': 'target',
+    replication_targets = [{'backend_id': REPLICATION_BACKEND_ID,
                             'cpg_map': HPE3PAR_CPG_MAP,
                             'hpe3par_api_url': 'https://1.1.1.1/api/v1',
                             'hpe3par_username': HPE3PAR_USER_NAME,
@@ -180,23 +180,9 @@ class HPE3PARBaseDriver(object):
                             'san_password': HPE3PAR_USER_PASS,
                             'san_ssh_port': HPE3PAR_SAN_SSH_PORT,
                             'ssh_conn_timeout': HPE3PAR_SAN_SSH_CON_TIMEOUT,
-                            'san_private_key': HPE3PAR_SAN_SSH_PRIVATE,
-                            'managed_backend_name': FAKE_FAILOVER_HOST}]
-
-    list_rep_targets = [{'target_device_id': 'target'}]
-
-    replication_devs_unmgd = [{'target_device_id': 'target',
-                               'cpg_map': HPE3PAR_CPG_MAP,
-                               'hpe3par_api_url': 'https://1.1.1.1/api/v1',
-                               'hpe3par_username': HPE3PAR_USER_NAME,
-                               'hpe3par_password': HPE3PAR_USER_PASS,
-                               'san_ip': HPE3PAR_SAN_IP,
-                               'san_login': HPE3PAR_USER_NAME,
-                               'san_password': HPE3PAR_USER_PASS,
-                               'san_ssh_port': HPE3PAR_SAN_SSH_PORT,
-                               'ssh_conn_timeout': HPE3PAR_SAN_SSH_CON_TIMEOUT,
-                               'san_private_key': HPE3PAR_SAN_SSH_PRIVATE,
-                               'managed_backend_name': None}]
+                            'san_private_key': HPE3PAR_SAN_SSH_PRIVATE}]
+
+    list_rep_targets = [{'backend_id': 'target'}]
 
     volume_encrypted = {'name': VOLUME_NAME,
                         'id': VOLUME_ID,
@@ -976,8 +962,7 @@ class HPE3PARBaseDriver(object):
             self.assertIsNone(return_model)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_create_volume_replicated_managed_periodic(self,
-                                                       _mock_volume_types):
+    def test_create_volume_replicated_periodic(self, _mock_volume_types):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         conf = self.setup_configuration()
@@ -1020,7 +1005,7 @@ class HPE3PARBaseDriver(object):
                 "qos": {},
                 "type": "OpenStack"})
 
-            target_device_id = self.replication_targets[0]['target_device_id']
+            backend_id = self.replication_targets[0]['backend_id']
             expected = [
                 mock.call.createVolume(
                     self.VOLUME_3PAR_NAME,
@@ -1035,7 +1020,7 @@ class HPE3PARBaseDriver(object):
                 mock.call.createRemoteCopyGroup(
                     self.RCG_3PAR_NAME,
                     [{'userCPG': HPE3PAR_CPG_REMOTE,
-                      'targetName': target_device_id,
+                      'targetName': backend_id,
                       'mode': PERIODIC_MODE,
                       'snapCPG': HPE3PAR_CPG_REMOTE}],
                     {'localUserCPG': HPE3PAR_CPG,
@@ -1044,12 +1029,12 @@ class HPE3PARBaseDriver(object):
                     self.RCG_3PAR_NAME,
                     self.VOLUME_3PAR_NAME,
                     [{'secVolumeName': self.VOLUME_3PAR_NAME,
-                      'targetName': target_device_id}],
+                      'targetName': backend_id}],
                     optional={'volumeAutoCreation': True}),
                 mock.call.modifyRemoteCopyGroup(
                     self.RCG_3PAR_NAME,
                     {'targets': [{'syncPeriod': SYNC_PERIOD,
-                                  'targetName': target_device_id}]}),
+                                  'targetName': backend_id}]}),
                 mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
             mock_client.assert_has_calls(
                 self.get_id_login +
@@ -1062,97 +1047,17 @@ class HPE3PARBaseDriver(object):
                              return_model)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_create_volume_replicated_managed_sync(self,
-                                                   _mock_volume_types):
+    def test_delete_volume_replicated_failedover(self, _mock_volume_types):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'sync'
+        self.replication_targets[0]['replication_mode'] = 'periodic'
         conf.replication_device = self.replication_targets
         mock_client = self.setup_driver(config=conf)
         mock_client.getStorageSystemInfo.return_value = (
             {'id': self.CLIENT_ID})
-        mock_client.getRemoteCopyGroup.side_effect = (
-            hpeexceptions.HTTPNotFound)
-        mock_client.getCPG.return_value = {'domain': None}
-        mock_replicated_client = self.setup_driver(config=conf)
-        mock_replicated_client.getStorageSystemInfo.return_value = (
-            {'id': self.REPLICATION_CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'replicated',
-            'extra_specs': {
-                'replication_enabled': '<is> True',
-                'replication:mode': 'sync',
-                'volume_type': self.volume_type_replicated}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client, \
-            mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_replication_client') as mock_replication_client:
-            mock_create_client.return_value = mock_client
-            mock_replication_client.return_value = mock_replicated_client
-
-            return_model = self.driver.create_volume(self.volume_replicated)
-            comment = Comment({
-                "volume_type_name": "replicated",
-                "display_name": "Foo Volume",
-                "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
-                "volume_type_id": "be9181f1-4040-46f2-8298-e7532f2bf9db",
-                "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
-                "qos": {},
-                "type": "OpenStack"})
-
-            target_device_id = self.replication_targets[0]['target_device_id']
-            expected = [
-                mock.call.createVolume(
-                    self.VOLUME_3PAR_NAME,
-                    HPE3PAR_CPG,
-                    2048, {
-                        'comment': comment,
-                        'tpvv': True,
-                        'tdvv': False,
-                        'snapCPG': HPE3PAR_CPG_SNAP}),
-                mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.createRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    [{'userCPG': HPE3PAR_CPG_REMOTE,
-                      'targetName': target_device_id,
-                      'mode': SYNC_MODE,
-                      'snapCPG': HPE3PAR_CPG_REMOTE}],
-                    {'localUserCPG': HPE3PAR_CPG,
-                     'localSnapCPG': HPE3PAR_CPG_SNAP}),
-                mock.call.addVolumeToRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    self.VOLUME_3PAR_NAME,
-                    [{'secVolumeName': self.VOLUME_3PAR_NAME,
-                      'targetName': target_device_id}],
-                    optional={'volumeAutoCreation': True}),
-                mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'enabled',
-                              'provider_location': self.CLIENT_ID},
-                             return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_create_volume_replicated_unmanaged_periodic(self,
-                                                         _mock_volume_types):
-        # setup_mock_client drive with default configuration
-        # and return the mock HTTP 3PAR client
-        conf = self.setup_configuration()
-        self.replication_devs_unmgd[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_devs_unmgd
-        mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = {'id': self.CLIENT_ID}
-        mock_client.getRemoteCopyGroup.side_effect = hpeexceptions.HTTPNotFound
+        mock_client.getRemoteCopyGroup.return_value = (
+            {'targets': [{'targetName': 'tgt'}]})
         mock_client.getCPG.return_value = {'domain': None}
         mock_replicated_client = self.setup_driver(config=conf)
         mock_replicated_client.getStorageSystemInfo.return_value = (
@@ -1161,8 +1066,6 @@ class HPE3PARBaseDriver(object):
         _mock_volume_types.return_value = {
             'name': 'replicated',
             'extra_specs': {
-                'cpg': HPE3PAR_CPG,
-                'snap_cpg': HPE3PAR_CPG_SNAP,
                 'replication_enabled': '<is> True',
                 'replication:mode': 'periodic',
                 'replication:sync_period': '900',
@@ -1177,70 +1080,45 @@ class HPE3PARBaseDriver(object):
             mock_create_client.return_value = mock_client
             mock_replication_client.return_value = mock_replicated_client
 
-            return_model = self.driver.create_volume(self.volume_replicated)
-            comment = Comment({
-                "volume_type_name": "replicated",
-                "display_name": "Foo Volume",
-                "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
-                "volume_type_id": "be9181f1-4040-46f2-8298-e7532f2bf9db",
-                "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
-                "qos": {},
-                "type": "OpenStack"})
+            volume = self.volume_replicated.copy()
+            volume['replication_status'] = 'failed-over'
+            self.driver.delete_volume(volume)
 
-            target_device_id = self.replication_targets[0]['target_device_id']
+            rcg_name = self.RCG_3PAR_NAME + ".r" + self.CLIENT_ID
             expected = [
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.createVolume(
-                    self.VOLUME_3PAR_NAME,
-                    HPE3PAR_CPG,
-                    2048, {
-                        'comment': comment,
-                        'tpvv': True,
-                        'tdvv': False,
-                        'snapCPG': HPE3PAR_CPG_SNAP}),
-                mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.createRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    [{'userCPG': HPE3PAR_CPG_REMOTE,
-                      'targetName': target_device_id,
-                      'mode': PERIODIC_MODE,
-                      'snapCPG': HPE3PAR_CPG_REMOTE}],
-                    {'localUserCPG': HPE3PAR_CPG,
-                     'localSnapCPG': HPE3PAR_CPG_SNAP}),
-                mock.call.addVolumeToRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
+                mock.call.getRemoteCopyGroup(rcg_name),
+                mock.call.toggleRemoteCopyConfigMirror(
+                    'tgt',
+                    mirror_config=False),
+                mock.call.stopRemoteCopy(rcg_name),
+                mock.call.removeVolumeFromRemoteCopyGroup(
+                    rcg_name,
                     self.VOLUME_3PAR_NAME,
-                    [{'secVolumeName': self.VOLUME_3PAR_NAME,
-                      'targetName': target_device_id}],
-                    optional={'volumeAutoCreation': True}),
-                mock.call.modifyRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    {'targets': [{'syncPeriod': SYNC_PERIOD,
-                                  'targetName': target_device_id}]}),
-                mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
+                    removeFromTarget=True),
+                mock.call.removeRemoteCopyGroup(rcg_name),
+                mock.call.deleteVolume(self.VOLUME_3PAR_NAME),
+                mock.call.toggleRemoteCopyConfigMirror(
+                    'tgt',
+                    mirror_config=True)]
             mock_client.assert_has_calls(
                 self.get_id_login +
                 self.standard_logout +
                 self.standard_login +
                 expected +
                 self.standard_logout)
-            self.assertEqual({'replication_status': 'enabled',
-                              'provider_location': self.CLIENT_ID},
-                             return_model)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_create_volume_replicated_unmanaged_sync(self,
-                                                     _mock_volume_types):
+    def test_create_volume_replicated_sync(self, _mock_volume_types):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
         conf = self.setup_configuration()
-        self.replication_devs_unmgd[0]['replication_mode'] = 'sync'
-        conf.replication_device = self.replication_devs_unmgd
+        self.replication_targets[0]['replication_mode'] = 'sync'
+        conf.replication_device = self.replication_targets
         mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = {'id': self.CLIENT_ID}
-        mock_client.getRemoteCopyGroup.side_effect = hpeexceptions.HTTPNotFound
+        mock_client.getStorageSystemInfo.return_value = (
+            {'id': self.CLIENT_ID})
+        mock_client.getRemoteCopyGroup.side_effect = (
+            hpeexceptions.HTTPNotFound)
         mock_client.getCPG.return_value = {'domain': None}
         mock_replicated_client = self.setup_driver(config=conf)
         mock_replicated_client.getStorageSystemInfo.return_value = (
@@ -1249,8 +1127,6 @@ class HPE3PARBaseDriver(object):
         _mock_volume_types.return_value = {
             'name': 'replicated',
             'extra_specs': {
-                'cpg': HPE3PAR_CPG,
-                'snap_cpg': HPE3PAR_CPG_SNAP,
                 'replication_enabled': '<is> True',
                 'replication:mode': 'sync',
                 'volume_type': self.volume_type_replicated}}
@@ -1274,9 +1150,8 @@ class HPE3PARBaseDriver(object):
                 "qos": {},
                 "type": "OpenStack"})
 
-            target_device_id = self.replication_targets[0]['target_device_id']
+            backend_id = self.replication_targets[0]['backend_id']
             expected = [
-                mock.call.getCPG(HPE3PAR_CPG),
                 mock.call.createVolume(
                     self.VOLUME_3PAR_NAME,
                     HPE3PAR_CPG,
@@ -1287,11 +1162,10 @@ class HPE3PARBaseDriver(object):
                         'snapCPG': HPE3PAR_CPG_SNAP}),
                 mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
                 mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.getCPG(HPE3PAR_CPG),
                 mock.call.createRemoteCopyGroup(
                     self.RCG_3PAR_NAME,
                     [{'userCPG': HPE3PAR_CPG_REMOTE,
-                      'targetName': target_device_id,
+                      'targetName': backend_id,
                       'mode': SYNC_MODE,
                       'snapCPG': HPE3PAR_CPG_REMOTE}],
                     {'localUserCPG': HPE3PAR_CPG,
@@ -1300,7 +1174,7 @@ class HPE3PARBaseDriver(object):
                     self.RCG_3PAR_NAME,
                     self.VOLUME_3PAR_NAME,
                     [{'secVolumeName': self.VOLUME_3PAR_NAME,
-                      'targetName': target_device_id}],
+                      'targetName': backend_id}],
                     optional={'volumeAutoCreation': True}),
                 mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
             mock_client.assert_has_calls(
@@ -4336,85 +4210,9 @@ class HPE3PARBaseDriver(object):
                 self.standard_logout)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_enable_not_in_rcopy(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = (
-            {'id': self.CLIENT_ID})
-        mock_client.getRemoteCopyGroup.side_effect = (
-            hpeexceptions.HTTPNotFound)
-        mock_client.getCPG.return_value = {'domain': None}
-        mock_replicated_client = self.setup_driver(config=conf)
-        mock_replicated_client.getStorageSystemInfo.return_value = (
-            {'id': self.REPLICATION_CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'replicated',
-            'extra_specs': {
-                'cpg': HPE3PAR_CPG,
-                'snap_cpg': HPE3PAR_CPG_SNAP,
-                'replication_enabled': '<is> True',
-                'replication:mode': 'periodic',
-                'replication:sync_period': '900',
-                'volume_type': self.volume_type_replicated}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client, \
-            mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_replication_client') as mock_replication_client:
-            mock_create_client.return_value = mock_client
-            mock_replication_client.return_value = mock_replicated_client
-
-            return_model = self.driver.replication_enable(
-                context.get_admin_context(),
-                self.volume_replicated)
-
-            target_device_id = self.replication_targets[0]['target_device_id']
-            expected = [
-                mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.getCPG(HPE3PAR_CPG),
-                mock.call.createRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    [{'userCPG': HPE3PAR_CPG_REMOTE,
-                      'targetName': target_device_id,
-                      'mode': PERIODIC_MODE,
-                      'snapCPG': HPE3PAR_CPG_REMOTE}],
-                    {'localUserCPG': HPE3PAR_CPG,
-                     'localSnapCPG': HPE3PAR_CPG_SNAP}),
-                mock.call.addVolumeToRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    self.VOLUME_3PAR_NAME,
-                    [{'secVolumeName': self.VOLUME_3PAR_NAME,
-                      'targetName': target_device_id}],
-                    optional={'volumeAutoCreation': True}),
-                mock.call.modifyRemoteCopyGroup(
-                    self.RCG_3PAR_NAME,
-                    {'targets': [{'syncPeriod': SYNC_PERIOD,
-                                  'targetName': target_device_id}]}),
-                mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'enabled',
-                              'provider_location': self.CLIENT_ID},
-                             return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_enable_in_rcopy(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
+    def test_failover_host(self, _mock_volume_types):
+        # periodic vs. sync is not relevant when conducting a failover. We
+        # will just use periodic.
         conf = self.setup_configuration()
         self.replication_targets[0]['replication_mode'] = 'periodic'
         conf.replication_device = self.replication_targets
@@ -4441,135 +4239,36 @@ class HPE3PARBaseDriver(object):
                 '_create_replication_client') as mock_replication_client:
             mock_create_client.return_value = mock_client
             mock_replication_client.return_value = mock_replicated_client
+            valid_backend_id = (
+                self.replication_targets[0]['backend_id'])
+            invalid_backend_id = 'INVALID'
 
-            return_model = self.driver.replication_enable(
-                context.get_admin_context(),
-                self.volume_replicated)
-
-            expected = [
-                mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
-                mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'enabled',
-                              'provider_location': self.CLIENT_ID},
-                             return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_enable_non_replicated_type(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-
-        _mock_volume_types.return_value = {
-            'name': 'NOT_replicated',
-            'extra_specs': {
-                'volume_type': self.volume_type}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client:
-            mock_create_client.return_value = mock_client
-
+            volumes = [self.volume_replicated]
+            # Test invalid secondary target.
             self.assertRaises(
                 exception.VolumeBackendAPIException,
-                self.driver.replication_enable,
+                self.driver.failover_host,
                 context.get_admin_context(),
-                self.volume_replicated)
+                volumes,
+                invalid_backend_id)
 
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_disable(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = (
-            {'id': self.CLIENT_ID})
-        mock_replicated_client = self.setup_driver(config=conf)
-        mock_replicated_client.getStorageSystemInfo.return_value = (
-            {'id': self.REPLICATION_CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'replicated',
-            'extra_specs': {
-                'replication_enabled': '<is> True',
-                'replication:mode': 'periodic',
-                'replication:sync_period': '900',
-                'volume_type': self.volume_type_replicated}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client, \
-            mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_replication_client') as mock_replication_client:
-            mock_create_client.return_value = mock_client
-            mock_replication_client.return_value = mock_replicated_client
-
-            return_model = self.driver.replication_disable(
+            # Test no secondary target.
+            self.assertRaises(
+                exception.VolumeBackendAPIException,
+                self.driver.failover_host,
                 context.get_admin_context(),
-                self.volume_replicated)
-
-            expected = [
-                mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'disabled'},
-                             return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_disable_fail(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-        mock_client.stopRemoteCopy.side_effect = (
-            Exception("Error: Remote Copy could not be stopped."))
-        mock_client.getStorageSystemInfo.return_value = (
-            {'id': self.CLIENT_ID})
-        mock_replicated_client = self.setup_driver(config=conf)
-        mock_replicated_client.getStorageSystemInfo.return_value = (
-            {'id': self.REPLICATION_CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'replicated',
-            'extra_specs': {
-                'replication_enabled': '<is> True',
-                'replication:mode': 'periodic',
-                'replication:sync_period': '900',
-                'volume_type': self.volume_type_replicated}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client, \
-            mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_replication_client') as mock_replication_client:
-            mock_create_client.return_value = mock_client
-            mock_replication_client.return_value = mock_replicated_client
+                volumes,
+                None)
 
-            return_model = self.driver.replication_disable(
+            # Test a successful failover.
+            expected_model = (self.REPLICATION_BACKEND_ID,
+                              [{'updates': {'replication_status':
+                                            'failed-over'},
+                                'volume_id': self.VOLUME_ID}])
+            return_model = self.driver.failover_host(
                 context.get_admin_context(),
-                self.volume_replicated)
-
+                volumes,
+                valid_backend_id)
             expected = [
                 mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
             mock_client.assert_has_calls(
@@ -4578,50 +4277,19 @@ class HPE3PARBaseDriver(object):
                 self.standard_login +
                 expected +
                 self.standard_logout)
-            self.assertEqual({'replication_status': 'disable_failed'},
-                             return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_disable_non_replicated_type(self, _mock_volume_types):
-        # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
-        # We will use managed and periodic as the default.
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-
-        _mock_volume_types.return_value = {
-            'name': 'NOT_replicated',
-            'extra_specs': {
-                'volume_type': self.volume_type}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client:
-            mock_create_client.return_value = mock_client
-
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_disable,
-                context.get_admin_context(),
-                self.volume_replicated)
+            self.assertEqual(expected_model, return_model)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_list_replication_targets(self, _mock_volume_types):
+    def test_replication_failback_ready(self, _mock_volume_types):
         # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
+        # failing back a volume.
         # We will use managed and periodic as the default.
-        target_device_id = self.replication_targets[0]['target_device_id']
         conf = self.setup_configuration()
         self.replication_targets[0]['replication_mode'] = 'periodic'
         conf.replication_device = self.replication_targets
         mock_client = self.setup_driver(config=conf)
-        mock_client.getRemoteCopyGroup.return_value = (
-            {'targets': [{'targetName': target_device_id}]})
         mock_client.getStorageSystemInfo.return_value = (
             {'id': self.CLIENT_ID})
-        mock_client.getCPG.return_value = {'domain': None}
         mock_replicated_client = self.setup_driver(config=conf)
         mock_replicated_client.getStorageSystemInfo.return_value = (
             {'id': self.REPLICATION_CLIENT_ID})
@@ -4643,151 +4311,28 @@ class HPE3PARBaseDriver(object):
             mock_create_client.return_value = mock_client
             mock_replication_client.return_value = mock_replicated_client
 
-            return_model = self.driver.list_replication_targets(
+            # Test a successful fail-back.
+            volume = self.volume_replicated.copy()
+            volume['replication_status'] = 'failed-over'
+            return_model = self.driver.failover_host(
                 context.get_admin_context(),
-                self.volume_replicated)
-
-            expected = [
-                mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-
-            targets = self.list_rep_targets
-            self.assertEqual({'volume_id': self.volume_replicated['id'],
-                              'targets': targets},
-                             return_model)
+                [volume],
+                'default')
+            expected_model = (None,
+                              [{'updates': {'replication_status':
+                                            'available'},
+                                'volume_id': self.VOLUME_ID}])
+            self.assertEqual(expected_model, return_model)
 
     @mock.patch.object(volume_types, 'get_volume_type')
-    def test_list_replication_targets_non_replicated_type(self,
-                                                          _mock_volume_types):
+    def test_replication_failback_not_ready(self, _mock_volume_types):
         # Managed vs. unmanaged and periodic vs. sync are not relevant when
-        # enabling/disabling replication and listing replication targets.
+        # failing back a volume.
         # We will use managed and periodic as the default.
         conf = self.setup_configuration()
         self.replication_targets[0]['replication_mode'] = 'periodic'
         conf.replication_device = self.replication_targets
         mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = (
-            {'id': self.CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'NOT_replicated',
-            'extra_specs': {
-                'volume_type': self.volume_type}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client:
-            mock_create_client.return_value = mock_client
-
-            return_model = self.driver.list_replication_targets(
-                context.get_admin_context(),
-                self.volume_replicated)
-
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                self.standard_logout)
-
-            self.assertEqual([], return_model)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_failover_managed(self, _mock_volume_types):
-        # periodic vs. sync is not relevant when conducting a failover. We
-        # will just use periodic.
-        provider_location = self.CLIENT_ID + ":" + self.REPLICATION_CLIENT_ID
-        conf = self.setup_configuration()
-        self.replication_targets[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_targets
-        mock_client = self.setup_driver(config=conf)
-        mock_client.getStorageSystemInfo.return_value = (
-            {'id': self.CLIENT_ID})
-        mock_replicated_client = self.setup_driver(config=conf)
-        mock_replicated_client.getStorageSystemInfo.return_value = (
-            {'id': self.REPLICATION_CLIENT_ID})
-
-        _mock_volume_types.return_value = {
-            'name': 'replicated',
-            'extra_specs': {
-                'replication_enabled': '<is> True',
-                'replication:mode': 'periodic',
-                'replication:sync_period': '900',
-                'volume_type': self.volume_type_replicated}}
-
-        with mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_client') as mock_create_client, \
-            mock.patch.object(
-                hpecommon.HPE3PARCommon,
-                '_create_replication_client') as mock_replication_client:
-            mock_create_client.return_value = mock_client
-            mock_replication_client.return_value = mock_replicated_client
-            valid_target_device_id = (
-                self.replication_targets[0]['target_device_id'])
-            invalid_target_device_id = 'INVALID'
-
-            # test invalid secondary target
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
-                context.get_admin_context(),
-                self.volume_replicated,
-                invalid_target_device_id)
-
-            # test no secondary target
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
-                context.get_admin_context(),
-                self.volume_replicated,
-                None)
-
-            # test a successful failover
-            volume = self.volume_replicated
-            volume['provider_location'] = self.CLIENT_ID
-            return_model = self.driver.replication_failover(
-                context.get_admin_context(),
-                volume,
-                valid_target_device_id)
-            expected = [
-                mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'inactive',
-                              'provider_location': provider_location,
-                              'host': self.FAKE_FAILOVER_HOST},
-                             return_model)
-
-            # test a unsuccessful failover
-            mock_replicated_client.recoverRemoteCopyGroupFromDisaster.\
-                side_effect = (
-                    exception.VolumeBackendAPIException(
-                        "Error: Failover was unsuccessful."))
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
-                context.get_admin_context(),
-                self.volume_replicated,
-                valid_target_device_id)
-
-    @mock.patch.object(volume_types, 'get_volume_type')
-    def test_replication_failover_unmanaged(self, _mock_volume_types):
-        # periodic vs. sync is not relevant when conducting a failover. We
-        # will just use periodic.
-        provider_location = self.CLIENT_ID + ":" + self.REPLICATION_CLIENT_ID
-        conf = self.setup_configuration()
-        self.replication_devs_unmgd[0]['replication_mode'] = 'periodic'
-        conf.replication_device = self.replication_devs_unmgd
-        mock_client = self.setup_driver(config=conf)
         mock_client.getStorageSystemInfo.return_value = (
             {'id': self.CLIENT_ID})
         mock_replicated_client = self.setup_driver(config=conf)
@@ -4809,57 +4354,21 @@ class HPE3PARBaseDriver(object):
                 hpecommon.HPE3PARCommon,
                 '_create_replication_client') as mock_replication_client:
             mock_create_client.return_value = mock_client
+            mock_client.getRemoteCopyGroup.side_effect = (
+                exception.VolumeBackendAPIException(
+                    "Error: Remote Copy Group not Ready."))
             mock_replication_client.return_value = mock_replicated_client
-            valid_target_device_id = (
-                self.replication_targets[0]['target_device_id'])
-            invalid_target_device_id = 'INVALID'
-
-            # test invalid secondary target
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
-                context.get_admin_context(),
-                self.volume_replicated,
-                invalid_target_device_id)
-
-            # test no secondary target
-            self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
-                context.get_admin_context(),
-                self.volume_replicated,
-                None)
 
-            # test a successful failover
-            volume = self.volume_replicated
-            volume['provider_location'] = self.CLIENT_ID
-            return_model = self.driver.replication_failover(
-                context.get_admin_context(),
-                volume,
-                valid_target_device_id)
-            expected = [
-                mock.call.stopRemoteCopy(self.RCG_3PAR_NAME)]
-            mock_client.assert_has_calls(
-                self.get_id_login +
-                self.standard_logout +
-                self.standard_login +
-                expected +
-                self.standard_logout)
-            self.assertEqual({'replication_status': 'inactive',
-                              'provider_location': provider_location},
-                             return_model)
+            # Test an unsuccessful fail-back.
+            volume = self.volume_replicated.copy()
+            volume['replication_status'] = 'failed-over'
 
-            # test a unsuccessful failover
-            mock_replicated_client.recoverRemoteCopyGroupFromDisaster.\
-                side_effect = (
-                    exception.VolumeBackendAPIException(
-                        "Error: Failover was unsuccessful."))
             self.assertRaises(
-                exception.VolumeBackendAPIException,
-                self.driver.replication_failover,
+                exception.VolumeDriverException,
+                self.driver.failover_host,
                 context.get_admin_context(),
-                self.volume_replicated,
-                valid_target_device_id)
+                [volume],
+                'default')
 
 
 class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
index 205f7bb816493a98510fa31dd6da378a43dafea1..27dbd59e42d789daf8c8eebab2646f8359dd2f1d 100644 (file)
@@ -227,10 +227,11 @@ class HPE3PARCommon(object):
         3.0.12 - Remove client version checks for replication
         3.0.13 - Support creating a cg from a source cg
         3.0.14 - Comparison of WWNs now handles case difference. bug #1546453
+        3.0.15 - Update replication to version 2.1
 
     """
 
-    VERSION = "3.0.14"
+    VERSION = "3.0.15"
 
     stats = {}
 
@@ -253,6 +254,11 @@ class HPE3PARCommon(object):
     EXTRA_SPEC_REP_MODE = "replication:mode"
     EXTRA_SPEC_REP_SYNC_PERIOD = "replication:sync_period"
     RC_ACTION_CHANGE_TO_PRIMARY = 7
+    DEFAULT_REP_MODE = 'periodic'
+    DEFAULT_SYNC_PERIOD = 900
+    RC_GROUP_STARTED = 3
+    SYNC_STATUS_COMPLETED = 3
+    FAILBACK_VALUE = 'default'
 
     # License values for reported capabilities
     PRIORITY_OPT_LIC = "Priority Optimization"
@@ -279,13 +285,14 @@ class HPE3PARCommon(object):
     hpe3par_valid_keys = ['cpg', 'snap_cpg', 'provisioning', 'persona', 'vvs',
                           'flash_cache']
 
-    def __init__(self, config):
+    def __init__(self, config, active_backend_id=None):
         self.config = config
         self.client = None
         self.uuid = uuid.uuid4()
         self._client_conf = {}
         self._replication_targets = []
         self._replication_enabled = False
+        self._active_backend_id = active_backend_id
 
     def get_version(self):
         return self.VERSION
@@ -381,7 +388,7 @@ class HPE3PARCommon(object):
         if client is not None:
             client.logout()
 
-    def do_setup(self, context, volume=None, timeout=None, stats=None):
+    def do_setup(self, context, timeout=None, stats=None):
         if hpe3parclient is None:
             msg = _('You must install hpe3parclient before using 3PAR'
                     ' drivers. Run "pip install python-3parclient" to'
@@ -393,7 +400,7 @@ class HPE3PARCommon(object):
             # to communicate with the 3PAR array. It will contain either
             # the values for the primary array or secondary array in the
             # case of a fail-over.
-            self._get_3par_config(volume)
+            self._get_3par_config()
             self.client = self._create_client(timeout=timeout)
             wsapi_version = self.client.getWsApiVersion()
             self.API_VERSION = wsapi_version['build']
@@ -452,7 +459,7 @@ class HPE3PARCommon(object):
         if self.client:
             self.client_login()
             try:
-                cpg_names = self.config.hpe3par_cpg
+                cpg_names = self._client_conf['hpe3par_cpg']
                 for cpg_name in cpg_names:
                     self.validate_cpg(cpg_name)
 
@@ -1218,7 +1225,7 @@ class HPE3PARCommon(object):
                     valid_licenses, self.REMOTE_COPY_LIC,
                     "Replication")
 
-        for cpg_name in self.config.hpe3par_cpg:
+        for cpg_name in self._client_conf['hpe3par_cpg']:
             try:
                 cpg = self.client.getCPG(cpg_name)
                 if (self.API_VERSION >= SRSTATLD_API_VERSION):
@@ -1315,6 +1322,8 @@ class HPE3PARCommon(object):
                       'vendor_name': 'Hewlett Packard Enterprise',
                       'volume_backend_name': None,
                       'array_id': info['id'],
+                      'replication_enabled': self._replication_enabled,
+                      'replication_targets': self._get_replication_targets(),
                       'pools': pools}
 
     def _check_license_enabled(self, valid_licenses,
@@ -1678,7 +1687,7 @@ class HPE3PARCommon(object):
 
         # Default to pool extracted from host.
         # If that doesn't work use the 1st CPG in the config as the default.
-        default_cpg = pool or self.config.hpe3par_cpg[0]
+        default_cpg = pool or self._client_conf['hpe3par_cpg'][0]
 
         cpg = self._get_key_value(hpe3par_keys, 'cpg', default_cpg)
         if cpg is not default_cpg:
@@ -2158,8 +2167,8 @@ class HPE3PARCommon(object):
             flash_cache = self.get_flash_cache_policy(hpe3par_keys)
 
             if qos or vvs_name or flash_cache is not None:
-                cpg_names = self._get_key_value(hpe3par_keys, 'cpg',
-                                                self.config.hpe3par_cpg)
+                cpg_names = self._get_key_value(
+                    hpe3par_keys, 'cpg', self._client_conf['hpe3par_cpg'])
                 try:
                     self._add_volume_to_volume_set(volume, volume_name,
                                                    cpg_names[0], vvs_name,
@@ -2850,172 +2859,142 @@ class HPE3PARCommon(object):
         return existing_vluns
 
     # v2 replication methods
-    def replication_enable(self, context, volume):
-        """Enable replication on a replication capable volume."""
-        if not self._volume_of_replicated_type(volume):
-            msg = _("Unable to enable volume replication because volume is "
-                    "not of replicated type.")
-            LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
-
-        model_update = {"provider_location": self.client.id}
-        # If replication is not enabled and the volume is of replicated type,
-        # we treat this as an error.
-        if not self._replication_enabled:
-            msg = _LE("Enabling replication failed because replication is "
-                      "not properly configured.")
-            LOG.error(msg)
-            model_update['replication_status'] = "error"
-        else:
-            if self._do_volume_replication_setup(volume):
-                model_update['replication_status'] = "enabled"
-            else:
-                model_update['replication_status'] = "error"
-
-        return model_update
-
-    def replication_disable(self, context, volume):
-        """Disable replication on the specified volume."""
-        if not self._volume_of_replicated_type(volume):
-            msg = _("Unable to disable volume replication because volume is "
-                    "not of replicated type.")
-            LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
-
-        model_update = {}
-        # If replication is not enabled and the volume is of replicated type,
-        # we treat this as an error.
-        if self._replication_enabled:
-            model_update['replication_status'] = 'disabled'
-            rcg_name = self._get_3par_rcg_name(volume['id'])
-            vol_name = self._get_3par_vol_name(volume['id'])
-
-            try:
-                self.client.stopRemoteCopy(rcg_name)
-            except Exception as ex:
-                msg = (_LE("There was a problem disabling replication on "
-                           "volume '%(name)s': %(error)s") %
-                       {'name': vol_name,
-                        'error': six.text_type(ex)})
-                LOG.error(msg)
-                model_update['replication_status'] = 'disable_failed'
-        else:
-            msg = _LE("Disabling replication failed because replication is "
-                      "not properly configured.")
-            LOG.error(msg)
-            model_update['replication_status'] = 'error'
-
-        return model_update
-
-    def replication_failover(self, context, volume, secondary):
+    def failover_host(self, context, volumes, secondary_backend_id):
         """Force failover to a secondary replication target."""
-        if not self._volume_of_replicated_type(volume):
-            msg = _("Unable to failover because volume is not of "
-                    "replicated type.")
-            LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
-
-        # If replication is not enabled and the volume is of replicated type,
-        # we treat this as an error.
+        # Ensure replication is enabled before we try and failover.
         if not self._replication_enabled:
             msg = _LE("Issuing a fail-over failed because replication is "
                       "not properly configured.")
             LOG.error(msg)
-            model_update = {"replication_status": "error"}
-            return model_update
-
-        failover_target = None
-        for target in self._replication_targets:
-            if target['target_device_id'] == secondary:
-                failover_target = target
-                break
-
-        if not failover_target:
-            msg = _("A valid secondary target MUST be specified in order "
-                    "to failover.")
-            LOG.error(msg)
             raise exception.VolumeBackendAPIException(data=msg)
 
-        if self.client is not None and failover_target['id'] == self.client.id:
-            msg = _("The failover array cannot be the primary array.")
-            LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
-
-        try:
-            # Try and stop remote-copy on main array.
-            rcg_name = self._get_3par_rcg_name(volume['id'])
-            self.client.stopRemoteCopy(rcg_name)
-        except Exception:
-            pass
+        # Check to see if the user requested to failback.
+        if secondary_backend_id == self.FAILBACK_VALUE:
+            volume_update_list = self._replication_failback(volumes)
+            target_id = None
+        else:
+            # Find the failover target.
+            failover_target = None
+            for target in self._replication_targets:
+                if target['backend_id'] == secondary_backend_id:
+                    failover_target = target
+                    break
+            if not failover_target:
+                msg = _("A valid secondary target MUST be specified in order "
+                        "to failover.")
+                LOG.error(msg)
+                raise exception.VolumeBackendAPIException(data=msg)
 
-        try:
-            # Failover to secondary array.
-            remote_rcg_name = self._get_3par_remote_rcg_name(
-                volume['id'], volume['provider_location'])
-            cl = self._create_replication_client(failover_target)
-            cl.recoverRemoteCopyGroupFromDisaster(
-                remote_rcg_name, self.RC_ACTION_CHANGE_TO_PRIMARY)
-            new_location = volume['provider_location'] + ":" + (
-                failover_target['id'])
-
-            model_update = {"provider_location": new_location,
-                            "replication_status": "inactive"}
-            if failover_target['managed_backend_name']:
-                # We want to update the volumes host if our target is managed.
-                model_update['host'] = failover_target['managed_backend_name']
+            target_id = failover_target['backend_id']
+            # For each volume, if it is replicated, we want to fail it over.
+            volume_update_list = []
+            for volume in volumes:
+                if self._volume_of_replicated_type(volume):
+                    try:
+                        # Try and stop remote-copy on main array. We eat the
+                        # exception here because when an array goes down, the
+                        # groups will stop automatically.
+                        rcg_name = self._get_3par_rcg_name(volume['id'])
+                        self.client.stopRemoteCopy(rcg_name)
+                    except Exception:
+                        pass
 
-        except Exception as ex:
-            msg = _("There was a problem with the failover (%s) and it was "
-                    "unsuccessful.") % six.text_type(ex)
+                    try:
+                        # Failover to secondary array.
+                        remote_rcg_name = self._get_3par_remote_rcg_name(
+                            volume['id'], volume['provider_location'])
+                        cl = self._create_replication_client(failover_target)
+                        cl.recoverRemoteCopyGroupFromDisaster(
+                            remote_rcg_name, self.RC_ACTION_CHANGE_TO_PRIMARY)
+                        volume_update_list.append(
+                            {'volume_id': volume['id'],
+                             'updates': {'replication_status': 'failed-over'}})
+                    except Exception as ex:
+                        msg = (_LE("There was a problem with the failover "
+                                   "(%(error)s) and it was unsuccessful. "
+                                   "Volume '%(volume)s will not be available "
+                                   "on the failed over target."),
+                               {'error': six.text_type(ex),
+                                'volume': volume['id']})
+                        LOG.error(msg)
+                        volume_update_list.append(
+                            {'volume_id': volume['id'],
+                             'updates': {'replication_status': 'error'}})
+                    finally:
+                        self._destroy_replication_client(cl)
+                else:
+                    # If the volume is not of replicated type, we need to
+                    # force the status into error state so a user knows they
+                    # do not have access to the volume.
+                    volume_update_list.append(
+                        {'volume_id': volume['id'],
+                         'updates': {'status': 'error'}})
+
+        return target_id, volume_update_list
+
+    def _replication_failback(self, volumes):
+        # Make sure the proper steps on the backend have been completed before
+        # we allow a fail-over.
+        if not self._is_host_ready_for_failback(volumes):
+            msg = _("The host is not ready to be failed back. Please "
+                    "resynchronize the volumes and resume replication on the "
+                    "3PAR backends.")
             LOG.error(msg)
-            raise exception.VolumeBackendAPIException(data=msg)
-        finally:
-            self._destroy_replication_client(cl)
+            raise exception.VolumeDriverException(data=msg)
 
-        return model_update
+        # Update the volumes status to available.
+        volume_update_list = []
+        for volume in volumes:
+            if self._volume_of_replicated_type(volume):
+                volume_update_list.append(
+                    {'volume_id': volume['id'],
+                     'updates': {'replication_status': 'available'}})
+            else:
+                # Upon failing back, we can move the non-replicated volumes
+                # back into available state.
+                volume_update_list.append(
+                    {'volume_id': volume['id'],
+                     'updates': {'status': 'available'}})
 
-    def list_replication_targets(self, context, volume):
-        """Provides a means to obtain replication targets for a volume.
+        return volume_update_list
 
-        This will query all enabled targets on a 3PAR backend and cross
-        reference them with all entries in cinder.conf. It will return
-        only those that appear on both, aka enabled replication targets.
-        """
-        if not self._volume_of_replicated_type(volume):
-            return []
+    def _is_host_ready_for_failback(self, volumes):
+        """Checks to make sure the volume has been synchronized
 
-        allowed_names = []
-        # If the primary target is offline we can not ask it what targets are
-        # available. Our only option is to list all cinder.conf entries.
+        This ensures that all the remote copy targets have been restored
+        to their natural direction, and all of the volumes have been
+        fully synchronized.
+        """
         try:
-            rcg_name = self._get_3par_rcg_name(volume['id'])
-            rcg = self.client.getRemoteCopyGroup(rcg_name)
-            rcg_targets = rcg['targets']
-            for target in rcg_targets:
-                allowed_names.append(target['targetName'])
+            for volume in volumes:
+                if self._volume_of_replicated_type(volume):
+                    location = volume.get('provider_location')
+                    remote_rcg_name = self._get_3par_remote_rcg_name(
+                        volume['id'],
+                        location)
+                    rcg = self.client.getRemoteCopyGroup(remote_rcg_name)
+
+                    # Make sure all targets are in their natural direction.
+                    targets = rcg['targets']
+                    for target in targets:
+                        if target['roleReversed'] or (
+                           target['state'] != self.RC_GROUP_STARTED):
+                            return False
+
+                    # Make sure all volumes are fully synced.
+                    volumes = rcg['volumes']
+                    for volume in volumes:
+                        remote_volumes = volume['remoteVolumes']
+                        for remote_volume in remote_volumes:
+                            if remote_volume['syncStatus'] != (
+                               self.SYNC_STATUS_COMPLETED):
+                                return False
         except Exception:
-            LOG.warning(_LW("The primary array is currently unreachable. All "
-                            "targets returned from list_replication_targets "
-                            "are pulled directly from cinder.conf and are not "
-                            "guarenteed to be available because they could "
-                            "not be verified with the primary array."))
-
-        replication_targets = []
-        volume_type = self._get_volume_type(volume["volume_type_id"])
-        extra_specs = volume_type.get("extra_specs")
-        replication_mode = extra_specs.get(self.EXTRA_SPEC_REP_MODE)
-        replication_mode_num = self._get_remote_copy_mode_num(
-            replication_mode)
-
-        for target in self._replication_targets:
-            if not allowed_names and replication_mode_num == (
-                target['replication_mode']) or (
-                    target['target_device_id'] in allowed_names):
-                list_vals = {'target_device_id': target['target_device_id']}
-                replication_targets.append(list_vals)
+            # If there was a problem, we will return false so we can
+            # log an error in the parent function.
+            return False
 
-        return {'volume_id': volume['id'],
-                'targets': replication_targets}
+        return True
 
     def _do_replication_setup(self):
         replication_targets = []
@@ -3042,7 +3021,7 @@ class HPE3PARCommon(object):
                 # Format hpe3par_iscsi_chap_enabled as a bool
                 remote_array['hpe3par_iscsi_chap_enabled'] = (
                     dev.get('hpe3par_iscsi_chap_enabled') == 'True')
-                array_name = remote_array['target_device_id']
+                array_name = remote_array['backend_id']
 
                 # Make sure we can log into the array, that it has been
                 # correctly configured, and its API version meets the
@@ -3066,7 +3045,7 @@ class HPE3PARCommon(object):
                         LOG.warning(msg)
                     elif not self._is_valid_replication_array(remote_array):
                         msg = (_LW("'%s' is not a valid replication array. "
-                                   "In order to be valid, target_device_id, "
+                                   "In order to be valid, backend_id, "
                                    "replication_mode, "
                                    "hpe3par_api_url, hpe3par_username, "
                                    "hpe3par_password, cpg_map, san_ip, "
@@ -3091,7 +3070,7 @@ class HPE3PARCommon(object):
     def _is_valid_replication_array(self, target):
         required_flags = ['hpe3par_api_url', 'hpe3par_username',
                           'hpe3par_password', 'san_ip', 'san_login',
-                          'san_password', 'target_device_id',
+                          'san_password', 'backend_id',
                           'replication_mode', 'cpg_map']
         try:
             self.check_replication_flags(target, required_flags)
@@ -3156,19 +3135,14 @@ class HPE3PARCommon(object):
             ret_mode = self.PERIODIC
         return ret_mode
 
-    def _get_3par_config(self, volume):
+    def _get_3par_config(self):
         self._do_replication_setup()
         conf = None
-        if self._replication_enabled and volume:
-            provider_location = volume.get('provider_location')
-            if provider_location:
-                if volume.get('replication_status') == 'failed-over':
-                    _, provider_location = provider_location.split(':')
-
-                for target in self._replication_targets:
-                    if target['id'] == provider_location:
-                        conf = target
-                        break
+        if self._replication_enabled:
+            for target in self._replication_targets:
+                if target['backend_id'] == self._active_backend_id:
+                    conf = target
+                    break
         self._build_3par_config(conf)
 
     def _build_3par_config(self, conf=None):
@@ -3182,6 +3156,8 @@ class HPE3PARCommon(object):
         with unmanaged replication.
         """
         if conf:
+            self._client_conf['hpe3par_cpg'] = self._generate_hpe3par_cpgs(
+                conf.get('cpg_map'))
             self._client_conf['hpe3par_username'] = (
                 conf.get('hpe3par_username'))
             self._client_conf['hpe3par_password'] = (
@@ -3202,6 +3178,8 @@ class HPE3PARCommon(object):
                 conf.get('iscsi_ip_address'))
             self._client_conf['iscsi_port'] = conf.get('iscsi_port')
         else:
+            self._client_conf['hpe3par_cpg'] = (
+                self.config.hpe3par_cpg)
             self._client_conf['hpe3par_username'] = (
                 self.config.hpe3par_username)
             self._client_conf['hpe3par_password'] = (
@@ -3234,6 +3212,22 @@ class HPE3PARCommon(object):
 
         return ret_target_cpg
 
+    def _generate_hpe3par_cpgs(self, cpg_map):
+        hpe3par_cpgs = []
+        cpg_pairs = cpg_map.split(' ')
+        for cpg_pair in cpg_pairs:
+            cpgs = cpg_pair.split(':')
+            hpe3par_cpgs.append(cpgs[1])
+
+        return hpe3par_cpgs
+
+    def _get_replication_targets(self):
+        replication_targets = []
+        for target in self._replication_targets:
+            replication_targets.append(target['backend_id'])
+
+        return replication_targets
+
     def _do_volume_replication_setup(self, volume):
         """This function will do or ensure the following:
 
@@ -3262,11 +3256,12 @@ class HPE3PARCommon(object):
             # are set correctly.
             volume_type = self._get_volume_type(volume["volume_type_id"])
             extra_specs = volume_type.get("extra_specs")
-            replication_mode = extra_specs.get(self.EXTRA_SPEC_REP_MODE)
+            replication_mode = extra_specs.get(
+                self.EXTRA_SPEC_REP_MODE, self.DEFAULT_REP_MODE)
             replication_mode_num = self._get_remote_copy_mode_num(
                 replication_mode)
             replication_sync_period = extra_specs.get(
-                self.EXTRA_SPEC_REP_SYNC_PERIOD)
+                self.EXTRA_SPEC_REP_SYNC_PERIOD, self.DEFAULT_SYNC_PERIOD)
             if replication_sync_period:
                 replication_sync_period = int(replication_sync_period)
             if not self._is_replication_mode_correct(replication_mode,
@@ -3290,12 +3285,12 @@ class HPE3PARCommon(object):
                 if target['replication_mode'] == replication_mode_num:
                     cpg = self._get_cpg_from_cpg_map(target['cpg_map'],
                                                      local_cpg)
-                    rcg_target = {'targetName': target['target_device_id'],
+                    rcg_target = {'targetName': target['backend_id'],
                                   'mode': replication_mode_num,
                                   'snapCPG': cpg,
                                   'userCPG': cpg}
                     rcg_targets.append(rcg_target)
-                    sync_target = {'targetName': target['target_device_id'],
+                    sync_target = {'targetName': target['backend_id'],
                                    'syncPeriod': replication_sync_period}
                     sync_targets.append(sync_target)
 
@@ -3320,7 +3315,7 @@ class HPE3PARCommon(object):
             for target in self._replication_targets:
                 # Only add targets that match the volumes replication mode.
                 if target['replication_mode'] == replication_mode_num:
-                    rcg_target = {'targetName': target['target_device_id'],
+                    rcg_target = {'targetName': target['backend_id'],
                                   'secVolumeName': vol_name}
                     rcg_targets.append(rcg_target)
             optional = {'volumeAutoCreation': True}
@@ -3409,8 +3404,8 @@ class HPE3PARCommon(object):
             pass
 
     def _delete_replicated_failed_over_volume(self, volume):
-        old_location, new_location = volume['provider_location'].split(':')
-        rcg_name = self._get_3par_remote_rcg_name(volume['id'], old_location)
+        location = volume.get('provider_location')
+        rcg_name = self._get_3par_remote_rcg_name(volume['id'], location)
         targets = self.client.getRemoteCopyGroup(rcg_name)['targets']
         # When failed over, we want to temporarily disable config mirroring
         # in order to be allowed to delete the volume and remote copy group
index 8d7017f6aaa71adef4e55802c0e18abe05401d49..a4e52d1a1e83c9e236e94e403b0d9efc3dacbcfc 100644 (file)
@@ -96,27 +96,29 @@ class HPE3PARFCDriver(driver.TransferVD,
         3.0.3 - Adds v2 unmanaged replication support
         3.0.4 - Adding manage/unmanage snapshot support
         3.0.5 - Optimize array ID retrieval
+        3.0.6 - Update replication to version 2.1
 
     """
 
-    VERSION = "3.0.5"
+    VERSION = "3.0.6"
 
     def __init__(self, *args, **kwargs):
         super(HPE3PARFCDriver, self).__init__(*args, **kwargs)
+        self._active_backend_id = kwargs.get('active_backend_id', None)
         self.configuration.append_config_values(hpecommon.hpe3par_opts)
         self.configuration.append_config_values(san.san_opts)
         self.lookup_service = fczm_utils.create_lookup_service()
 
     def _init_common(self):
-        return hpecommon.HPE3PARCommon(self.configuration)
+        return hpecommon.HPE3PARCommon(self.configuration,
+                                       self._active_backend_id)
 
-    def _login(self, volume=None, timeout=None):
+    def _login(self, timeout=None):
         common = self._init_common()
         # If replication is enabled and we cannot login, we do not want to
         # raise an exception so a failover can still be executed.
         try:
-            common.do_setup(None, volume=volume, timeout=timeout,
-                            stats=self._stats)
+            common.do_setup(None, timeout=timeout, stats=self._stats)
             common.client_login()
         except Exception:
             if common._replication_enabled:
@@ -170,21 +172,21 @@ class HPE3PARFCDriver(driver.TransferVD,
         pass
 
     def create_volume(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_volume(volume)
         finally:
             self._logout(common)
 
     def create_cloned_volume(self, volume, src_vref):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_cloned_volume(volume, src_vref)
         finally:
             self._logout(common)
 
     def delete_volume(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.delete_volume(volume)
         finally:
@@ -195,21 +197,21 @@ class HPE3PARFCDriver(driver.TransferVD,
 
         TODO: support using the size from the user.
         """
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_volume_from_snapshot(volume, snapshot)
         finally:
             self._logout(common)
 
     def create_snapshot(self, snapshot):
-        common = self._login(snapshot['volume'])
+        common = self._login()
         try:
             common.create_snapshot(snapshot)
         finally:
             self._logout(common)
 
     def delete_snapshot(self, snapshot):
-        common = self._login(snapshot['volume'])
+        common = self._login()
         try:
             common.delete_snapshot(snapshot)
         finally:
@@ -255,7 +257,7 @@ class HPE3PARFCDriver(driver.TransferVD,
           * Create a VLUN for that HOST with the volume we want to export.
 
         """
-        common = self._login(volume)
+        common = self._login()
         try:
             # we have to make sure we have a host
             host = self._create_host(common, volume, connector)
@@ -298,7 +300,7 @@ class HPE3PARFCDriver(driver.TransferVD,
     @fczm_utils.RemoveFCZone
     def terminate_connection(self, volume, connector, **kwargs):
         """Driver entry point to unattach a volume from an instance."""
-        common = self._login(volume)
+        common = self._login()
         try:
             hostname = common._safe_hostname(connector['host'])
             common.terminate_connection(volume, hostname,
@@ -454,7 +456,7 @@ class HPE3PARFCDriver(driver.TransferVD,
         pass
 
     def extend_volume(self, volume, new_size):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.extend_volume(volume, new_size)
         finally:
@@ -509,7 +511,7 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing(self, volume, existing_ref):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.manage_existing(volume, existing_ref)
         finally:
@@ -523,7 +525,7 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing_get_size(self, volume, existing_ref):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.manage_existing_get_size(volume, existing_ref)
         finally:
@@ -538,7 +540,7 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def unmanage(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.unmanage(volume)
         finally:
@@ -553,14 +555,14 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def attach_volume(self, context, volume, instance_uuid, host_name,
                       mountpoint):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.attach_volume(volume, instance_uuid)
         finally:
             self._logout(common)
 
     def detach_volume(self, context, volume, attachment=None):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.detach_volume(volume, attachment)
         finally:
@@ -568,7 +570,7 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def retype(self, context, volume, new_type, diff, host):
         """Convert the volume to be of the new type."""
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.retype(volume, new_type, diff, host)
         finally:
@@ -582,7 +584,7 @@ class HPE3PARFCDriver(driver.TransferVD,
                           "to a host with storage_protocol=%s.", protocol)
                 return False, None
 
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.migrate_volume(volume, host)
         finally:
@@ -591,7 +593,7 @@ class HPE3PARFCDriver(driver.TransferVD,
     def update_migrated_volume(self, context, volume, new_volume,
                                original_volume_status):
         """Update the name of the migrated volume to it's new ID."""
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.update_migrated_volume(context, volume, new_volume,
                                                  original_volume_status)
@@ -599,7 +601,7 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def get_pool(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.get_cpg(volume)
         except hpeexceptions.HTTPNotFound:
@@ -609,34 +611,14 @@ class HPE3PARFCDriver(driver.TransferVD,
         finally:
             self._logout(common)
 
-    def replication_enable(self, context, volume):
-        """Enable replication on a replication capable volume."""
-        common = self._login(volume)
-        try:
-            return common.replication_enable(context, volume)
-        finally:
-            self._logout(common)
-
-    def replication_disable(self, context, volume):
-        """Disable replication on the specified volume."""
-        common = self._login(volume)
-        try:
-            return common.replication_disable(context, volume)
-        finally:
-            self._logout(common)
-
-    def replication_failover(self, context, volume, secondary):
+    def failover_host(self, context, volumes, secondary_backend_id):
         """Force failover to a secondary replication target."""
-        common = self._login(volume, timeout=30)
-        try:
-            return common.replication_failover(context, volume, secondary)
-        finally:
-            self._logout(common)
-
-    def list_replication_targets(self, context, volume):
-        """Provides a means to obtain replication targets for a volume."""
-        common = self._login(volume, timeout=30)
+        common = self._login(timeout=30)
         try:
-            return common.list_replication_targets(context, volume)
+            # Update the active_backend_id in the driver and return it.
+            active_backend_id, volume_updates = common.failover_host(
+                context, volumes, secondary_backend_id)
+            self._active_backend_id = active_backend_id
+            return active_backend_id, volume_updates
         finally:
             self._logout(common)
index 562829cadea2983c6b92cda050b46e34f1f41c66..4873ca62da9148aeec7d835133620bd9a5d3a122 100644 (file)
@@ -108,26 +108,28 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         3.0.5 - Adds v2 unmanaged replication support
         3.0.6 - Adding manage/unmanage snapshot support
         3.0.7 - Optimize array ID retrieval
+        3.0.8 - Update replication to version 2.1
 
     """
 
-    VERSION = "3.0.7"
+    VERSION = "3.0.8"
 
     def __init__(self, *args, **kwargs):
         super(HPE3PARISCSIDriver, self).__init__(*args, **kwargs)
+        self._active_backend_id = kwargs.get('active_backend_id', None)
         self.configuration.append_config_values(hpecommon.hpe3par_opts)
         self.configuration.append_config_values(san.san_opts)
 
     def _init_common(self):
-        return hpecommon.HPE3PARCommon(self.configuration)
+        return hpecommon.HPE3PARCommon(self.configuration,
+                                       self._active_backend_id)
 
-    def _login(self, volume=None, timeout=None):
+    def _login(self, timeout=None):
         common = self._init_common()
         # If replication is enabled and we cannot login, we do not want to
         # raise an exception so a failover can still be executed.
         try:
-            common.do_setup(None, volume=volume, timeout=timeout,
-                            stats=self._stats)
+            common.do_setup(None, timeout=timeout, stats=self._stats)
             common.client_login()
         except Exception:
             if common._replication_enabled:
@@ -248,7 +250,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         pass
 
     def create_volume(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_volume(volume)
         finally:
@@ -256,14 +258,14 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def create_cloned_volume(self, volume, src_vref):
         """Clone an existing volume."""
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_cloned_volume(volume, src_vref)
         finally:
             self._logout(common)
 
     def delete_volume(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.delete_volume(volume)
         finally:
@@ -274,21 +276,21 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
         TODO: support using the size from the user.
         """
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.create_volume_from_snapshot(volume, snapshot)
         finally:
             self._logout(common)
 
     def create_snapshot(self, snapshot):
-        common = self._login(snapshot['volume'])
+        common = self._login()
         try:
             common.create_snapshot(snapshot)
         finally:
             self._logout(common)
 
     def delete_snapshot(self, snapshot):
-        common = self._login(snapshot['volume'])
+        common = self._login()
         try:
             common.delete_snapshot(snapshot)
         finally:
@@ -320,7 +322,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
           * Create a host on the 3par
           * create vlun on the 3par
         """
-        common = self._login(volume)
+        common = self._login()
         try:
             # If the volume has been failed over, we need to reinitialize
             # iSCSI ports so they represent the new array.
@@ -444,7 +446,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def terminate_connection(self, volume, connector, **kwargs):
         """Driver entry point to unattach a volume from an instance."""
-        common = self._login(volume)
+        common = self._login()
         try:
             hostname = common._safe_hostname(connector['host'])
             common.terminate_connection(
@@ -651,7 +653,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         return model_update
 
     def create_export(self, context, volume, connector):
-        common = self._login(volume)
+        common = self._login()
         try:
             return self._do_export(common, volume)
         finally:
@@ -662,7 +664,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
         Also retrieves CHAP credentials, if present on the volume
         """
-        common = self._login(volume)
+        common = self._login()
         try:
             vol_name = common._get_3par_vol_name(volume['id'])
             common.client.getVolume(vol_name)
@@ -765,7 +767,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         return current_least_used_nsp
 
     def extend_volume(self, volume, new_size):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.extend_volume(volume, new_size)
         finally:
@@ -820,7 +822,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing(self, volume, existing_ref):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.manage_existing(volume, existing_ref)
         finally:
@@ -834,7 +836,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing_get_size(self, volume, existing_ref):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.manage_existing_get_size(volume, existing_ref)
         finally:
@@ -849,7 +851,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def unmanage(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.unmanage(volume)
         finally:
@@ -864,14 +866,14 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def attach_volume(self, context, volume, instance_uuid, host_name,
                       mountpoint):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.attach_volume(volume, instance_uuid)
         finally:
             self._logout(common)
 
     def detach_volume(self, context, volume, attachment=None):
-        common = self._login(volume)
+        common = self._login()
         try:
             common.detach_volume(volume, attachment)
         finally:
@@ -879,7 +881,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def retype(self, context, volume, new_type, diff, host):
         """Convert the volume to be of the new type."""
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.retype(volume, new_type, diff, host)
         finally:
@@ -893,7 +895,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                           "to a host with storage_protocol=%s.", protocol)
                 return False, None
 
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.migrate_volume(volume, host)
         finally:
@@ -902,7 +904,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
     def update_migrated_volume(self, context, volume, new_volume,
                                original_volume_status):
         """Update the name of the migrated volume to it's new ID."""
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.update_migrated_volume(context, volume, new_volume,
                                                  original_volume_status)
@@ -910,7 +912,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def get_pool(self, volume):
-        common = self._login(volume)
+        common = self._login()
         try:
             return common.get_cpg(volume)
         except hpeexceptions.HTTPNotFound:
@@ -920,34 +922,14 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         finally:
             self._logout(common)
 
-    def replication_enable(self, context, volume):
-        """Enable replication on a replication capable volume."""
-        common = self._login(volume)
-        try:
-            return common.replication_enable(context, volume)
-        finally:
-            self._logout(common)
-
-    def replication_disable(self, context, volume):
-        """Disable replication on the specified volume."""
-        common = self._login(volume)
-        try:
-            return common.replication_disable(context, volume)
-        finally:
-            self._logout(common)
-
-    def replication_failover(self, context, volume, secondary):
+    def failover_host(self, context, volumes, secondary_backend_id):
         """Force failover to a secondary replication target."""
-        common = self._login(volume, timeout=30)
-        try:
-            return common.replication_failover(context, volume, secondary)
-        finally:
-            self._logout(common)
-
-    def list_replication_targets(self, context, volume):
-        """Provides a means to obtain replication targets for a volume."""
-        common = self._login(volume, timeout=30)
+        common = self._login(timeout=30)
         try:
-            return common.list_replication_targets(context, volume)
+            # Update the active_backend_id in the driver and return it.
+            active_backend_id, volume_updates = common.failover_host(
+                context, volumes, secondary_backend_id)
+            self._active_backend_id = active_backend_id
+            return active_backend_id, volume_updates
         finally:
             self._logout(common)
diff --git a/releasenotes/notes/replication-v2.1-3par-b3f780a109f9195c.yaml b/releasenotes/notes/replication-v2.1-3par-b3f780a109f9195c.yaml
new file mode 100644 (file)
index 0000000..16289c1
--- /dev/null
@@ -0,0 +1,3 @@
+---
+features:
+  - Adds v2.1 replication support to the HPE 3PAR driver.