]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
3PAR: Implement v2 replication (unmanaged)
authorAlex O'Rourke <alex.orourke@hpe.com>
Thu, 29 Oct 2015 22:04:32 +0000 (15:04 -0700)
committerAlex O'Rourke <alex.orourke@hpe.com>
Thu, 17 Dec 2015 19:14:13 +0000 (11:14 -0800)
This patch implements the unmanaged side of v2 replication in the HPE
3PAR driver.

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.

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,REMOTE_COPY_CPG
volume_driver = cinder.volume.drivers.hpe.hpe_3par_fc.HPE3PARFCDriver
replication_device = target_device_id:eos7,
                     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 unmanaged 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 = replication_mode:periodic,
                     target_device_id:eos16,
                     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 11.51.51.101,
                     iscsi_ip_address:11.51.51.100

Change-Id: I92bbe919c177e8242de8affad62f36a6edf7b0d7
Implements: blueprint hp-3par-v2-replication
DocImpact

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/3par-v2-replication-unmanaged-13f3d6a52198a7a6.yaml [new file with mode: 0644]

index e8fe3f32bac1cfc378ae651afc5407fad66ba13d..f997c6266fa5b01ed267ea13beb677912077bb2c 100644 (file)
@@ -172,6 +172,19 @@ class HPE3PARBaseDriver(object):
 
     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}]
+
     volume_encrypted = {'name': VOLUME_NAME,
                         'id': VOLUME_ID,
                         'display_name': 'Foo Volume',
@@ -223,7 +236,8 @@ class HPE3PARBaseDriver(object):
                 'progress': '0%',
                 'volume_size': 2,
                 'display_name': 'fakesnap',
-                'display_description': FAKE_DESC}
+                'display_description': FAKE_DESC,
+                'volume': volume}
 
     wwn = ["123456789012345", "123456789054321"]
 
@@ -1110,6 +1124,179 @@ class HPE3PARBaseDriver(object):
                               'provider_location': self.CLIENT_ID},
                              return_model)
 
+    @mock.patch('hpe3parclient.version', "4.0.2")
+    @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.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.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.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,
+                    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('hpe3parclient.version', "4.0.2")
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_create_volume_replicated_unmanaged_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
+        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': '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.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': 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_dedup(self, _mock_volume_types):
         # setup_mock_client drive with default configuration
@@ -4274,6 +4461,89 @@ class HPE3PARBaseDriver(object):
                 self.volume_replicated,
                 valid_target_device_id)
 
+    @mock.patch('hpe3parclient.version', "4.0.2")
+    @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)
+        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},
+                             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)
+
 
 class TestHPE3PARFCDriver(HPE3PARBaseDriver, test.TestCase):
 
@@ -6035,7 +6305,11 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
     def test_get_least_used_nsp_for_host_single(self):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
-        mock_client = self.setup_driver()
+
+        # Setup two ISCSI IPs
+        conf = self.setup_configuration()
+        conf.hpe3par_iscsi_ips = ["10.10.220.253"]
+        mock_client = self.setup_driver(config=conf)
 
         mock_client.getPorts.return_value = PORTS_RET
         mock_client.getVLUNs.return_value = VLUNS1_RET
@@ -6045,10 +6319,6 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
             mock_create_client.return_value = mock_client
             common = self.driver._login()
 
-            # Setup a single ISCSI IP
-            iscsi_ips = ["10.10.220.253"]
-            self.driver.configuration.hpe3par_iscsi_ips = iscsi_ips
-
             self.driver.initialize_iscsi_ports(common)
 
             nsp = self.driver._get_least_used_nsp_for_host(common, 'newhost')
@@ -6057,7 +6327,11 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
     def test_get_least_used_nsp_for_host_new(self):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
-        mock_client = self.setup_driver()
+
+        # Setup two ISCSI IPs
+        conf = self.setup_configuration()
+        conf.hpe3par_iscsi_ips = ["10.10.220.252", "10.10.220.253"]
+        mock_client = self.setup_driver(config=conf)
 
         mock_client.getPorts.return_value = PORTS_RET
         mock_client.getVLUNs.return_value = VLUNS1_RET
@@ -6067,10 +6341,6 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
             mock_create_client.return_value = mock_client
             common = self.driver._login()
 
-            # Setup two ISCSI IPs
-            iscsi_ips = ["10.10.220.252", "10.10.220.253"]
-            self.driver.configuration.hpe3par_iscsi_ips = iscsi_ips
-
             self.driver.initialize_iscsi_ports(common)
 
             # Host 'newhost' does not yet have any iscsi paths,
@@ -6081,7 +6351,11 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
     def test_get_least_used_nsp_for_host_reuse(self):
         # setup_mock_client drive with default configuration
         # and return the mock HTTP 3PAR client
-        mock_client = self.setup_driver()
+
+        # Setup two ISCSI IPs
+        conf = self.setup_configuration()
+        conf.hpe3par_iscsi_ips = ["10.10.220.252", "10.10.220.253"]
+        mock_client = self.setup_driver(config=conf)
 
         mock_client.getPorts.return_value = PORTS_RET
         mock_client.getVLUNs.return_value = VLUNS1_RET
@@ -6091,10 +6365,6 @@ class TestHPE3PARISCSIDriver(HPE3PARBaseDriver, test.TestCase):
             mock_create_client.return_value = mock_client
             common = self.driver._login()
 
-            # Setup two ISCSI IPs
-            iscsi_ips = ["10.10.220.252", "10.10.220.253"]
-            self.driver.configuration.hpe3par_iscsi_ips = iscsi_ips
-
             self.driver.initialize_iscsi_ports(common)
 
             # hosts 'foo' and 'bar' already have active iscsi paths
index 4b479a570c9016608e2de959579e75e26a0b6ffb..14c2845f3f16cc289f5cb3abdc8c97abeaf420e6 100644 (file)
@@ -216,10 +216,11 @@ class HPE3PARCommon(object):
         3.0.2 - Python 3 support
         3.0.3 - Remove db access for consistency groups
         3.0.4 - Adds v2 managed replication support
+        3.0.5 - Adds v2 unmanaged replication support
 
     """
 
-    VERSION = "3.0.4"
+    VERSION = "3.0.5"
 
     stats = {}
 
@@ -241,6 +242,7 @@ class HPE3PARCommon(object):
     PERIODIC = 2
     EXTRA_SPEC_REP_MODE = "replication:mode"
     EXTRA_SPEC_REP_SYNC_PERIOD = "replication:sync_period"
+    RC_ACTION_CHANGE_TO_PRIMARY = 7
 
     # Valid values for volume type extra specs
     # The first value in the list is the default value
@@ -266,6 +268,7 @@ class HPE3PARCommon(object):
         self.config = config
         self.client = None
         self.uuid = uuid.uuid4()
+        self._client_conf = {}
         self._replication_targets = []
         self._replication_enabled = False
 
@@ -279,14 +282,22 @@ class HPE3PARCommon(object):
                 LOG.error(msg)
                 raise exception.InvalidInput(reason=msg)
 
+    def check_replication_flags(self, options, required_flags):
+        for flag in required_flags:
+            if not options.get(flag, None):
+                msg = (_('%s is not set and is required for the replicaiton '
+                         'device to be valid.') % flag)
+                LOG.error(msg)
+                raise exception.InvalidInput(reason=msg)
+
     def _create_client(self, timeout=None):
+        hpe3par_api_url = self._client_conf['hpe3par_api_url']
         # Timeout is only supported in version 4.0.2 and greater of the
         # python-3parclient.
         if hpe3parclient.version >= MIN_REP_CLIENT_VERSION:
-            cl = client.HPE3ParClient(self.config.hpe3par_api_url,
-                                      timeout=timeout)
+            cl = client.HPE3ParClient(hpe3par_api_url, timeout=timeout)
         else:
-            cl = client.HPE3ParClient(self.config.hpe3par_api_url)
+            cl = client.HPE3ParClient(hpe3par_api_url)
         client_version = hpe3parclient.version
 
         if client_version < MIN_CLIENT_VERSION:
@@ -304,11 +315,11 @@ class HPE3PARCommon(object):
     def client_login(self):
         try:
             LOG.debug("Connecting to 3PAR")
-            self.client.login(self.config.hpe3par_username,
-                              self.config.hpe3par_password)
+            self.client.login(self._client_conf['hpe3par_username'],
+                              self._client_conf['hpe3par_password'])
         except hpeexceptions.HTTPUnauthorized as ex:
             msg = (_("Failed to Login to 3PAR (%(url)s) because %(err)s") %
-                   {'url': self.config.hpe3par_api_url, 'err': ex})
+                   {'url': self._client_conf['hpe3par_api_url'], 'err': ex})
             LOG.error(msg)
             raise exception.InvalidInput(reason=msg)
 
@@ -317,12 +328,12 @@ class HPE3PARCommon(object):
         if CONF.strict_ssh_host_key_policy:
             policy = "RejectPolicy"
         self.client.setSSHOptions(
-            self.config.san_ip,
-            self.config.san_login,
-            self.config.san_password,
-            port=self.config.san_ssh_port,
-            conn_timeout=self.config.ssh_conn_timeout,
-            privatekey=self.config.san_private_key,
+            self._client_conf['san_ip'],
+            self._client_conf['san_login'],
+            self._client_conf['san_password'],
+            port=self._client_conf['san_ssh_port'],
+            conn_timeout=self._client_conf['ssh_conn_timeout'],
+            privatekey=self._client_conf['san_private_key'],
             missing_key_policy=policy,
             known_hosts_file=known_hosts_file)
 
@@ -357,9 +368,10 @@ class HPE3PARCommon(object):
         return cl
 
     def _destroy_replication_client(self, client):
-        client.logout()
+        if client is not None:
+            client.logout()
 
-    def do_setup(self, context, timeout=None):
+    def do_setup(self, context, volume=None, timeout=None):
         if hpe3parclient is None:
             msg = _('You must install hpe3parclient before using 3PAR'
                     ' drivers. Run "pip install python-3parclient" to'
@@ -367,6 +379,11 @@ class HPE3PARCommon(object):
             raise exception.VolumeBackendAPIException(data=msg)
 
         try:
+            # This will set self._client_conf with the proper credentials
+            # 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.client = self._create_client(timeout=timeout)
             wsapi_version = self.client.getWsApiVersion()
             self.API_VERSION = wsapi_version['build']
@@ -416,11 +433,6 @@ class HPE3PARCommon(object):
         finally:
             self.client_logout()
 
-        # v2 replication setup
-        if not self._replication_enabled and (
-           hpe3parclient.version >= MIN_REP_CLIENT_VERSION):
-            self._do_replication_setup()
-
     def check_for_setup_error(self):
         if self.client:
             self.client_login()
@@ -943,6 +955,9 @@ class HPE3PARCommon(object):
             if 'must be in the same domain' in e.get_description():
                 LOG.error(e.get_description())
                 raise exception.Invalid3PARDomain(err=e.get_description())
+            else:
+                raise exception.VolumeBackendAPIException(
+                    data=e.get_description())
 
     def _safe_hostname(self, hostname):
         """We have to use a safe hostname length for 3PAR host names."""
@@ -2693,7 +2708,7 @@ class HPE3PARCommon(object):
                 volume['id'], volume['provider_location'])
             cl = self._create_replication_client(failover_target)
             cl.recoverRemoteCopyGroupFromDisaster(
-                remote_rcg_name, self.client.RC_ACTION_CHANGE_TO_PRIMARY)
+                remote_rcg_name, self.RC_ACTION_CHANGE_TO_PRIMARY)
             new_location = volume['provider_location'] + ":" + (
                 failover_target['id'])
 
@@ -2740,9 +2755,16 @@ class HPE3PARCommon(object):
                             "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 or (
-               target['target_device_id'] in allowed_names):
+            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)
 
@@ -2750,49 +2772,36 @@ class HPE3PARCommon(object):
                 'targets': replication_targets}
 
     def _do_replication_setup(self):
+        replication_targets = []
         replication_devices = self.config.replication_device
         if replication_devices:
             for dev in replication_devices:
-                remote_array = {}
-                is_managed = dev.get('managed_backend_name')
-                if not is_managed:
-                    msg = _("Unmanaged replication is not supported at this "
-                            "time. Please configure cinder.conf for managed "
-                            "replication.")
-                    LOG.error(msg)
-                    raise exception.VolumeBackendAPIException(data=msg)
-
-                remote_array['managed_backend_name'] = is_managed
+                remote_array = dict(dev.items())
+                # Override and set defaults for certain entries
+                remote_array['managed_backend_name'] = (
+                    dev.get('managed_backend_name'))
                 remote_array['replication_mode'] = (
                     self._get_remote_copy_mode_num(
                         dev.get('replication_mode')))
-                remote_array['target_device_id'] = (
-                    dev.get('target_device_id'))
-                remote_array['cpg_map'] = (
-                    dev.get('cpg_map'))
-                remote_array['hpe3par_api_url'] = (
-                    dev.get('hpe3par_api_url'))
-                remote_array['hpe3par_username'] = (
-                    dev.get('hpe3par_username'))
-                remote_array['hpe3par_password'] = (
-                    dev.get('hpe3par_password'))
-                remote_array['san_ip'] = (
-                    dev.get('san_ip'))
-                remote_array['san_login'] = (
-                    dev.get('san_login'))
-                remote_array['san_password'] = (
-                    dev.get('san_password'))
                 remote_array['san_ssh_port'] = (
                     dev.get('san_ssh_port', self.config.san_ssh_port))
                 remote_array['ssh_conn_timeout'] = (
                     dev.get('ssh_conn_timeout', self.config.ssh_conn_timeout))
                 remote_array['san_private_key'] = (
                     dev.get('san_private_key', self.config.san_private_key))
+                # Format iscsi IPs correctly
+                iscsi_ips = dev.get('hpe3par_iscsi_ips')
+                if iscsi_ips:
+                    remote_array['hpe3par_iscsi_ips'] = iscsi_ips.split(' ')
+                # 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']
 
                 # Make sure we can log into the client, that it has been
                 # correctly configured, and it its version matches the
                 # primary arrarys version.
+                cl = None
                 try:
                     cl = self._create_replication_client(remote_array)
                     array_id = six.text_type(cl.getStorageSystemInfo()['id'])
@@ -2816,13 +2825,14 @@ class HPE3PARCommon(object):
                                    "In order to be valid, target_device_id, "
                                    "replication_mode, "
                                    "hpe3par_api_url, hpe3par_username, "
-                                   "hpe3par_password, cpg_map, and "
+                                   "hpe3par_password, cpg_map, san_ip, "
+                                   "san_login, and san_password "
                                    "must be specified. If the target is "
                                    "managed, managed_backend_name must be set "
                                    "as well.") % array_name)
                         LOG.warning(msg)
                     else:
-                        self._replication_targets.append(remote_array)
+                        replication_targets.append(remote_array)
                 except Exception:
                     msg = (_LE("Could not log in to 3PAR array (%s) with the "
                                "provided credentials.") % array_name)
@@ -2830,14 +2840,20 @@ class HPE3PARCommon(object):
                 finally:
                     self._destroy_replication_client(cl)
 
+            self._replication_targets = replication_targets
             if self._is_replication_configured_correct():
                 self._replication_enabled = True
 
     def _is_valid_replication_array(self, target):
-        for k, v in target.items():
-            if v is None:
-                return False
-        return True
+        required_flags = ['hpe3par_api_url', 'hpe3par_username',
+                          'hpe3par_password', 'san_ip', 'san_login',
+                          'san_password', 'target_device_id',
+                          'replication_mode', 'cpg_map']
+        try:
+            self.check_replication_flags(target, required_flags)
+            return True
+        except Exception:
+            return False
 
     def _is_replication_configured_correct(self):
         rep_flag = True
@@ -2896,6 +2912,73 @@ class HPE3PARCommon(object):
             ret_mode = self.PERIODIC
         return ret_mode
 
+    def _get_3par_config(self, volume):
+        if hpe3parclient.version >= MIN_REP_CLIENT_VERSION:
+            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
+        self._build_3par_config(conf)
+
+    def _build_3par_config(self, conf=None):
+        """Build 3PAR client config dictionary.
+
+        self._client_conf will contain values from self.config if the volume
+        is located on the primary array in order to properly contact it. If
+        the volume has been failed over and therefore on a secondary array,
+        self._client_conf will contain values on how to contact that array.
+        The only time we will return with entries from a secondary array is
+        with unmanaged replication.
+        """
+        if conf:
+            self._client_conf['hpe3par_username'] = (
+                conf.get('hpe3par_username'))
+            self._client_conf['hpe3par_password'] = (
+                conf.get('hpe3par_password'))
+            self._client_conf['san_ip'] = conf.get('san_ip')
+            self._client_conf['san_login'] = conf.get('san_login')
+            self._client_conf['san_password'] = conf.get('san_password')
+            self._client_conf['san_ssh_port'] = conf.get('san_ssh_port')
+            self._client_conf['ssh_conn_timeout'] = (
+                conf.get('ssh_conn_timeout'))
+            self._client_conf['san_private_key'] = conf.get('san_private_key')
+            self._client_conf['hpe3par_api_url'] = conf.get('hpe3par_api_url')
+            self._client_conf['hpe3par_iscsi_ips'] = (
+                conf.get('hpe3par_iscsi_ips'))
+            self._client_conf['hpe3par_iscsi_chap_enabled'] = (
+                conf.get('hpe3par_iscsi_chap_enabled'))
+            self._client_conf['iscsi_ip_address'] = (
+                conf.get('iscsi_ip_address'))
+            self._client_conf['iscsi_port'] = conf.get('iscsi_port')
+        else:
+            self._client_conf['hpe3par_username'] = (
+                self.config.hpe3par_username)
+            self._client_conf['hpe3par_password'] = (
+                self.config.hpe3par_password)
+            self._client_conf['san_ip'] = self.config.san_ip
+            self._client_conf['san_login'] = self.config.san_login
+            self._client_conf['san_password'] = self.config.san_password
+            self._client_conf['san_ssh_port'] = self.config.san_ssh_port
+            self._client_conf['ssh_conn_timeout'] = (
+                self.config.ssh_conn_timeout)
+            self._client_conf['san_private_key'] = self.config.san_private_key
+            self._client_conf['hpe3par_api_url'] = self.config.hpe3par_api_url
+            self._client_conf['hpe3par_iscsi_ips'] = (
+                self.config.hpe3par_iscsi_ips)
+            self._client_conf['hpe3par_iscsi_chap_enabled'] = (
+                self.config.hpe3par_iscsi_chap_enabled)
+            self._client_conf['iscsi_ip_address'] = (
+                self.config.iscsi_ip_address)
+            self._client_conf['iscsi_port'] = self.config.iscsi_port
+
     def _get_cpg_from_cpg_map(self, cpg_map, target_cpg):
         ret_target_cpg = None
         cpg_pairs = cpg_map.split(' ')
@@ -3096,8 +3179,9 @@ class HPE3PARCommon(object):
         # Do regular volume replication destroy now config mirroring is off
         try:
             self._do_volume_replication_destroy(volume, rcg_name)
-        except Exception:
-            msg = (_("The failed-over volume could not be deleted."))
+        except Exception as ex:
+            msg = (_("The failed-over volume could not be deleted: %s") %
+                   six.text_type(ex))
             LOG.error(msg)
             raise exception.VolumeIsBusy(message=msg)
         finally:
index 290628c4e94d483f5c35262084bdc2b245b2d187..5d813914aba5dcec93615e75e3dc22e05ba2d66b 100644 (file)
@@ -92,10 +92,11 @@ class HPE3PARFCDriver(driver.TransferVD,
         3.0.0 - Rebranded HP to HPE.
         3.0.1 - Remove db access for consistency groups
         3.0.2 - Adds v2 managed replication support
+        3.0.3 - Adds v2 unmanaged replication support
 
     """
 
-    VERSION = "3.0.2"
+    VERSION = "3.0.3"
 
     def __init__(self, *args, **kwargs):
         super(HPE3PARFCDriver, self).__init__(*args, **kwargs)
@@ -106,12 +107,12 @@ class HPE3PARFCDriver(driver.TransferVD,
     def _init_common(self):
         return hpecommon.HPE3PARCommon(self.configuration)
 
-    def _login(self, timeout=None):
+    def _login(self, volume=None, 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, timeout=timeout)
+            common.do_setup(None, volume, timeout=timeout)
             common.client_login()
         except Exception:
             if common._replication_enabled:
@@ -165,21 +166,21 @@ class HPE3PARFCDriver(driver.TransferVD,
         pass
 
     def create_volume(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_volume(volume)
         finally:
             self._logout(common)
 
     def create_cloned_volume(self, volume, src_vref):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_cloned_volume(volume, src_vref)
         finally:
             self._logout(common)
 
     def delete_volume(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.delete_volume(volume)
         finally:
@@ -190,21 +191,21 @@ class HPE3PARFCDriver(driver.TransferVD,
 
         TODO: support using the size from the user.
         """
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_volume_from_snapshot(volume, snapshot)
         finally:
             self._logout(common)
 
     def create_snapshot(self, snapshot):
-        common = self._login()
+        common = self._login(snapshot['volume'])
         try:
             common.create_snapshot(snapshot)
         finally:
             self._logout(common)
 
     def delete_snapshot(self, snapshot):
-        common = self._login()
+        common = self._login(snapshot['volume'])
         try:
             common.delete_snapshot(snapshot)
         finally:
@@ -250,7 +251,7 @@ class HPE3PARFCDriver(driver.TransferVD,
           * Create a VLUN for that HOST with the volume we want to export.
 
         """
-        common = self._login()
+        common = self._login(volume)
         try:
             # we have to make sure we have a host
             host = self._create_host(common, volume, connector)
@@ -293,7 +294,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()
+        common = self._login(volume)
         try:
             hostname = common._safe_hostname(connector['host'])
             common.terminate_connection(volume, hostname,
@@ -449,7 +450,7 @@ class HPE3PARFCDriver(driver.TransferVD,
         pass
 
     def extend_volume(self, volume, new_size):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.extend_volume(volume, new_size)
         finally:
@@ -504,21 +505,21 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing(self, volume, existing_ref):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.manage_existing(volume, existing_ref)
         finally:
             self._logout(common)
 
     def manage_existing_get_size(self, volume, existing_ref):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.manage_existing_get_size(volume, existing_ref)
         finally:
             self._logout(common)
 
     def unmanage(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.unmanage(volume)
         finally:
@@ -526,14 +527,14 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def attach_volume(self, context, volume, instance_uuid, host_name,
                       mountpoint):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.attach_volume(volume, instance_uuid)
         finally:
             self._logout(common)
 
     def detach_volume(self, context, volume, attachment=None):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.detach_volume(volume, attachment)
         finally:
@@ -541,7 +542,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()
+        common = self._login(volume)
         try:
             return common.retype(volume, new_type, diff, host)
         finally:
@@ -555,7 +556,7 @@ class HPE3PARFCDriver(driver.TransferVD,
                           "to a host with storage_protocol=%s.", protocol)
                 return False, None
 
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.migrate_volume(volume, host)
         finally:
@@ -564,7 +565,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()
+        common = self._login(volume)
         try:
             return common.update_migrated_volume(context, volume, new_volume,
                                                  original_volume_status)
@@ -572,7 +573,7 @@ class HPE3PARFCDriver(driver.TransferVD,
             self._logout(common)
 
     def get_pool(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.get_cpg(volume)
         except hpeexceptions.HTTPNotFound:
@@ -591,7 +592,7 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def replication_enable(self, context, volume):
         """Enable replication on a replication capable volume."""
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.replication_enable(context, volume)
         finally:
@@ -599,7 +600,7 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def replication_disable(self, context, volume):
         """Disable replication on the specified volume."""
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.replication_disable(context, volume)
         finally:
@@ -607,7 +608,7 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def replication_failover(self, context, volume, secondary):
         """Force failover to a secondary replication target."""
-        common = self._login(timeout=30)
+        common = self._login(volume, timeout=30)
         try:
             return common.replication_failover(context, volume, secondary)
         finally:
@@ -615,7 +616,7 @@ class HPE3PARFCDriver(driver.TransferVD,
 
     def list_replication_targets(self, context, volume):
         """Provides a means to obtain replication targets for a volume."""
-        common = self._login(timeout=30)
+        common = self._login(volume, timeout=30)
         try:
             return common.list_replication_targets(context, volume)
         finally:
index ed5cc044b96d2df95cc9f51e20375b0f1405bd33..ae0b9bc0fe1b7b5216ceb5597cf46b7fd34ea15e 100644 (file)
@@ -104,10 +104,11 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         3.0.2 - Remove db access for consistency groups
         3.0.3 - Fix multipath dictionary key error. bug #1522062
         3.0.4 - Adds v2 managed replication support
+        3.0.5 - Adds v2 unmanaged replication support
 
     """
 
-    VERSION = "3.0.4"
+    VERSION = "3.0.5"
 
     def __init__(self, *args, **kwargs):
         super(HPE3PARISCSIDriver, self).__init__(*args, **kwargs)
@@ -117,12 +118,12 @@ class HPE3PARISCSIDriver(driver.TransferVD,
     def _init_common(self):
         return hpecommon.HPE3PARCommon(self.configuration)
 
-    def _login(self, timeout=None):
+    def _login(self, volume=None, timeout=None):
         common = self._init_common()
-        common.do_setup(None, timeout=timeout)
         # 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, timeout=timeout)
             common.client_login()
         except Exception:
             if common._replication_enabled:
@@ -171,6 +172,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         self._check_flags(common)
         common.check_for_setup_error()
 
+        self.iscsi_ips = {}
         common.client_login()
         try:
             self.initialize_iscsi_ports(common)
@@ -181,13 +183,13 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         # map iscsi_ip-> ip_port
         #             -> iqn
         #             -> nsp
-        self.iscsi_ips = {}
+        iscsi_ip_list = {}
         temp_iscsi_ip = {}
 
         # use the 3PAR ip_addr list for iSCSI configuration
-        if len(self.configuration.hpe3par_iscsi_ips) > 0:
+        if len(common._client_conf['hpe3par_iscsi_ips']) > 0:
             # add port values to ip_addr, if necessary
-            for ip_addr in self.configuration.hpe3par_iscsi_ips:
+            for ip_addr in common._client_conf['hpe3par_iscsi_ips']:
                 ip = ip_addr.split(':')
                 if len(ip) == 1:
                     temp_iscsi_ip[ip_addr] = {'ip_port': DEFAULT_ISCSI_PORT}
@@ -199,9 +201,9 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         # add the single value iscsi_ip_address option to the IP dictionary.
         # This way we can see if it's a valid iSCSI IP. If it's not valid,
         # we won't use it and won't bother to report it, see below
-        if (self.configuration.iscsi_ip_address not in temp_iscsi_ip):
-            ip = self.configuration.iscsi_ip_address
-            ip_port = self.configuration.iscsi_port
+        if (common._client_conf['iscsi_ip_address'] not in temp_iscsi_ip):
+            ip = common._client_conf['iscsi_ip_address']
+            ip_port = common._client_conf['iscsi_port']
             temp_iscsi_ip[ip] = {'ip_port': ip_port}
 
         # get all the valid iSCSI ports from 3PAR
@@ -213,17 +215,16 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             ip = port['IPAddr']
             if ip in temp_iscsi_ip:
                 ip_port = temp_iscsi_ip[ip]['ip_port']
-                self.iscsi_ips[ip] = {'ip_port': ip_port,
-                                      'nsp': port['nsp'],
-                                      'iqn': port['iSCSIName']
-                                      }
+                iscsi_ip_list[ip] = {'ip_port': ip_port,
+                                     'nsp': port['nsp'],
+                                     'iqn': port['iSCSIName']}
                 del temp_iscsi_ip[ip]
 
         # if the single value iscsi_ip_address option is still in the
         # temp dictionary it's because it defaults to $my_ip which doesn't
         # make sense in this context. So, if present, remove it and move on.
-        if (self.configuration.iscsi_ip_address in temp_iscsi_ip):
-            del temp_iscsi_ip[self.configuration.iscsi_ip_address]
+        if common._client_conf['iscsi_ip_address'] in temp_iscsi_ip:
+            del temp_iscsi_ip[common._client_conf['iscsi_ip_address']]
 
         # lets see if there are invalid iSCSI IPs left in the temp dict
         if len(temp_iscsi_ip) > 0:
@@ -232,17 +233,18 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                             "iscsi_ip_address '%s.'"),
                         (", ".join(temp_iscsi_ip)))
 
-        if not len(self.iscsi_ips) > 0:
+        if not len(iscsi_ip_list) > 0:
             msg = _('At least one valid iSCSI IP address must be set.')
             LOG.error(msg)
             raise exception.InvalidInput(reason=msg)
+        self.iscsi_ips[common._client_conf['hpe3par_api_url']] = iscsi_ip_list
 
     def check_for_setup_error(self):
         """Setup errors are already checked for in do_setup so return pass."""
         pass
 
     def create_volume(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_volume(volume)
         finally:
@@ -250,14 +252,14 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def create_cloned_volume(self, volume, src_vref):
         """Clone an existing volume."""
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_cloned_volume(volume, src_vref)
         finally:
             self._logout(common)
 
     def delete_volume(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.delete_volume(volume)
         finally:
@@ -268,21 +270,21 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
         TODO: support using the size from the user.
         """
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.create_volume_from_snapshot(volume, snapshot)
         finally:
             self._logout(common)
 
     def create_snapshot(self, snapshot):
-        common = self._login()
+        common = self._login(snapshot['volume'])
         try:
             common.create_snapshot(snapshot)
         finally:
             self._logout(common)
 
     def delete_snapshot(self, snapshot):
-        common = self._login()
+        common = self._login(snapshot['volume'])
         try:
             common.delete_snapshot(snapshot)
         finally:
@@ -314,8 +316,17 @@ class HPE3PARISCSIDriver(driver.TransferVD,
           * Create a host on the 3par
           * create vlun on the 3par
         """
-        common = self._login()
+        common = self._login(volume)
         try:
+            # If the volume has been failed over, we need to reinitialize
+            # iSCSI ports so they represent the new array.
+            if volume.get('replication_status') == 'failed-over' and (
+               common._client_conf['hpe3par_api_url'] not in self.iscsi_ips):
+                self.initialize_iscsi_ports(common)
+
+            # Grab the correct iSCSI ports
+            iscsi_ips = self.iscsi_ips[common._client_conf['hpe3par_api_url']]
+
             # we have to make sure we have a host
             host, username, password = self._create_host(
                 common,
@@ -331,7 +342,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                 target_luns = []
 
                 # Target portal ips are defined in cinder.conf.
-                target_portal_ips = self.iscsi_ips.keys()
+                target_portal_ips = iscsi_ips.keys()
 
                 # Collect all existing VLUNs for this volume/host combination.
                 existing_vluns = common.find_existing_vluns(volume, host)
@@ -347,15 +358,15 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                         # instead of creating a new VLUN.
                         for v in existing_vluns:
                             portPos = common.build_portPos(
-                                self.iscsi_ips[iscsi_ip]['nsp'])
+                                iscsi_ips[iscsi_ip]['nsp'])
                             if v['portPos'] == portPos:
                                 vlun = v
                                 break
                         else:
                             vlun = common.create_vlun(
-                                volume, host, self.iscsi_ips[iscsi_ip]['nsp'])
+                                volume, host, iscsi_ips[iscsi_ip]['nsp'])
                         iscsi_ip_port = "%s:%s" % (
-                            iscsi_ip, self.iscsi_ips[iscsi_ip]['ip_port'])
+                            iscsi_ip, iscsi_ips[iscsi_ip]['ip_port'])
                         target_portals.append(iscsi_ip_port)
                         target_iqns.append(port['iSCSIName'])
                         target_luns.append(vlun['lun'])
@@ -400,12 +411,12 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                 if least_used_nsp is None:
                     LOG.warning(_LW("Least busy iSCSI port not found, "
                                     "using first iSCSI port in list."))
-                    iscsi_ip = self.iscsi_ips.keys()[0]
+                    iscsi_ip = iscsi_ips.keys()[0]
                 else:
-                    iscsi_ip = self._get_ip_using_nsp(least_used_nsp)
+                    iscsi_ip = self._get_ip_using_nsp(least_used_nsp, common)
 
-                iscsi_ip_port = self.iscsi_ips[iscsi_ip]['ip_port']
-                iscsi_target_iqn = self.iscsi_ips[iscsi_ip]['iqn']
+                iscsi_ip_port = iscsi_ips[iscsi_ip]['ip_port']
+                iscsi_target_iqn = iscsi_ips[iscsi_ip]['iqn']
                 info = {'driver_volume_type': 'iscsi',
                         'data': {'target_portal': "%s:%s" %
                                  (iscsi_ip, iscsi_ip_port),
@@ -415,7 +426,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                                  }
                         }
 
-            if self.configuration.hpe3par_iscsi_chap_enabled:
+            if common._client_conf['hpe3par_iscsi_chap_enabled']:
                 info['data']['auth_method'] = 'CHAP'
                 info['data']['auth_username'] = username
                 info['data']['auth_password'] = password
@@ -429,7 +440,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()
+        common = self._login(volume)
         try:
             hostname = common._safe_hostname(connector['host'])
             common.terminate_connection(
@@ -497,7 +508,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def _set_3par_chaps(self, common, hostname, volume, username, password):
         """Sets a 3PAR host's CHAP credentials."""
-        if not self.configuration.hpe3par_iscsi_chap_enabled:
+        if not common._client_conf['hpe3par_iscsi_chap_enabled']:
             return
 
         mod_request = {'chapOperation': common.client.HOST_EDIT_ADD,
@@ -517,7 +528,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         domain = common.get_domain(cpg)
 
         # Get the CHAP secret if CHAP is enabled
-        if self.configuration.hpe3par_iscsi_chap_enabled:
+        if common._client_conf['hpe3par_iscsi_chap_enabled']:
             vol_name = common._get_3par_vol_name(volume['id'])
             username = common.client.getVolumeMetaData(
                 vol_name, CHAP_USER_KEY)['value']
@@ -550,7 +561,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                     password)
                 host = common._get_3par_host(hostname)
             elif (not host['initiatorChapEnabled'] and
-                    self.configuration.hpe3par_iscsi_chap_enabled):
+                    common._client_conf['hpe3par_iscsi_chap_enabled']):
                 LOG.warning(_LW("Host exists without CHAP credentials set and "
                                 "has iSCSI attachments but CHAP is enabled. "
                                 "Updating host with new CHAP credentials."))
@@ -567,7 +578,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         """Gets the associated account, generates CHAP info and updates."""
         model_update = {}
 
-        if not self.configuration.hpe3par_iscsi_chap_enabled:
+        if not common._client_conf['hpe3par_iscsi_chap_enabled']:
             model_update['provider_auth'] = None
             return model_update
 
@@ -636,7 +647,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         return model_update
 
     def create_export(self, context, volume, connector):
-        common = self._login()
+        common = self._login(volume)
         try:
             return self._do_export(common, volume)
         finally:
@@ -647,7 +658,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
         Also retrieves CHAP credentials, if present on the volume
         """
-        common = self._login()
+        common = self._login(volume)
         try:
             vol_name = common._get_3par_vol_name(volume['id'])
             common.client.getVolume(vol_name)
@@ -687,7 +698,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             * Return NSP with fewest active vluns
         """
 
-        iscsi_nsps = self._get_iscsi_nsps()
+        iscsi_nsps = self._get_iscsi_nsps(common)
         # If there's only one path, use it
         if len(iscsi_nsps) == 1:
             return iscsi_nsps[0]
@@ -705,19 +716,21 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         # Calculate the least used iscsi nsp
         least_used_nsp = self._get_least_used_nsp(common,
                                                   vluns['members'],
-                                                  self._get_iscsi_nsps())
+                                                  self._get_iscsi_nsps(common))
         return least_used_nsp
 
-    def _get_iscsi_nsps(self):
+    def _get_iscsi_nsps(self, common):
         """Return the list of candidate nsps."""
         nsps = []
-        for value in self.iscsi_ips.values():
+        iscsi_ips = self.iscsi_ips[common._client_conf['hpe3par_api_url']]
+        for value in iscsi_ips.values():
             nsps.append(value['nsp'])
         return nsps
 
-    def _get_ip_using_nsp(self, nsp):
+    def _get_ip_using_nsp(self, nsp, common):
         """Return IP associated with given nsp."""
-        for (key, value) in self.iscsi_ips.items():
+        iscsi_ips = self.iscsi_ips[common._client_conf['hpe3par_api_url']]
+        for (key, value) in iscsi_ips.items():
             if value['nsp'] == nsp:
                 return key
 
@@ -748,7 +761,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
         return current_least_used_nsp
 
     def extend_volume(self, volume, new_size):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.extend_volume(volume, new_size)
         finally:
@@ -803,21 +816,21 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def manage_existing(self, volume, existing_ref):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.manage_existing(volume, existing_ref)
         finally:
             self._logout(common)
 
     def manage_existing_get_size(self, volume, existing_ref):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.manage_existing_get_size(volume, existing_ref)
         finally:
             self._logout(common)
 
     def unmanage(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.unmanage(volume)
         finally:
@@ -825,14 +838,14 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def attach_volume(self, context, volume, instance_uuid, host_name,
                       mountpoint):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.attach_volume(volume, instance_uuid)
         finally:
             self._logout(common)
 
     def detach_volume(self, context, volume, attachment=None):
-        common = self._login()
+        common = self._login(volume)
         try:
             common.detach_volume(volume, attachment)
         finally:
@@ -840,7 +853,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()
+        common = self._login(volume)
         try:
             return common.retype(volume, new_type, diff, host)
         finally:
@@ -854,7 +867,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
                           "to a host with storage_protocol=%s.", protocol)
                 return False, None
 
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.migrate_volume(volume, host)
         finally:
@@ -863,7 +876,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()
+        common = self._login(volume)
         try:
             return common.update_migrated_volume(context, volume, new_volume,
                                                  original_volume_status)
@@ -871,7 +884,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
             self._logout(common)
 
     def get_pool(self, volume):
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.get_cpg(volume)
         except hpeexceptions.HTTPNotFound:
@@ -890,7 +903,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def replication_enable(self, context, volume):
         """Enable replication on a replication capable volume."""
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.replication_enable(context, volume)
         finally:
@@ -898,7 +911,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def replication_disable(self, context, volume):
         """Disable replication on the specified volume."""
-        common = self._login()
+        common = self._login(volume)
         try:
             return common.replication_disable(context, volume)
         finally:
@@ -906,7 +919,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def replication_failover(self, context, volume, secondary):
         """Force failover to a secondary replication target."""
-        common = self._login(timeout=30)
+        common = self._login(volume, timeout=30)
         try:
             return common.replication_failover(context, volume, secondary)
         finally:
@@ -914,7 +927,7 @@ class HPE3PARISCSIDriver(driver.TransferVD,
 
     def list_replication_targets(self, context, volume):
         """Provides a means to obtain replication targets for a volume."""
-        common = self._login(timeout=30)
+        common = self._login(volume, timeout=30)
         try:
             return common.list_replication_targets(context, volume)
         finally:
diff --git a/releasenotes/notes/3par-v2-replication-unmanaged-13f3d6a52198a7a6.yaml b/releasenotes/notes/3par-v2-replication-unmanaged-13f3d6a52198a7a6.yaml
new file mode 100644 (file)
index 0000000..6c3d528
--- /dev/null
@@ -0,0 +1,3 @@
+---
+features:
+  - Added unmanaged v2 replication support to the HPE 3PAR driver.