diff --git a/manila/share/drivers/netapp/common.py b/manila/share/drivers/netapp/common.py index 0319525346..a0064ec3dd 100644 --- a/manila/share/drivers/netapp/common.py +++ b/manila/share/drivers/netapp/common.py @@ -126,4 +126,5 @@ class NetAppDriver(object): driver = importutils.import_object(driver_loc, *args, **kwargs) LOG.info('NetApp driver of family %(storage_family)s and mode ' '%(driver_mode)s loaded.', fmt) + driver.ipv6_implemented = True return driver diff --git a/manila/share/drivers/netapp/dataontap/client/client_cmode.py b/manila/share/drivers/netapp/dataontap/client/client_cmode.py index 32443c2c00..a416157ffb 100644 --- a/manila/share/drivers/netapp/dataontap/client/client_cmode.py +++ b/manila/share/drivers/netapp/dataontap/client/client_cmode.py @@ -1269,7 +1269,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): """Enables NFS on Vserver.""" self.send_request('nfs-enable') self._enable_nfs_protocols(versions) - self._create_default_nfs_export_rule() + self._create_default_nfs_export_rules() @na_utils.trace def _enable_nfs_protocols(self, versions): @@ -1286,7 +1286,7 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): self.send_request('nfs-service-modify', nfs_service_modify_args) @na_utils.trace - def _create_default_nfs_export_rule(self): + def _create_default_nfs_export_rules(self): """Create the default export rule for the NFS service.""" export_rule_create_args = { @@ -1300,6 +1300,8 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): }, } self.send_request('export-rule-create', export_rule_create_args) + export_rule_create_args['client-match'] = '::/0' + self.send_request('export-rule-create', export_rule_create_args) @na_utils.trace def configure_ldap(self, security_service): @@ -3652,3 +3654,15 @@ class NetAppCmodeClient(client_base.NetAppBaseClient): msg = 'Could not delete QoS policy groups. Details: %(ex)s' msg_args = {'ex': ex} LOG.debug(msg, msg_args) + + @na_utils.trace + def get_net_options(self): + result = self.send_request('net-options-get', None, False) + options = result.get_child_by_name('net-options') + ipv6_enabled = False + ipv6_info = options.get_child_by_name('ipv6-options-info') + if ipv6_info: + ipv6_enabled = ipv6_info.get_child_content('enabled') == 'true' + return { + 'ipv6-enabled': ipv6_enabled, + } diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py index 293273b503..03a07ba218 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_multi_svm.py @@ -234,3 +234,6 @@ class NetAppCmodeMultiSvmShareDriver(driver.ShareDriver): snapshot_dict, fallback_create, share_server) + + def get_configured_ip_version(self): + return self.library.get_configured_ip_version() diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py index 356962193f..46e8392ae1 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/drv_single_svm.py @@ -250,3 +250,6 @@ class NetAppCmodeSingleSvmShareDriver(driver.ShareDriver): snapshot_dict, fallback_create, share_server) + + def get_configured_ip_version(self): + return self.library.get_configured_ip_version() diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py index 2f715b0f18..f9bd64004d 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_multi_svm.py @@ -384,3 +384,10 @@ class NetAppCmodeMultiSVMFileStorageLibrary( self._client.delete_vlan(node, port, vlan) except exception.NetAppException: LOG.exception("Deleting Vserver VLAN failed.") + + def get_configured_ip_version(self): + versions = [4] + options = self._client.get_net_options() + if options['ipv6-enabled']: + versions.append(6) + return versions diff --git a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_single_svm.py b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_single_svm.py index aeaf25ad24..e5acb5a126 100644 --- a/manila/share/drivers/netapp/dataontap/cluster_mode/lib_single_svm.py +++ b/manila/share/drivers/netapp/dataontap/cluster_mode/lib_single_svm.py @@ -146,3 +146,22 @@ class NetAppCmodeSingleSVMFileStorageLibrary( def get_admin_network_allocations_number(self): """Get number of network allocations for creating admin LIFs.""" return 0 + + @na_utils.trace + def get_configured_ip_version(self): + ipv4 = False + ipv6 = False + vserver_client = self._get_api_client(vserver=self._vserver) + interfaces = vserver_client.get_network_interfaces() + for interface in interfaces: + address = interface['address'] + if ':' in address: + ipv6 = True + else: + ipv4 = True + versions = [] + if ipv4: + versions.append(4) + if ipv6: + versions.append(6) + return versions diff --git a/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py b/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py index 81b855697f..5bb67ea644 100644 --- a/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py +++ b/manila/share/drivers/netapp/dataontap/protocols/nfs_cmode.py @@ -17,7 +17,6 @@ NetApp cDOT NFS protocol helper class. import uuid -import netaddr from oslo_log import log import six @@ -33,6 +32,13 @@ LOG = log.getLogger(__name__) class NetAppCmodeNFSHelper(base.NetAppBaseHelper): """NetApp cDOT NFS protocol helper class.""" + @staticmethod + def _escaped_address(address): + if ':' in address: + return '[%s]' % address + else: + return address + @na_utils.trace def create_share(self, share, share_name, clear_current_export_policy=True): @@ -45,7 +51,8 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper): # Return a callback that may be used for generating export paths # for this share. return (lambda export_address, export_path=export_path: - ':'.join([export_address, export_path])) + ':'.join([self._escaped_address(export_address), + export_path])) @na_utils.trace @base.access_rules_synchronized @@ -67,7 +74,7 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper): # Sort rules by ascending network size new_rules = {rule['access_to']: rule['access_level'] for rule in rules} - addresses = self._get_sorted_access_rule_addresses(new_rules) + addresses = sorted(new_rules, reverse=True) # Ensure current export policy has the name we expect self._ensure_export_policy(share, share_name) @@ -123,22 +130,6 @@ class NetAppCmodeNFSHelper(base.NetAppBaseHelper): if rule['access_level'] not in constants.ACCESS_LEVELS: raise exception.InvalidShareAccessLevel(level=rule['access_level']) - @na_utils.trace - def _get_sorted_access_rule_addresses(self, rules): - """Given a dict of access rules, sort by increasing network size.""" - - networks = sorted([self._get_network_object_from_rule(rule) - for rule in rules], reverse=True) - - return [six.text_type(network) for network in networks] - - def _get_network_object_from_rule(self, rule): - """Get most appropriate netaddr object for address or network rule.""" - try: - return netaddr.IPAddress(rule) - except ValueError: - return netaddr.IPNetwork(rule) - @na_utils.trace def get_target(self, share): """Returns ID of target OnTap device based on export location.""" diff --git a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py index d2ece0b1d7..ee1d2af90d 100644 --- a/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/client/test_client_cmode.py @@ -2176,14 +2176,14 @@ class NetAppClientCmodeTestCase(test.TestCase): self.mock_object(self.client, 'send_request') self.mock_object(self.client, '_enable_nfs_protocols') - self.mock_object(self.client, '_create_default_nfs_export_rule') + self.mock_object(self.client, '_create_default_nfs_export_rules') self.client.enable_nfs(fake.NFS_VERSIONS) self.client.send_request.assert_called_once_with('nfs-enable') self.client._enable_nfs_protocols.assert_called_once_with( fake.NFS_VERSIONS) - self.client._create_default_nfs_export_rule.assert_called_once_with() + self.client._create_default_nfs_export_rules.assert_called_once_with() @ddt.data((True, True, True), (True, False, False), (False, True, True)) @ddt.unpack @@ -2209,11 +2209,17 @@ class NetAppClientCmodeTestCase(test.TestCase): self.client.send_request.assert_called_once_with( 'nfs-service-modify', nfs_service_modify_args) - def test_create_default_nfs_export_rule(self): + def test_create_default_nfs_export_rules(self): - self.mock_object(self.client, 'send_request') + class CopyingMock(mock.Mock): + def __call__(self, *args, **kwargs): + args = copy.deepcopy(args) + kwargs = copy.deepcopy(kwargs) + return super(CopyingMock, self).__call__(*args, **kwargs) - self.client._create_default_nfs_export_rule() + self.mock_object(self.client, 'send_request', CopyingMock()) + + self.client._create_default_nfs_export_rules() export_rule_create_args = { 'client-match': '0.0.0.0/0', @@ -2225,8 +2231,11 @@ class NetAppClientCmodeTestCase(test.TestCase): 'security-flavor': 'never' } } - self.client.send_request.assert_called_once_with( - 'export-rule-create', export_rule_create_args) + export_rule_create_args2 = export_rule_create_args.copy() + export_rule_create_args2['client-match'] = '::/0' + self.client.send_request.assert_has_calls([ + mock.call('export-rule-create', export_rule_create_args), + mock.call('export-rule-create', export_rule_create_args2)]) def test_configure_ldap(self): diff --git a/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py b/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py index ba7e3c5071..a533fe318d 100644 --- a/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py +++ b/manila/tests/share/drivers/netapp/dataontap/protocols/test_nfs_cmode.py @@ -20,7 +20,6 @@ import uuid import ddt import mock -import netaddr from manila import exception from manila.share.drivers.netapp.dataontap.protocols import nfs_cmode @@ -40,6 +39,11 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase): self.helper = nfs_cmode.NetAppCmodeNFSHelper() self.helper.set_client(self.mock_client) + @ddt.data(('1.2.3.4', '1.2.3.4'), ('fc00::1', '[fc00::1]')) + @ddt.unpack + def test__escaped_address(self, raw, escaped): + self.assertEqual(escaped, self.helper._escaped_address(raw)) + def test_create_share(self): mock_ensure_export_policy = self.mock_object(self.helper, @@ -121,35 +125,6 @@ class NetAppClusteredNFSHelperTestCase(test.TestCase): self.helper._validate_access_rule, rule) - def test_get_sorted_access_rule_addresses(self): - - result = self.helper._get_sorted_access_rule_addresses( - fake.NEW_NFS_RULES) - - expected = [ - '10.10.20.10', - '10.10.20.0/24', - '10.10.10.10', - '10.10.10.0/30', - '10.10.10.0/24', - ] - self.assertEqual(expected, result) - - @ddt.data({'rule': '1.2.3.4', 'out': netaddr.IPAddress('1.2.3.4')}, - {'rule': '1.2.3.4/32', 'out': netaddr.IPNetwork('1.2.3.4/32')}) - @ddt.unpack - def test_get_network_object_from_rule(self, rule, out): - - result = self.helper._get_network_object_from_rule(rule) - - self.assertEqual(out, result) - - def test_get_network_object_from_rule_invalid(self): - - self.assertRaises(netaddr.AddrFormatError, - self.helper._get_network_object_from_rule, - 'invalid') - def test_get_target(self): target = self.helper.get_target(fake.NFS_SHARE) diff --git a/releasenotes/notes/netapp-ipv6-support-f448e99a7c112362.yaml b/releasenotes/notes/netapp-ipv6-support-f448e99a7c112362.yaml new file mode 100644 index 0000000000..ab5fcee7fb --- /dev/null +++ b/releasenotes/notes/netapp-ipv6-support-f448e99a7c112362.yaml @@ -0,0 +1,4 @@ +--- +features: + Added support for IPv6 export location and access rules to + the NetApp driver.