From 40eaa89a9001edda3e1146831b26aaf7ded64c4a Mon Sep 17 00:00:00 2001 From: Alyson Rosa Date: Mon, 20 Aug 2018 11:23:22 -0300 Subject: [PATCH] Fix IPv6 for Cinder NetApp ONTAP drivers NetApp ONTAP driver currently have some issues with IPv6: - The URL is not properly formatted when using in management path - NFS driver breaks when handling IPv6 addresses - iSCSI driver creates a URL that is not properly formatted when using IPv6 addresses This patch fixes all issues related to IPv6 on NetApp ONTAP drivers. Closes-bug: 1788419 Closes-bug: 1788460 Change-Id: I6eeca47997c7134d6604874bea48eab7cab6c1a2 (cherry picked from commit 925376527e356ed14320716d0f72b48b13b8aecb) (cherry picked from commit b290b49183200fb9dbc4fff655f00cfaf3c8b67a) --- .../netapp/dataontap/client/test_api.py | 20 ++++++ .../volume/drivers/netapp/dataontap/fakes.py | 4 +- .../drivers/netapp/dataontap/test_nfs_base.py | 69 +++++++------------ .../netapp/dataontap/test_nfs_cmode.py | 14 ++-- .../tests/unit/volume/drivers/netapp/fakes.py | 13 +++- .../unit/volume/drivers/netapp/test_utils.py | 38 +++++++++- .../drivers/netapp/dataontap/client/api.py | 8 ++- .../drivers/netapp/dataontap/nfs_base.py | 29 ++++---- .../drivers/netapp/dataontap/nfs_cmode.py | 19 ++--- cinder/volume/drivers/netapp/utils.py | 28 ++++++++ ...p-driver-cinder-ipv6-c3c4d0d6a7d0de91.yaml | 4 ++ 11 files changed, 163 insertions(+), 83 deletions(-) create mode 100644 releasenotes/notes/bugfix-netapp-driver-cinder-ipv6-c3c4d0d6a7d0de91.yaml diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py index c1f104716f0..e85fc371b76 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/client/test_api.py @@ -21,6 +21,7 @@ Tests for NetApp API layer import ddt from lxml import etree import mock +from oslo_utils import netutils import paramiko import six from six.moves import urllib @@ -238,6 +239,25 @@ class NetAppApiServerTests(test.TestCase): self.root.send_http_request(na_element) + @ddt.data('192.168.1.0', '127.0.0.1', '0.0.0.0', + '::ffff:8', 'fdf8:f53b:82e4::53', '2001::1', + 'fe80::200::abcd', '2001:0000:4136:e378:8000:63bf:3fff:fdd2') + def test__get_url(self, host): + port = '80' + root = netapp_api.NaServer(host, port=port) + + protocol = root.TRANSPORT_TYPE_HTTP + url = root.URL_FILER + + if netutils.is_valid_ipv6(host): + host = netutils.escape_ipv6(host) + + result = '%s://%s:%s/%s' % (protocol, host, port, url) + + url = root._get_url() + + self.assertEqual(result, url) + class NetAppApiElementTransTests(test.TestCase): """Test case for NetApp API element translations.""" diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py index 2c5219cedbc..48f303a038a 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/fakes.py @@ -30,8 +30,10 @@ HOST_NAME = 'fake.host.name' BACKEND_NAME = 'fake_backend_name' POOL_NAME = 'aggr1' SHARE_IP = '192.168.99.24' +IPV6_ADDRESS = 'fe80::6e40:8ff:fe8a:130' EXPORT_PATH = '/fake/export/path' NFS_SHARE = '%s:%s' % (SHARE_IP, EXPORT_PATH) +NFS_SHARE_IPV6 = '[%s]:%s' % (IPV6_ADDRESS, EXPORT_PATH) HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, POOL_NAME) NFS_HOST_STRING = '%s@%s#%s' % (HOST_NAME, BACKEND_NAME, NFS_SHARE) AGGREGATE = 'aggr1' @@ -242,9 +244,7 @@ ISCSI_TARGET_DETAILS_LIST = [ ] IPV4_ADDRESS = '192.168.14.2' -IPV6_ADDRESS = 'fe80::6e40:8ff:fe8a:130' NFS_SHARE_IPV4 = IPV4_ADDRESS + ':' + EXPORT_PATH -NFS_SHARE_IPV6 = IPV6_ADDRESS + ':' + EXPORT_PATH RESERVED_PERCENTAGE = 7 MAX_OVER_SUBSCRIPTION_RATIO = 19.0 diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py index 3cca4d986c8..e2de9d9e120 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_base.py @@ -323,18 +323,16 @@ class NetAppNfsDriverTestCase(test.TestCase): self.driver._delete_file.assert_called_once_with(snapshot.volume_id, snapshot.name) - def test__get_volume_location(self): + @ddt.data(fake.NFS_SHARE, fake.NFS_SHARE_IPV6) + def test__get_volume_location(self, provider): volume_id = fake.VOLUME_ID - self.mock_object(self.driver, '_get_host_ip', - return_value='168.124.10.12') - self.mock_object(self.driver, '_get_export_path', - return_value='/fake_mount_path') + + self.mock_object(self.driver, '_get_provider_location', + return_value=provider) retval = self.driver._get_volume_location(volume_id) - self.assertEqual('168.124.10.12:/fake_mount_path', retval) - self.driver._get_host_ip.assert_called_once_with(volume_id) - self.driver._get_export_path.assert_called_once_with(volume_id) + self.assertEqual(provider, retval) def test__clone_backing_file_for_volume(self): self.assertRaises(NotImplementedError, @@ -507,34 +505,35 @@ class NetAppNfsDriverTestCase(test.TestCase): self.assertEqual(0, mock_delete.call_count) - def test_get_export_ip_path_volume_id_provided(self): - mock_get_host_ip = self.mock_object(self.driver, '_get_host_ip') - mock_get_host_ip.return_value = fake.IPV4_ADDRESS + @ddt.data((fake.NFS_SHARE, fake.SHARE_IP), + (fake.NFS_SHARE_IPV6, fake.IPV6_ADDRESS)) + @ddt.unpack + def test_get_export_ip_path_volume_id_provided(self, provider_location, + ip): + mock_get_host_ip = self.mock_object(self.driver, + '_get_provider_location') + mock_get_host_ip.return_value = provider_location - mock_get_export_path = self.mock_object( - self.driver, '_get_export_path') - mock_get_export_path.return_value = fake.EXPORT_PATH - - expected = (fake.IPV4_ADDRESS, fake.EXPORT_PATH) + expected = (ip, fake.EXPORT_PATH) result = self.driver._get_export_ip_path(fake.VOLUME_ID) self.assertEqual(expected, result) - def test_get_export_ip_path_share_provided(self): - expected = (fake.SHARE_IP, fake.EXPORT_PATH) + @ddt.data((fake.NFS_SHARE, fake.SHARE_IP, fake.EXPORT_PATH), + (fake.NFS_SHARE_IPV6, fake.IPV6_ADDRESS, fake.EXPORT_PATH)) + @ddt.unpack + def test_get_export_ip_path_share_provided(self, share, ip, path): + expected = (ip, path) - result = self.driver._get_export_ip_path(share=fake.NFS_SHARE) + result = self.driver._get_export_ip_path(share=share) self.assertEqual(expected, result) def test_get_export_ip_path_volume_id_and_share_provided(self): - mock_get_host_ip = self.mock_object(self.driver, '_get_host_ip') - mock_get_host_ip.return_value = fake.IPV4_ADDRESS - - mock_get_export_path = self.mock_object( - self.driver, '_get_export_path') - mock_get_export_path.return_value = fake.EXPORT_PATH + mock_get_host_ip = self.mock_object(self.driver, + '_get_provider_location') + mock_get_host_ip.return_value = fake.NFS_SHARE_IPV4 expected = (fake.IPV4_ADDRESS, fake.EXPORT_PATH) @@ -547,26 +546,6 @@ class NetAppNfsDriverTestCase(test.TestCase): self.assertRaises(exception.InvalidInput, self.driver._get_export_ip_path) - def test_get_host_ip(self): - mock_get_provider_location = self.mock_object( - self.driver, '_get_provider_location') - mock_get_provider_location.return_value = fake.NFS_SHARE - expected = fake.SHARE_IP - - result = self.driver._get_host_ip(fake.VOLUME_ID) - - self.assertEqual(expected, result) - - def test_get_export_path(self): - mock_get_provider_location = self.mock_object( - self.driver, '_get_provider_location') - mock_get_provider_location.return_value = fake.NFS_SHARE - expected = fake.EXPORT_PATH - - result = self.driver._get_export_path(fake.VOLUME_ID) - - self.assertEqual(expected, result) - def test_construct_image_url_loc(self): img_loc = fake.FAKE_IMAGE_LOCATION diff --git a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py index 9cadf9bce75..e2fb005ed67 100644 --- a/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py +++ b/cinder/tests/unit/volume/drivers/netapp/dataontap/test_nfs_cmode.py @@ -1127,18 +1127,20 @@ class NetAppCmodeNfsDriverTestCase(test.TestCase): fake.EXPORT_PATH, fake.IMAGE_FILE_ID, fake.VOLUME['name'], fake.VSERVER_NAME, dest_exists=True) - def test_get_source_ip_and_path(self): + @ddt.data((fake.NFS_SHARE, fake.SHARE_IP), + (fake.NFS_SHARE_IPV6, fake.IPV6_ADDRESS)) + @ddt.unpack + def test_get_source_ip_and_path(self, share, ip): self.driver._get_ip_verify_on_cluster = mock.Mock( - return_value=fake.SHARE_IP) + return_value=ip) src_ip, src_path = self.driver._get_source_ip_and_path( - fake.NFS_SHARE, fake.IMAGE_FILE_ID) + share, fake.IMAGE_FILE_ID) - self.assertEqual(fake.SHARE_IP, src_ip) + self.assertEqual(ip, src_ip) assert_path = fake.EXPORT_PATH + '/' + fake.IMAGE_FILE_ID self.assertEqual(assert_path, src_path) - self.driver._get_ip_verify_on_cluster.assert_called_once_with( - fake.SHARE_IP) + self.driver._get_ip_verify_on_cluster.assert_called_once_with(ip) def test_get_destination_ip_and_path(self): self.driver._get_ip_verify_on_cluster = mock.Mock( diff --git a/cinder/tests/unit/volume/drivers/netapp/fakes.py b/cinder/tests/unit/volume/drivers/netapp/fakes.py index ebbead0aa99..6f74f55ae21 100644 --- a/cinder/tests/unit/volume/drivers/netapp/fakes.py +++ b/cinder/tests/unit/volume/drivers/netapp/fakes.py @@ -24,17 +24,19 @@ ISCSI_FAKE_LUN_ID = 1 ISCSI_FAKE_IQN = 'iqn.1993-08.org.debian:01:10' -ISCSI_FAKE_ADDRESS = '10.63.165.216' +ISCSI_FAKE_ADDRESS_IPV4 = '10.63.165.216' +ISCSI_FAKE_ADDRESS_IPV6 = 'fe80::72a4:a152:aad9:30d9' ISCSI_FAKE_PORT = '2232' ISCSI_FAKE_VOLUME = {'id': 'fake_id'} ISCSI_FAKE_TARGET = {} -ISCSI_FAKE_TARGET['address'] = ISCSI_FAKE_ADDRESS +ISCSI_FAKE_TARGET['address'] = ISCSI_FAKE_ADDRESS_IPV4 ISCSI_FAKE_TARGET['port'] = ISCSI_FAKE_PORT ISCSI_FAKE_VOLUME = {'id': 'fake_id', 'provider_auth': 'None stack password'} +ISCSI_FAKE_VOLUME_NO_AUTH = {'id': 'fake_id', 'provider_auth': ''} FC_ISCSI_TARGET_INFO_DICT = {'target_discovered': False, 'target_portal': '10.63.165.216:2232', @@ -44,6 +46,13 @@ FC_ISCSI_TARGET_INFO_DICT = {'target_discovered': False, 'auth_method': 'None', 'auth_username': 'stack', 'auth_password': 'password'} +FC_ISCSI_TARGET_INFO_DICT_IPV6 = {'target_discovered': False, + 'target_portal': + '[fe80::72a4:a152:aad9:30d9]:2232', + 'target_iqn': ISCSI_FAKE_IQN, + 'target_lun': ISCSI_FAKE_LUN_ID, + 'volume_id': ISCSI_FAKE_VOLUME['id']} + VOLUME_NAME = 'fake_volume_name' VOLUME_ID = 'fake_volume_id' VOLUME_TYPE_ID = 'fake_volume_type_id' diff --git a/cinder/tests/unit/volume/drivers/netapp/test_utils.py b/cinder/tests/unit/volume/drivers/netapp/test_utils.py index 40c6534a5b1..0688814e29d 100644 --- a/cinder/tests/unit/volume/drivers/netapp/test_utils.py +++ b/cinder/tests/unit/volume/drivers/netapp/test_utils.py @@ -35,6 +35,7 @@ from cinder.volume import qos_specs from cinder.volume import volume_types +@ddt.ddt class NetAppDriverUtilsTestCase(test.TestCase): @mock.patch.object(na_utils, 'LOG', mock.Mock()) @@ -118,7 +119,7 @@ class NetAppDriverUtilsTestCase(test.TestCase): actual_properties = na_utils.get_iscsi_connection_properties( fake.ISCSI_FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME, - fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS, + fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS_IPV4, fake.ISCSI_FAKE_PORT) actual_properties_mapped = actual_properties['data'] @@ -131,7 +132,7 @@ class NetAppDriverUtilsTestCase(test.TestCase): actual_properties = na_utils.get_iscsi_connection_properties( FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME, fake.ISCSI_FAKE_IQN, - fake.ISCSI_FAKE_ADDRESS, fake.ISCSI_FAKE_PORT) + fake.ISCSI_FAKE_ADDRESS_IPV4, fake.ISCSI_FAKE_PORT) actual_properties_mapped = actual_properties['data'] @@ -142,9 +143,17 @@ class NetAppDriverUtilsTestCase(test.TestCase): self.assertRaises(TypeError, na_utils.get_iscsi_connection_properties, FAKE_LUN_ID, fake.ISCSI_FAKE_VOLUME, - fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS, + fake.ISCSI_FAKE_IQN, fake.ISCSI_FAKE_ADDRESS_IPV4, fake.ISCSI_FAKE_PORT) + def test_iscsi_connection_properties_ipv6(self): + actual_properties = na_utils.get_iscsi_connection_properties( + '1', fake.ISCSI_FAKE_VOLUME_NO_AUTH, fake.ISCSI_FAKE_IQN, + fake.ISCSI_FAKE_ADDRESS_IPV6, fake.ISCSI_FAKE_PORT) + + self.assertDictEqual(actual_properties['data'], + fake.FC_ISCSI_TARGET_INFO_DICT_IPV6) + def test_get_volume_extra_specs(self): fake_extra_specs = {'fake_key': 'fake_value'} fake_volume_type = {'extra_specs': fake_extra_specs} @@ -519,6 +528,29 @@ class NetAppDriverUtilsTestCase(test.TestCase): self.assertIsNone(result) + @ddt.data(("192.168.99.24:/fake/export/path", "192.168.99.24", + "/fake/export/path"), + ("127.0.0.1:/", "127.0.0.1", "/"), + ("[f180::30d9]:/path_to-export/3.1/this folder", "f180::30d9", + "/path_to-export/3.1/this folder"), + ("[::]:/", "::", "/"), + ("[2001:db8::1]:/fake_export", "2001:db8::1", "/fake_export")) + @ddt.unpack + def test_get_export_host_junction_path(self, share, host, junction_path): + result_host, result_path = na_utils.get_export_host_junction_path( + share) + + self.assertEqual(host, result_host) + self.assertEqual(junction_path, result_path) + + @ddt.data("192.14.21.0/wrong_export", "192.14.21.0:8080:/wrong_export" + "2001:db8::1:/wrong_export", + "[2001:db8::1:/wrong_export", "2001:db8::1]:/wrong_export") + def test_get_export_host_junction_path_with_invalid_exports(self, share): + self.assertRaises(exception.NetAppDriverException, + na_utils.get_export_host_junction_path, + share) + class OpenStackInfoTestCase(test.TestCase): diff --git a/cinder/volume/drivers/netapp/dataontap/client/api.py b/cinder/volume/drivers/netapp/dataontap/client/api.py index aae2c3e007e..d139b5b3882 100644 --- a/cinder/volume/drivers/netapp/dataontap/client/api.py +++ b/cinder/volume/drivers/netapp/dataontap/client/api.py @@ -26,6 +26,7 @@ from eventlet import semaphore from lxml import etree from oslo_log import log as logging +from oslo_utils import netutils import random import six from six.moves import urllib @@ -270,7 +271,12 @@ class NaServer(object): return processed_response.get_child_by_name('results') def _get_url(self): - return '%s://%s:%s/%s' % (self._protocol, self._host, self._port, + host = self._host + + if netutils.is_valid_ipv6(host): + host = netutils.escape_ipv6(host) + + return '%s://%s:%s/%s' % (self._protocol, host, self._port, self._url) def _build_opener(self): diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_base.py b/cinder/volume/drivers/netapp/dataontap/nfs_base.py index 7025a68c0f8..a5727a30c0e 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_base.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_base.py @@ -31,6 +31,7 @@ import time from oslo_concurrency import processutils from oslo_config import cfg from oslo_log import log as logging +from oslo_utils import netutils from oslo_utils import units import six from six.moves import urllib @@ -279,8 +280,13 @@ class NetAppNfsDriver(driver.ManageableVD, def _get_volume_location(self, volume_id): """Returns NFS mount address as :.""" - nfs_server_ip = self._get_host_ip(volume_id) - export_path = self._get_export_path(volume_id) + provider_location = self._get_provider_location(volume_id) + nfs_server_ip, export_path = na_utils.get_export_host_junction_path( + provider_location) + + if netutils.is_valid_ipv6(nfs_server_ip): + nfs_server_ip = netutils.escape_ipv6(nfs_server_ip) + return nfs_server_ip + ':' + export_path def _clone_backing_file_for_volume(self, volume_name, clone_name, @@ -303,14 +309,6 @@ class NetAppNfsDriver(driver.ManageableVD, volume = self.db.volume_get(self._context, volume_id) return volume.provider_location - def _get_host_ip(self, volume_id): - """Returns IP address for the given volume.""" - return self._get_provider_location(volume_id).rsplit(':')[0] - - def _get_export_path(self, volume_id): - """Returns NFS export path for the given volume.""" - return self._get_provider_location(volume_id).rsplit(':')[1] - def _volume_not_present(self, nfs_mount, volume_name): """Check if volume exists.""" try: @@ -751,7 +749,7 @@ class NetAppNfsDriver(driver.ManageableVD, ip = na_utils.resolve_hostname(host) share_candidates = [] for sh in self._mounted_shares: - sh_exp = sh.split(':')[1] + sh_exp = sh.split(':')[-1] if sh_exp == dir: share_candidates.append(sh) if share_candidates: @@ -866,11 +864,12 @@ class NetAppNfsDriver(driver.ManageableVD, """ if volume_id: - host_ip = self._get_host_ip(volume_id) - export_path = self._get_export_path(volume_id) + provider_location = self._get_provider_location(volume_id) + host_ip, export_path = na_utils.get_export_host_junction_path( + provider_location) elif share: - host_ip = share.split(':')[0] - export_path = share.split(':')[1] + host_ip, export_path = na_utils.get_export_host_junction_path( + share) else: raise exception.InvalidInput( 'A volume ID or share was not specified.') diff --git a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py index 5b06ec54389..d6c2d295829 100644 --- a/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py +++ b/cinder/volume/drivers/netapp/dataontap/nfs_cmode.py @@ -188,7 +188,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, return target_path = '%s' % (volume['name']) share = volume_utils.extract_host(volume['host'], level='pool') - export_path = share.split(':')[1] + __, export_path = na_utils.get_export_host_junction_path(share) flex_vol_name = self.zapi_client.get_vol_by_junc_vserver(self.vserver, export_path) self.zapi_client.file_assign_qos(flex_vol_name, @@ -325,9 +325,8 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, vserver_addresses = self.zapi_client.get_operational_lif_addresses() for share in self._mounted_shares: + host, junction_path = na_utils.get_export_host_junction_path(share) - host = share.split(':')[0] - junction_path = share.split(':')[1] address = na_utils.resolve_hostname(host) if address not in vserver_addresses: @@ -365,7 +364,7 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, ip_vserver = self._get_vserver_for_ip(ip) if ip_vserver and shares: for share in shares: - ip_sh = share.split(':')[0] + ip_sh, __ = na_utils.get_export_host_junction_path(share) sh_vserver = self._get_vserver_for_ip(ip_sh) if sh_vserver == ip_vserver: LOG.debug('Share match found for ip %s', ip) @@ -543,15 +542,17 @@ class NetAppCmodeNfsDriver(nfs_base.NetAppNfsDriver, volume['id']) def _get_source_ip_and_path(self, nfs_share, file_name): - src_ip = self._get_ip_verify_on_cluster(nfs_share.split(':')[0]) - src_path = os.path.join(nfs_share.split(':')[1], file_name) + host, share_path = na_utils.get_export_host_junction_path(nfs_share) + src_ip = self._get_ip_verify_on_cluster(host) + src_path = os.path.join(share_path, file_name) + return src_ip, src_path def _get_destination_ip_and_path(self, volume): share = volume_utils.extract_host(volume['host'], level='pool') - share_ip_and_path = share.split(":") - dest_ip = self._get_ip_verify_on_cluster(share_ip_and_path[0]) - dest_path = os.path.join(share_ip_and_path[1], volume['name']) + share_ip, share_path = na_utils.get_export_host_junction_path(share) + dest_ip = self._get_ip_verify_on_cluster(share_ip) + dest_path = os.path.join(share_path, volume['name']) return dest_ip, dest_path diff --git a/cinder/volume/drivers/netapp/utils.py b/cinder/volume/drivers/netapp/utils.py index 38be5df238b..da148079857 100644 --- a/cinder/volume/drivers/netapp/utils.py +++ b/cinder/volume/drivers/netapp/utils.py @@ -30,6 +30,7 @@ import socket from oslo_concurrency import processutils as putils from oslo_log import log as logging +from oslo_utils import netutils import six from cinder import context @@ -154,6 +155,9 @@ def log_extra_spec_warnings(extra_specs): def get_iscsi_connection_properties(lun_id, volume, iqn, address, port): + # literal ipv6 address + if netutils.is_valid_ipv6(address): + address = netutils.escape_ipv6(address) properties = {} properties['target_discovered'] = False @@ -341,6 +345,30 @@ def get_legacy_qos_policy(extra_specs): return dict(policy_name=external_policy_name) +def get_export_host_junction_path(share): + if '[' in share and ']' in share: + try: + # ipv6 + host = re.search('\[(.*)\]', share).group(1) + junction_path = share.split(':')[-1] + except AttributeError: + raise exception.NetAppDriverException(_("Share '%s' is " + "not in a valid " + "format.") % share) + else: + # ipv4 + path = share.split(':') + if len(path) == 2: + host = path[0] + junction_path = path[1] + else: + raise exception.NetAppDriverException(_("Share '%s' is " + "not in a valid " + "format.") % share) + + return host, junction_path + + class hashabledict(dict): """A hashable dictionary that is comparable (i.e. in unit tests, etc.)""" def __hash__(self): diff --git a/releasenotes/notes/bugfix-netapp-driver-cinder-ipv6-c3c4d0d6a7d0de91.yaml b/releasenotes/notes/bugfix-netapp-driver-cinder-ipv6-c3c4d0d6a7d0de91.yaml new file mode 100644 index 00000000000..c93bde76150 --- /dev/null +++ b/releasenotes/notes/bugfix-netapp-driver-cinder-ipv6-c3c4d0d6a7d0de91.yaml @@ -0,0 +1,4 @@ +--- +fixes: + - Fixed support for IPv6 on management and data paths for NFS, iSCSI + and FCP NetApp ONTAP drivers.