]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
LeftHand: Implement v2 replication (unmanaged)
authorAlex O'Rourke <alex.orourke@hpe.com>
Mon, 23 Nov 2015 18:43:13 +0000 (10:43 -0800)
committerAlex O'Rourke <alex.orourke@hpe.com>
Fri, 22 Jan 2016 22:27:49 +0000 (14:27 -0800)
This patch implements the unmanaged side of v2 replication in the HPE
LeftHand driver.

With unmanaged replication, the original driver instance will handle
all provisioning requests even after a failover.

cinder.conf should have the replication config group:

[lefthandrep]
hpelefthand_api_url = https://10.10.10.10:8081/lhos
hpelefthand_username = user
hpelefthand_password = pass
hpelefthand_clustername = vsa-12-5-mgmt1-vip
volume_backend_name = lefthandrep
volume_driver = cinder.volume.drivers.hpe.hpe_lefthand_iscsi.\
                HPELeftHandISCSIDriver
replication_device = target_device_id:lh-id,
                     hpelefthand_api_url:https://11.11.11.11:8081/lhos,
                     hpelefthand_username:user2,
                     hpelefthand_password:pass2,
                     hpelefthand_clustername:vsa-12-5-mgmt2-vip

Change-Id: I3c489e986648eee16b3bf5a19799a4ea0c0240b0
Implements: blueprint hp-lefthand-v2-replication
DocImpact

cinder/tests/unit/test_hpelefthand.py
cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py
releasenotes/notes/lefthand-v2-replication-unmanaged-315c2888423f0f3b.yaml [new file with mode: 0644]

index df1f9f7bfa282be149d05b62f19c32f2652765be..5f6c08b7bfb99110dc1882632f3d071bbb2b4a78 100644 (file)
@@ -88,6 +88,17 @@ class HPELeftHandBaseDriver(object):
                      'cluster_id': 6,
                      'cluster_vip': '10.0.1.6'}]
 
+    repl_targets_unmgd = [{'target_device_id': 'target',
+                           'hpelefthand_api_url': HPELEFTHAND_API_URL2,
+                           'hpelefthand_username': HPELEFTHAND_USERNAME,
+                           'hpelefthand_password': HPELEFTHAND_PASSWORD,
+                           'hpelefthand_clustername': HPELEFTHAND_CLUSTER_NAME,
+                           'hpelefthand_ssh_port': HPELEFTHAND_SSH_PORT,
+                           'ssh_conn_timeout': HPELEFTHAND_SAN_SSH_CON_TIMEOUT,
+                           'san_private_key': HPELEFTHAND_SAN_SSH_PRIVATE,
+                           'cluster_id': 6,
+                           'cluster_vip': '10.0.1.6'}]
+
     list_rep_targets = [{'target_device_id': 'target'}]
 
     serverName = 'fakehost'
@@ -98,7 +109,8 @@ class HPELeftHandBaseDriver(object):
     snapshot_id = 3
     snapshot = {
         'name': snapshot_name,
-        'volume_name': volume_name}
+        'volume_name': volume_name,
+        'volume': volume}
 
     cloned_volume_name = "clone_volume"
     cloned_volume = {'name': cloned_volume_name}
@@ -1921,6 +1933,67 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase):
                               'provider_location': prov_location},
                              return_model)
 
+    @mock.patch('hpelefthandclient.version', "2.0.1")
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_create_volume_replicated_unmanaged(self, _mock_get_volume_type):
+        # set up driver with default config
+        conf = self.default_mock_conf()
+        conf.replication_device = self.repl_targets_unmgd
+        mock_client = self.setup_driver(config=conf)
+        mock_client.createVolume.return_value = {
+            'iscsiIqn': self.connector['initiator']}
+        mock_client.doesRemoteSnapshotScheduleExist.return_value = False
+        mock_replicated_client = self.setup_driver(config=conf)
+
+        _mock_get_volume_type.return_value = {
+            'name': 'replicated',
+            'extra_specs': {
+                'replication_enabled': '<is> True'}}
+
+        with mock.patch.object(
+                hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+                '_create_client') as mock_do_setup, \
+            mock.patch.object(
+                hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+                '_create_replication_client') as mock_replication_client:
+            mock_do_setup.return_value = mock_client
+            mock_replication_client.return_value = mock_replicated_client
+            return_model = self.driver.create_volume(self.volume_replicated)
+
+            expected = [
+                mock.call.createVolume(
+                    'fakevolume_replicated',
+                    1,
+                    units.Gi,
+                    {'isThinProvisioned': True,
+                     'clusterName': 'CloudCluster1'}),
+                mock.call.doesRemoteSnapshotScheduleExist(
+                    'fakevolume_replicated_SCHED_Pri'),
+                mock.call.createRemoteSnapshotSchedule(
+                    'fakevolume_replicated',
+                    'fakevolume_replicated_SCHED',
+                    1800,
+                    '1970-01-01T00:00:00Z',
+                    5,
+                    'CloudCluster1',
+                    5,
+                    'fakevolume_replicated',
+                    '1.1.1.1',
+                    'foo1',
+                    'bar2'),
+                mock.call.logout()]
+
+            mock_client.assert_has_calls(
+                self.driver_startup_call_stack +
+                self.driver_startup_ssh +
+                expected)
+            prov_location = '10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0'
+            rep_data = json.dumps({"location": HPELEFTHAND_API_URL})
+            self.assertEqual({'replication_status': 'enabled',
+                              'replication_driver_data': rep_data,
+                              'provider_location': prov_location},
+                             return_model)
+
     @mock.patch('hpelefthandclient.version', "2.0.1")
     @mock.patch.object(volume_types, 'get_volume_type')
     def test_delete_volume_replicated(self, _mock_get_volume_type):
@@ -2203,3 +2276,50 @@ class TestHPELeftHandISCSIDriver(HPELeftHandBaseDriver, test.TestCase):
                               'replication_driver_data': rep_data,
                               'host': FAKE_FAILOVER_HOST},
                              return_model)
+
+    @mock.patch('hpelefthandclient.version', "2.0.1")
+    @mock.patch.object(volume_types, 'get_volume_type')
+    def test_replication_failover_unmanaged(self, _mock_get_volume_type):
+        ctxt = context.get_admin_context()
+        # set up driver with default config
+        conf = self.default_mock_conf()
+        conf.replication_device = self.repl_targets_unmgd
+        mock_client = self.setup_driver(config=conf)
+        mock_replicated_client = self.setup_driver(config=conf)
+        mock_replicated_client.getVolumeByName.return_value = {
+            'iscsiIqn': self.connector['initiator']}
+
+        _mock_get_volume_type.return_value = {
+            'name': 'replicated',
+            'extra_specs': {
+                'replication_enabled': '<is> True'}}
+
+        with mock.patch.object(
+                hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+                '_create_client') as mock_do_setup, \
+            mock.patch.object(
+                hpe_lefthand_iscsi.HPELeftHandISCSIDriver,
+                '_create_replication_client') as mock_replication_client:
+            mock_do_setup.return_value = mock_client
+            mock_replication_client.return_value = mock_replicated_client
+            valid_target_device_id = (self.repl_targets[0]['target_device_id'])
+            invalid_target_device_id = 'INVALID'
+
+            # test invalid secondary target
+            self.assertRaises(
+                exception.VolumeBackendAPIException,
+                self.driver.replication_failover,
+                ctxt,
+                self.volume_replicated,
+                invalid_target_device_id)
+
+            # test a successful failover
+            return_model = self.driver.replication_failover(
+                context.get_admin_context(),
+                self.volume_replicated,
+                valid_target_device_id)
+            rep_data = json.dumps({"location": HPELEFTHAND_API_URL2})
+            prov_location = '10.0.1.6:3260,1 iqn.1993-08.org.debian:01:222 0'
+            self.assertEqual({'provider_location': prov_location,
+                              'replication_driver_data': rep_data},
+                             return_model)
index 4a5d8429ba97f47d20bd5c167196049fa4d48839..57f44404ae600df0171fd2943b234064624c9a23 100644 (file)
@@ -147,9 +147,10 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         2.0.0 - Rebranded HP to HPE
         2.0.1 - Remove db access for consistency groups
         2.0.2 - Adds v2 managed replication support
+        2.0.3 - Adds v2 unmanaged replication support
     """
 
-    VERSION = "2.0.2"
+    VERSION = "2.0.3"
 
     device_stats = {}
 
@@ -176,21 +177,56 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         # blank is the only invalid character for cluster names
         # so we need to use it as a separator
         self.DRIVER_LOCATION = self.__class__.__name__ + ' %(cluster)s %(vip)s'
+        self._client_conf = {}
         self._replication_targets = []
         self._replication_enabled = False
 
-    def _login(self, timeout=None):
+    def _login(self, volume=None, timeout=None):
+        conf = self._get_lefthand_config(volume)
+        if conf:
+            self._client_conf['hpelefthand_username'] = (
+                conf['hpelefthand_username'])
+            self._client_conf['hpelefthand_password'] = (
+                conf['hpelefthand_password'])
+            self._client_conf['hpelefthand_clustername'] = (
+                conf['hpelefthand_clustername'])
+            self._client_conf['hpelefthand_api_url'] = (
+                conf['hpelefthand_api_url'])
+            self._client_conf['hpelefthand_ssh_port'] = (
+                conf['hpelefthand_ssh_port'])
+            self._client_conf['hpelefthand_iscsi_chap_enabled'] = (
+                conf['hpelefthand_iscsi_chap_enabled'])
+            self._client_conf['ssh_conn_timeout'] = conf['ssh_conn_timeout']
+            self._client_conf['san_private_key'] = conf['san_private_key']
+        else:
+            self._client_conf['hpelefthand_username'] = (
+                self.configuration.hpelefthand_username)
+            self._client_conf['hpelefthand_password'] = (
+                self.configuration.hpelefthand_password)
+            self._client_conf['hpelefthand_clustername'] = (
+                self.configuration.hpelefthand_clustername)
+            self._client_conf['hpelefthand_api_url'] = (
+                self.configuration.hpelefthand_api_url)
+            self._client_conf['hpelefthand_ssh_port'] = (
+                self.configuration.hpelefthand_ssh_port)
+            self._client_conf['hpelefthand_iscsi_chap_enabled'] = (
+                self.configuration.hpelefthand_iscsi_chap_enabled)
+            self._client_conf['ssh_conn_timeout'] = (
+                self.configuration.ssh_conn_timeout)
+            self._client_conf['san_private_key'] = (
+                self.configuration.san_private_key)
+
         client = self._create_client(timeout=timeout)
         try:
             if self.configuration.hpelefthand_debug:
                 client.debug_rest(True)
 
             client.login(
-                self.configuration.hpelefthand_username,
-                self.configuration.hpelefthand_password)
+                self._client_conf['hpelefthand_username'],
+                self._client_conf['hpelefthand_password'])
 
             cluster_info = client.getClusterByName(
-                self.configuration.hpelefthand_clustername)
+                self._client_conf['hpelefthand_clustername'])
             self.cluster_id = cluster_info['id']
             virtual_ips = cluster_info['virtualIPAddresses']
             self.cluster_vip = virtual_ips[0]['ipV4Address']
@@ -200,18 +236,18 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             if hpelefthandclient.version >= MIN_REP_CLIENT_VERSION:
                 # Extract IP address from API URL
                 ssh_ip = self._extract_ip_from_url(
-                    self.configuration.hpelefthand_api_url)
+                    self._client_conf['hpelefthand_api_url'])
                 known_hosts_file = CONF.ssh_hosts_key_file
                 policy = "AutoAddPolicy"
                 if CONF.strict_ssh_host_key_policy:
                     policy = "RejectPolicy"
                 client.setSSHOptions(
                     ssh_ip,
-                    self.configuration.hpelefthand_username,
-                    self.configuration.hpelefthand_password,
-                    port=self.configuration.hpelefthand_ssh_port,
-                    conn_timeout=self.configuration.ssh_conn_timeout,
-                    privatekey=self.configuration.san_private_key,
+                    self._client_conf['hpelefthand_username'],
+                    self._client_conf['hpelefthand_password'],
+                    port=self._client_conf['hpelefthand_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)
 
@@ -229,12 +265,12 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
     def _create_client(self, timeout=None):
         # Timeout is only supported in version 2.0.1 and greater of the
         # python-lefthandclient.
+        hpelefthand_api_url = self._client_conf['hpelefthand_api_url']
         if hpelefthandclient.version >= MIN_REP_CLIENT_VERSION:
             client = hpe_lh_client.HPELeftHandClient(
-                self.configuration.hpelefthand_api_url, timeout=timeout)
+                hpelefthand_api_url, timeout=timeout)
         else:
-            client = hpe_lh_client.HPELeftHandClient(
-                self.configuration.hpelefthand_api_url)
+            client = hpe_lh_client.HPELeftHandClient(hpelefthand_api_url)
         return client
 
     def _create_replication_client(self, remote_array):
@@ -312,6 +348,14 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         finally:
             self._logout(client)
 
+    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 get_version_string(self):
         return (_('REST %(proxy_ver)s hpelefthandclient %(rest_ver)s') % {
             'proxy_ver': self.VERSION,
@@ -319,7 +363,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def create_volume(self, volume):
         """Creates a volume."""
-        client = self._login()
+        client = self._login(volume)
         try:
             # get the extra specs of interest from this volume's volume type
             volume_extra_specs = self._get_volume_extra_specs(volume)
@@ -344,7 +388,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             if optional.get('isAdaptiveOptimizationEnabled'):
                 del optional['isAdaptiveOptimizationEnabled']
 
-            clusterName = self.configuration.hpelefthand_clustername
+            clusterName = self._client_conf['hpelefthand_clustername']
             optional['clusterName'] = clusterName
 
             volume_info = client.createVolume(
@@ -359,7 +403,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                self._do_volume_replication_setup(volume, client, optional)):
                 model_update['replication_status'] = 'enabled'
                 model_update['replication_driver_data'] = (json.dumps(
-                    {'location': self.configuration.hpelefthand_api_url}))
+                    {'location': self._client_conf['hpelefthand_api_url']}))
 
             return model_update
         except Exception as ex:
@@ -369,7 +413,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def delete_volume(self, volume):
         """Deletes a volume."""
-        client = self._login()
+        client = self._login(volume)
         # v2 replication check
         # If the volume type is replication enabled, we want to call our own
         # method of deconstructing the volume and its dependencies
@@ -389,7 +433,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def extend_volume(self, volume, new_size):
         """Extend the size of an existing volume."""
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(volume['name'])
 
@@ -538,7 +582,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def create_snapshot(self, snapshot):
         """Creates a snapshot."""
-        client = self._login()
+        client = self._login(snapshot['volume'])
         try:
             volume_info = client.getVolumeByName(snapshot['volume_name'])
 
@@ -553,7 +597,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def delete_snapshot(self, snapshot):
         """Deletes a snapshot."""
-        client = self._login()
+        client = self._login(snapshot['volume'])
         try:
             snap_info = client.getSnapshotByName(snapshot['name'])
             client.deleteSnapshot(snap_info['id'])
@@ -592,7 +636,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         data['storage_protocol'] = 'iSCSI'
         data['vendor_name'] = 'Hewlett Packard Enterprise'
         data['location_info'] = (self.DRIVER_LOCATION % {
-            'cluster': self.configuration.hpelefthand_clustername,
+            'cluster': self._client_conf['hpelefthand_clustername'],
             'vip': self.cluster_vip})
         data['thin_provisioning_support'] = True
         data['thick_provisioning_support'] = True
@@ -618,7 +662,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         total_volumes = 0
         provisioned_size = 0
         volumes = client.getVolumes(
-            cluster=self.configuration.hpelefthand_clustername,
+            cluster=self._client_conf['hpelefthand_clustername'],
             fields=['members[id]', 'members[clusterName]', 'members[size]'])
         if volumes:
             total_volumes = volumes['total']
@@ -645,7 +689,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         used from that host. HPE VSA requires a volume to be assigned
         to a server.
         """
-        client = self._login()
+        client = self._login(volume)
         try:
             server_info = self._create_server(connector, client)
             volume_info = client.getVolumeByName(volume['name'])
@@ -682,7 +726,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def terminate_connection(self, volume, connector, **kwargs):
         """Unassign the volume from the host."""
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(volume['name'])
             server_info = client.getServerByName(connector['host'])
@@ -707,7 +751,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def create_volume_from_snapshot(self, volume, snapshot):
         """Creates a volume from a snapshot."""
-        client = self._login()
+        client = self._login(volume)
         try:
             snap_info = client.getSnapshotByName(snapshot['name'])
             volume_info = client.cloneSnapshot(
@@ -721,7 +765,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                self._do_volume_replication_setup(volume, client)):
                 model_update['replication_status'] = 'enabled'
                 model_update['replication_driver_data'] = (json.dumps(
-                    {'location': self.configuration.hpelefthand_api_url}))
+                    {'location': self._client_conf['hpelefthand_api_url']}))
 
             return model_update
         except Exception as ex:
@@ -730,7 +774,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             self._logout(client)
 
     def create_cloned_volume(self, volume, src_vref):
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(src_vref['name'])
             clone_info = client.cloneVolume(volume['name'], volume_info['id'])
@@ -742,7 +786,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                self._do_volume_replication_setup(volume, client)):
                 model_update['replication_status'] = 'enabled'
                 model_update['replication_driver_data'] = (json.dumps(
-                    {'location': self.configuration.hpelefthand_api_url}))
+                    {'location': self._client_conf['hpelefthand_api_url']}))
 
             return model_update
         except Exception as ex:
@@ -802,7 +846,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
     def _create_server(self, connector, client):
         server_info = None
-        chap_enabled = self.configuration.hpelefthand_iscsi_chap_enabled
+        chap_enabled = self._client_conf['hpelefthand_iscsi_chap_enabled']
         try:
             server_info = client.getServerByName(connector['host'])
             chap_secret = server_info['chapTargetSecret']
@@ -857,7 +901,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                                                    'new_type': new_type,
                                                    'diff': diff,
                                                    'host': host})
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(volume['name'])
 
@@ -911,19 +955,18 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                      host['host'] is its name, and host['capabilities'] is a
                      dictionary of its reported capabilities.
         """
-        LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s, '
-                  'cluster=%(cluster)s', {
-                      'id': volume['id'],
-                      'host': host,
-                      'cluster': self.configuration.hpelefthand_clustername})
-
         false_ret = (False, None)
         if 'location_info' not in host['capabilities']:
             return false_ret
 
         host_location = host['capabilities']['location_info']
         (driver, cluster, vip) = host_location.split(' ')
-        client = self._login()
+        client = self._login(volume)
+        LOG.debug('enter: migrate_volume: id=%(id)s, host=%(host)s, '
+                  'cluster=%(cluster)s', {
+                      'id': volume['id'],
+                      'host': host,
+                      'cluster': self._client_conf['hpelefthand_clustername']})
         try:
             # get the cluster info, if it exists and compare
             cluster_info = client.getClusterByName(cluster)
@@ -1003,7 +1046,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             # volume isn't attached and can be updated
             original_name = CONF.volume_name_template % volume['id']
             current_name = CONF.volume_name_template % new_volume['id']
-            client = self._login()
+            client = self._login(volume)
             try:
                 volume_info = client.getVolumeByName(current_name)
                 volumeMods = {'name': original_name}
@@ -1038,7 +1081,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
         target_vol_name = self._get_existing_volume_ref_name(existing_ref)
 
         # Check for the existence of the virtual volume.
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(target_vol_name)
         except hpeexceptions.HTTPNotFound:
@@ -1136,7 +1179,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                 reason=reason)
 
         # Check for the existence of the virtual volume.
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(target_vol_name)
         except hpeexceptions.HTTPNotFound:
@@ -1156,7 +1199,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
 
         # Rename the volume's name to unm-* format so that it can be
         # easily found later.
-        client = self._login()
+        client = self._login(volume)
         try:
             volume_info = client.getVolumeByName(volume['name'])
             new_vol_name = 'unm-' + six.text_type(volume['id'])
@@ -1218,7 +1261,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             LOG.error(msg)
             model_update['replication_status'] = "error"
         else:
-            client = self._login()
+            client = self._login(volume)
             try:
                 if self._do_volume_replication_setup(volume, client):
                     model_update['replication_status'] = "enabled"
@@ -1238,7 +1281,7 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             model_update['replication_status'] = 'disabled'
             vol_name = volume['name']
 
-            client = self._login()
+            client = self._login(volume)
             try:
                 name = vol_name + self.REP_SCHEDULE_SUFFIX + "_Pri"
                 client.stopRemoteSnapshotSchedule(name)
@@ -1354,32 +1397,19 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             # as a failover can still occur, so we need out replication
             # devices to exist.
             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['target_device_id'] = (
-                    dev.get('target_device_id'))
-                remote_array['hpelefthand_api_url'] = (
-                    dev.get('hpelefthand_api_url'))
-                remote_array['hpelefthand_username'] = (
-                    dev.get('hpelefthand_username'))
-                remote_array['hpelefthand_password'] = (
-                    dev.get('hpelefthand_password'))
-                remote_array['hpelefthand_clustername'] = (
-                    dev.get('hpelefthand_clustername'))
+                remote_array = dict(dev.items())
+                # Override and set defaults for certain entries
+                remote_array['managed_backend_name'] = (
+                    dev.get('managed_backend_name'))
                 remote_array['hpelefthand_ssh_port'] = (
                     dev.get('hpelefthand_ssh_port', default_san_ssh_port))
                 remote_array['ssh_conn_timeout'] = (
                     dev.get('ssh_conn_timeout', default_ssh_conn_timeout))
                 remote_array['san_private_key'] = (
                     dev.get('san_private_key', default_san_private_key))
+                # Format hpe3par_iscsi_chap_enabled as a bool
+                remote_array['hpelefthand_iscsi_chap_enabled'] = (
+                    dev.get('hpelefthand_iscsi_chap_enabled') == 'True')
                 remote_array['cluster_id'] = None
                 remote_array['cluster_vip'] = None
                 array_name = remote_array['target_device_id']
@@ -1432,10 +1462,14 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
                 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 = ['hpelefthand_api_url', 'hpelefthand_username',
+                          'hpelefthand_password', 'target_device_id',
+                          'hpelefthand_clustername']
+        try:
+            self.check_replication_flags(target, required_flags)
+            return True
+        except Exception:
+            return False
 
     def _is_replication_configured_correct(self):
         rep_flag = True
@@ -1466,6 +1500,22 @@ class HPELeftHandISCSIDriver(driver.ISCSIDriver):
             exists = False
         return exists
 
+    def _get_lefthand_config(self, volume):
+        conf = None
+        if volume:
+            rep_location = None
+            rep_data = volume.get('replication_driver_data')
+            if rep_data:
+                rep_data = json.loads(rep_data)
+                rep_location = rep_data.get('location')
+            if rep_location:
+                for target in self._replication_targets:
+                    if target['hpelefthand_api_url'] == rep_location:
+                        conf = target
+                        break
+
+        return conf
+
     def _do_volume_replication_setup(self, volume, client, optional=None):
         """This function will do or ensure the following:
 
diff --git a/releasenotes/notes/lefthand-v2-replication-unmanaged-315c2888423f0f3b.yaml b/releasenotes/notes/lefthand-v2-replication-unmanaged-315c2888423f0f3b.yaml
new file mode 100644 (file)
index 0000000..3e19a14
--- /dev/null
@@ -0,0 +1,3 @@
+---
+features:
+  - Added unmanaged v2 replication support to the HPE LeftHand driver.