From aba098e575ba9da2715980f2f197aa67752684cb Mon Sep 17 00:00:00 2001 From: Alex O'Rourke Date: Mon, 23 Nov 2015 10:43:13 -0800 Subject: [PATCH] LeftHand: Implement v2 replication (unmanaged) 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 | 122 +++++++++++- .../volume/drivers/hpe/hpe_lefthand_iscsi.py | 186 +++++++++++------- ...eplication-unmanaged-315c2888423f0f3b.yaml | 3 + 3 files changed, 242 insertions(+), 69 deletions(-) create mode 100644 releasenotes/notes/lefthand-v2-replication-unmanaged-315c2888423f0f3b.yaml diff --git a/cinder/tests/unit/test_hpelefthand.py b/cinder/tests/unit/test_hpelefthand.py index df1f9f7bf..5f6c08b7b 100644 --- a/cinder/tests/unit/test_hpelefthand.py +++ b/cinder/tests/unit/test_hpelefthand.py @@ -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': ' 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': ' 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) diff --git a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py index 4a5d8429b..57f44404a 100644 --- a/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py +++ b/cinder/volume/drivers/hpe/hpe_lefthand_iscsi.py @@ -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 index 000000000..3e19a14b1 --- /dev/null +++ b/releasenotes/notes/lefthand-v2-replication-unmanaged-315c2888423f0f3b.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added unmanaged v2 replication support to the HPE LeftHand driver. -- 2.45.2