diff --git a/manila/network/__init__.py b/manila/network/__init__.py index dee16b58c5..d3369dfbbe 100644 --- a/manila/network/__init__.py +++ b/manila/network/__init__.py @@ -32,6 +32,19 @@ network_opts = [ help='The full class name of the Networking API class to use.'), ] +network_base_opts = [ + cfg.BoolOpt( + 'network_plugin_ipv4_enabled', + default=True, + help="Whether to support IPv4 network resource, Default=True."), + cfg.BoolOpt( + 'network_plugin_ipv6_enabled', + default=False, + help="Whether to support IPv6 network resource, Default=False. " + "If this option is True, the value of " + "'network_plugin_ipv4_enabled' will be ignored."), +] + CONF = cfg.CONF @@ -55,7 +68,14 @@ def API(config_group_name=None, label='user'): class NetworkBaseAPI(db_base.Base): """User network plugin for setting up main net interfaces.""" - def __init__(self, db_driver=None): + def __init__(self, config_group_name=None, db_driver=None): + if config_group_name: + CONF.register_opts(network_base_opts, + group=config_group_name) + else: + CONF.register_opts(network_base_opts) + self.configuration = getattr(CONF, + six.text_type(config_group_name), CONF) super(NetworkBaseAPI, self).__init__(db_driver=db_driver) def _verify_share_network(self, share_server_id, share_network): @@ -84,3 +104,17 @@ class NetworkBaseAPI(db_base.Base): @abc.abstractmethod def deallocate_network(self, context, share_server_id): pass + + @property + def enabled_ip_version(self): + if not hasattr(self, '_enabled_ip_version'): + if self.configuration.network_plugin_ipv6_enabled: + self._enabled_ip_version = 6 + elif self.configuration.network_plugin_ipv4_enabled: + self._enabled_ip_version = 4 + else: + msg = _("Either 'network_plugin_ipv4_enabled' or " + "'network_plugin_ipv6_enabled' " + "should be configured to 'True'.") + raise exception.NetworkBadConfigurationException(reason=msg) + return self._enabled_ip_version diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py index c4f9ce0e76..6b831f9af9 100644 --- a/manila/network/neutron/neutron_network_plugin.py +++ b/manila/network/neutron/neutron_network_plugin.py @@ -14,6 +14,8 @@ # License for the specific language governing permissions and limitations # under the License. +import ipaddress +import six import socket from oslo_config import cfg @@ -99,7 +101,10 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): def __init__(self, *args, **kwargs): db_driver = kwargs.pop('db_driver', None) - super(NeutronNetworkPlugin, self).__init__(db_driver=db_driver) + config_group_name = kwargs.get('config_group_name', 'DEFAULT') + super(NeutronNetworkPlugin, + self).__init__(config_group_name=config_group_name, + db_driver=db_driver) self._neutron_api = None self._neutron_api_args = args self._neutron_api_kwargs = kwargs @@ -156,6 +161,23 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): return ports + def _get_matched_ip_address(self, fixed_ips, ip_version): + """Get first ip address which matches the specified ip_version.""" + + for ip in fixed_ips: + try: + address = ipaddress.ip_address(six.text_type(ip['ip_address'])) + if address.version == ip_version: + return ip['ip_address'] + except ValueError: + LOG.error("%(address)s isn't a valid ip " + "address, omitted."), {'address': + ip['ip_address']} + msg = _("Can not find any IP address with configured IP " + "version %(version)s in share-network.") % {'version': + ip_version} + raise exception.NetworkBadConfigurationException(reason=msg) + def deallocate_network(self, context, share_server_id): """Deallocate neutron network resources for the given share server. @@ -188,10 +210,12 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI): port = self.neutron_api.create_port( share_network['project_id'], **create_args) + ip_address = self._get_matched_ip_address(port['fixed_ips'], + share_network['ip_version']) port_dict = { 'id': port['id'], 'share_server_id': share_server['id'], - 'ip_address': port['fixed_ips'][0]['ip_address'], + 'ip_address': ip_address, 'gateway': share_network['gateway'], 'mac_address': port['mac_address'], 'status': constants.STATUS_ACTIVE, diff --git a/manila/network/standalone_network_plugin.py b/manila/network/standalone_network_plugin.py index f1bfcf51ec..1e09cce51e 100644 --- a/manila/network/standalone_network_plugin.py +++ b/manila/network/standalone_network_plugin.py @@ -27,7 +27,7 @@ from manila import utils standalone_network_plugin_opts = [ cfg.StrOpt( 'standalone_network_plugin_gateway', - help="Gateway IPv4 address that should be used. Required.", + help="Gateway address that should be used. Required.", deprecated_group='DEFAULT'), cfg.StrOpt( 'standalone_network_plugin_mask', @@ -63,7 +63,12 @@ standalone_network_plugin_opts = [ 'standalone_network_plugin_ip_version', default=4, help="IP version of network. Optional." - "Allowed values are '4' and '6'. Default value is '4'.", + "Allowed values are '4' and '6'. Default value is '4'. " + "Note: This option is no longer used and has no effect", + deprecated_for_removal=True, + deprecated_reason="This option has been replaced by " + "'network_plugin_ipv4_enabled' and " + "'network_plugin_ipv6_enabled' options.", deprecated_group='DEFAULT'), cfg.IntOpt( 'standalone_network_plugin_mtu', @@ -89,8 +94,10 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): """ def __init__(self, config_group_name=None, db_driver=None, label='user'): - super(StandaloneNetworkPlugin, self).__init__(db_driver=db_driver) self.config_group_name = config_group_name or 'DEFAULT' + super(StandaloneNetworkPlugin, + self).__init__(config_group_name=self.config_group_name, + db_driver=db_driver) CONF.register_opts( standalone_network_plugin_opts, group=self.config_group_name) @@ -125,6 +132,34 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): def _set_persistent_network_data(self): """Sets persistent data for whole plugin.""" + # NOTE(tommylikehu): Standalone plugin could only support + # either IPv4 or IPv6, so if both network_plugin_ipv4_enabled + # and network_plugin_ipv6_enabled are configured True + # we would only support IPv6. + ipv4_enabled = getattr(self.configuration, + 'network_plugin_ipv4_enabled', None) + ipv6_enabled = getattr(self.configuration, + 'network_plugin_ipv6_enabled', None) + + if ipv4_enabled: + ip_version = 4 + if ipv6_enabled: + ip_version = 6 + if ipv4_enabled and ipv6_enabled: + LOG.warning("Only IPv6 is enabled, although both " + "'network_plugin_ipv4_enabled' and " + "'network_plugin_ipv6_enabled' are " + "configured True.") + + if not (ipv4_enabled or ipv6_enabled): + ip_version = int( + self.configuration.standalone_network_plugin_ip_version) + LOG.warning("You're using a deprecated option that may" + " be removed and silently ignored in the future. " + "Please use 'network_plugin_ipv4_enabled' or " + "'network_plugin_ipv6_enabled' instead of " + "'standalone_network_plugin_ip_version'.") + self.network_type = ( self.configuration.standalone_network_plugin_network_type) self.segmentation_id = ( @@ -133,8 +168,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): self.mask = self.configuration.standalone_network_plugin_mask self.allowed_ip_ranges = ( self.configuration.standalone_network_plugin_allowed_ip_ranges) - self.ip_version = int( - self.configuration.standalone_network_plugin_ip_version) + self.ip_version = ip_version self.net = self._get_network() self.allowed_cidrs = self._get_list_of_allowed_addresses() self.reserved_addresses = ( @@ -191,7 +225,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI): msg = _("Config option " "'standalone_network_plugin_allowed_ip_ranges' " "has incorrect value " - "'%s'") % self.allowed_ip_ranges + "'%s'.") % self.allowed_ip_ranges raise exception.NetworkBadConfigurationException( reason=msg) diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py index 3d1d9eefbf..b831ee28ee 100644 --- a/manila/scheduler/host_manager.py +++ b/manila/scheduler/host_manager.py @@ -143,6 +143,8 @@ class HostState(object): self.compression = False self.replication_type = None self.replication_domain = None + self.ipv4_support = None + self.ipv6_support = None # PoolState for all pools self.pools = {} @@ -332,6 +334,12 @@ class HostState(object): pool_cap['sg_consistent_snapshot_support'] = ( self.sg_consistent_snapshot_support) + if self.ipv4_support is not None: + pool_cap['ipv4_support'] = self.ipv4_support + + if self.ipv6_support is not None: + pool_cap['ipv6_support'] = self.ipv6_support + def update_backend(self, capability): self.share_backend_name = capability.get('share_backend_name') self.vendor_name = capability.get('vendor_name') @@ -351,6 +359,10 @@ class HostState(object): self.replication_domain = capability.get('replication_domain') self.sg_consistent_snapshot_support = capability.get( 'share_group_stats', {}).get('consistent_snapshot_support') + if capability.get('ipv4_support') is not None: + self.ipv4_support = capability['ipv4_support'] + if capability.get('ipv6_support') is not None: + self.ipv6_support = capability['ipv6_support'] def consume_from_share(self, share): """Incrementally update host state from an share.""" diff --git a/manila/scheduler/utils.py b/manila/scheduler/utils.py index 10bfaadd62..afc6622223 100644 --- a/manila/scheduler/utils.py +++ b/manila/scheduler/utils.py @@ -55,6 +55,8 @@ def generate_stats(host_state, properties): host_state.max_over_subscription_ratio, 'sg_consistent_snapshot_support': ( host_state.sg_consistent_snapshot_support), + 'ipv4_support': host_state.ipv4_support, + 'ipv6_support': host_state.ipv6_support, } host_caps = host_state.capabilities diff --git a/manila/share/access.py b/manila/share/access.py index 7b0149c82f..d8b2816a54 100644 --- a/manila/share/access.py +++ b/manila/share/access.py @@ -14,6 +14,7 @@ # under the License. import copy +import ipaddress from oslo_log import log @@ -21,6 +22,8 @@ from manila.common import constants from manila.i18n import _ from manila import utils +import six + LOG = log.getLogger(__name__) @@ -459,6 +462,18 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin): return access_rules_to_be_on_share + @staticmethod + def _filter_ipv6_rules(rules, share_instance_proto): + filtered = [] + for rule in rules: + if rule['access_type'] == 'ip' and share_instance_proto == 'nfs': + ip_version = ipaddress.ip_network( + six.text_type(rule['access_to'])).version + if 6 == ip_version: + continue + filtered.append(rule) + return filtered + def _get_rules_to_send_to_driver(self, context, share_instance): add_rules = [] delete_rules = [] @@ -472,6 +487,7 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin): share_instance_id=share_instance['id']) # Update queued rules to transitional states for rule in existing_rules_in_db: + if rule['state'] == constants.ACCESS_STATE_APPLYING: add_rules.append(rule) elif rule['state'] == constants.ACCESS_STATE_DENYING: @@ -480,6 +496,13 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin): access_rules_to_be_on_share = [ r for r in existing_rules_in_db if r['id'] not in delete_rule_ids ] + share = self.db.share_get(context, share_instance['share_id']) + si_proto = share['share_proto'].lower() + if not self.driver.ipv6_implemented: + add_rules = self._filter_ipv6_rules(add_rules, si_proto) + delete_rules = self._filter_ipv6_rules(delete_rules, si_proto) + access_rules_to_be_on_share = self._filter_ipv6_rules( + access_rules_to_be_on_share, si_proto) return access_rules_to_be_on_share, add_rules, delete_rules def _check_needs_refresh(self, context, share_instance_id): diff --git a/manila/share/driver.py b/manila/share/driver.py index 5ddb6eec3c..49bad69d49 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -252,6 +252,8 @@ class ShareDriver(object): self.configuration = kwargs.get('configuration', None) self.initialized = False self._stats = {} + self.ip_version = None + self.ipv6_implemented = False self.pools = [] if self.configuration: @@ -1116,6 +1118,7 @@ class ShareDriver(object): replication_domain=self.replication_domain, filter_function=self.get_filter_function(), goodness_function=self.get_goodness_function(), + ipv4_support=True, ) if isinstance(data, dict): common.update(data) @@ -1126,6 +1129,7 @@ class ShareDriver(object): 'consistent_snapshot_support'), } + self.add_ip_version_capability(common) self._stats = common def get_share_server_pools(self, share_server): @@ -2446,3 +2450,61 @@ class ShareDriver(object): LOG.debug("This backend does not support gathering 'used_size' of " "shares created on it.") return [] + + def get_configured_ip_version(self): + """"Get Configured IP versions when DHSS is false. + + The supported versions are returned with list, possible + values are: [4], [6] or [4, 6] + Each driver could override the method to return the IP version + which represents its self configuration. + """ + + # For drivers that haven't implemented IPv6, assume legacy behavior + if not self.ipv6_implemented: + return [4] + + raise NotImplementedError() + + def add_ip_version_capability(self, data): + """Add IP version support capabilities. + + When DHSS is true, the capabilities are determined by driver + and configured network plugin. + When DHSS is false, the capabilities are determined by driver and its + configuration. + :param data: the capability dictionary + :returns: capability data + """ + ipv4_support = data.get('ipv4_support', False) + ipv6_support = data.get('ipv6_support', False) + if self.ip_version is None: + if self.driver_handles_share_servers: + user_network_version = self.network_api.enabled_ip_version + if self.admin_network_api: + if (user_network_version == + self.admin_network_api.enabled_ip_version): + self.ip_version = user_network_version + else: + LOG.warning("The enabled IP version for the admin " + "network plugin is different from " + "that of user network plugin, this " + "may lead to the backend never being " + "chosen by the scheduler when ip " + "version is specified in the share " + "type.") + else: + self.ip_version = user_network_version + else: + self.ip_version = self.get_configured_ip_version() + + if not isinstance(self.ip_version, list): + self.ip_version = [self.ip_version] + + data['ipv4_support'] = (4 in self.ip_version) and ipv4_support + data['ipv6_support'] = (6 in self.ip_version) and ipv6_support + if not (data['ipv4_support'] or data['ipv6_support']): + LOG.error("Backend %s capabilities 'ipv4_support' " + "and 'ipv6_support' are both False.", + data['share_backend_name']) + return data diff --git a/manila/share/drivers/helpers.py b/manila/share/drivers/helpers.py index 0e37443600..96452a5ebb 100644 --- a/manila/share/drivers/helpers.py +++ b/manila/share/drivers/helpers.py @@ -13,6 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. +import copy +import netaddr import os import re @@ -183,7 +185,20 @@ class NFSHelper(NASHelperBase): def create_exports(self, server, share_name, recreate=False): path = os.path.join(self.configuration.share_mount_path, share_name) - return self.get_exports_for_share(server, path) + server_copy = copy.copy(server) + public_addresses = [] + if 'public_addresses' in server_copy: + for address in server_copy['public_addresses']: + public_addresses.append( + self._get_parsed_address_or_cidr(address)) + server_copy['public_addresses'] = public_addresses + + for t in ['public_address', 'admin_ip', 'ip']: + address = server_copy.get(t) + if address is not None: + server_copy[t] = self._get_parsed_address_or_cidr(address) + + return self.get_exports_for_share(server_copy, path) def init_helper(self, server): try: @@ -198,12 +213,6 @@ class NFSHelper(NASHelperBase): def remove_exports(self, server, share_name): """Remove exports.""" - def _get_parsed_access_to(self, access_to): - netmask = utils.cidr_to_netmask(access_to) - if netmask == '255.255.255.255': - return access_to.split('/')[0] - return access_to.split('/')[0] + '/' + netmask - @nfs_synchronized def update_access(self, server, share_name, access_rules, add_rules, delete_rules): @@ -234,8 +243,9 @@ class NFSHelper(NASHelperBase): server, ['sudo', 'exportfs', '-o', rules_options % access['access_level'], - ':'.join((self._get_parsed_access_to(access['access_to']), - local_path))]) + ':'.join(( + self._get_parsed_address_or_cidr(access['access_to']), + local_path))]) self._sync_nfs_temp_and_perm_files(server) # Adding/Deleting specific rules else: @@ -245,7 +255,7 @@ class NFSHelper(NASHelperBase): (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW)) for access in delete_rules: - access['access_to'] = self._get_parsed_access_to( + access['access_to'] = self._get_parsed_address_or_cidr( access['access_to']) try: self.validate_access_rules( @@ -265,7 +275,7 @@ class NFSHelper(NASHelperBase): if delete_rules: self._sync_nfs_temp_and_perm_files(server) for access in add_rules: - access['access_to'] = self._get_parsed_access_to( + access['access_to'] = self._get_parsed_address_or_cidr( access['access_to']) found_item = re.search( re.escape(local_path) + '[\s\n]*' + re.escape( @@ -290,6 +300,22 @@ class NFSHelper(NASHelperBase): if add_rules: self._sync_nfs_temp_and_perm_files(server) + def _get_parsed_address_or_cidr(self, access_to): + try: + network = netaddr.IPNetwork(access_to) + except netaddr.AddrFormatError: + raise exception.InvalidInput( + reason=_("Invalid address or cidr supplied %s.") % access_to) + mask_length = network.netmask.netmask_bits() + address = access_to.split('/')[0] + if network.version == 4: + if mask_length == 32: + return address + return '%s/%s' % (address, mask_length) + if mask_length == 128: + return "[%s]" % address + return "[%s]/%s" % (address, mask_length) + @staticmethod def get_host_list(output, local_path): entries = [] diff --git a/manila/share/drivers/lvm.py b/manila/share/drivers/lvm.py index b8212e11ce..d74c4ba38d 100644 --- a/manila/share/drivers/lvm.py +++ b/manila/share/drivers/lvm.py @@ -18,6 +18,7 @@ LVM Driver for shares. """ +import ipaddress import math import os import re @@ -154,6 +155,7 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): self.configuration.share_mount_path = ( self.configuration.lvm_share_export_root) self._helpers = None + self.configured_ip_version = None self.backend_name = self.configuration.safe_get( 'share_backend_name') or 'LVM' # Set of parameters used for compatibility with @@ -168,6 +170,7 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): else: self.share_server['public_addresses'] = ( self.configuration.lvm_share_export_ips) + self.ipv6_implemented = True def _ssh_exec_as_root(self, server, command, check_exit_code=True): kwargs = {} @@ -212,7 +215,8 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): 'revert_to_snapshot_support': True, 'mount_snapshot_support': True, 'driver_name': 'LVMShareDriver', - 'pools': self.get_share_server_pools() + 'pools': self.get_share_server_pools(), + 'ipv6_support': True } super(LVMShareDriver, self)._update_share_stats(data) @@ -431,6 +435,31 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver): super(LVMShareDriver, self).delete_snapshot(context, snapshot, share_server) + def get_configured_ip_version(self): + """"Get Configured IP versions when DHSS is false.""" + if self.configured_ip_version is None: + try: + self.configured_ip_version = [] + if self.configuration.lvm_share_export_ip: + self.configured_ip_version.append(ipaddress.ip_address( + six.text_type( + self.configuration.lvm_share_export_ip)).version) + else: + for ip in self.configuration.lvm_share_export_ips: + self.configured_ip_version.append( + ipaddress.ip_address(six.text_type(ip)).version) + except Exception: + if self.configuration.lvm_share_export_ip: + message = (_("Invalid 'lvm_share_export_ip' option " + "supplied %s.") % + self.configuration.lvm_share_export_ip) + else: + message = (_("Invalid 'lvm_share_export_ips' option " + "supplied %s.") % + self.configuration.lvm_share_export_ips) + raise exception.InvalidInput(reason=message) + return self.configured_ip_version + def snapshot_update_access(self, context, snapshot, access_rules, add_rules, delete_rules, share_server=None): """Update access rules for given snapshot. diff --git a/manila/share/drivers/service_instance.py b/manila/share/drivers/service_instance.py index cbd84d9e57..e1630bb690 100644 --- a/manila/share/drivers/service_instance.py +++ b/manila/share/drivers/service_instance.py @@ -121,13 +121,13 @@ no_share_servers_handling_mode_opts = [ "service_net_name_or_ip", help="Can be either name of network that is used by service " "instance within Nova to get IP address or IP address itself " - "for managing shares there. " + "(either IPv4 or IPv6) for managing shares there. " "Used only when share servers handling is disabled."), cfg.HostAddressOpt( "tenant_net_name_or_ip", help="Can be either name of network that is used by service " "instance within Nova to get IP address or IP address itself " - "for exporting shares. " + "(either IPv4 or IPv6) for exporting shares. " "Used only when share servers handling is disabled."), ] @@ -241,13 +241,13 @@ class ServiceInstanceManager(object): self.admin_context, self.get_config_option('service_instance_name_or_id')) - if netutils.is_valid_ipv4(data['service_net_name_or_ip']): + if netutils.is_valid_ip(data['service_net_name_or_ip']): data['private_address'] = [data['service_net_name_or_ip']] else: data['private_address'] = self._get_addresses_by_network_name( data['service_net_name_or_ip'], data['instance']) - if netutils.is_valid_ipv4(data['tenant_net_name_or_ip']): + if netutils.is_valid_ip(data['tenant_net_name_or_ip']): data['public_address'] = [data['tenant_net_name_or_ip']] else: data['public_address'] = self._get_addresses_by_network_name( @@ -267,13 +267,13 @@ class ServiceInstanceManager(object): 'instance_id': data['instance']['id'], } for key in ('private_address', 'public_address'): - data[key + '_v4'] = None + data[key + '_first'] = None for address in data[key]: - if netutils.is_valid_ipv4(address): - data[key + '_v4'] = address + if netutils.is_valid_ip(address): + data[key + '_first'] = address break - share_server['ip'] = data['private_address_v4'] - share_server['public_address'] = data['public_address_v4'] + share_server['ip'] = data['private_address_first'] + share_server['public_address'] = data['public_address_first'] return {'backend_details': share_server} def _get_addresses_by_network_name(self, net_name, server): diff --git a/manila/tests/network/neutron/test_neutron_plugin.py b/manila/tests/network/neutron/test_neutron_plugin.py index a0db5d1f3b..896cb7dbf1 100644 --- a/manila/tests/network/neutron/test_neutron_plugin.py +++ b/manila/tests/network/neutron/test_neutron_plugin.py @@ -44,7 +44,7 @@ fake_neutron_port = { "binding:capabilities": {"port_filter": True}, "mac_address": "test_mac", "fixed_ips": [ - {"subnet_id": "test_subnet_id", "ip_address": "test_ip"}, + {"subnet_id": "test_subnet_id", "ip_address": "203.0.113.100"}, ], "id": "test_port_id", "security_groups": ["fake_sec_group_id"], @@ -1505,6 +1505,7 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase): [fake_neutron_port], fake_share_server) +@ddt.ddt class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): def setUp(self): super(NeutronBindSingleNetworkPluginWithNormalTypeTest, self).setUp() @@ -1588,3 +1589,40 @@ class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): self.bind_plugin._wait_for_ports_bind.assert_called_once_with( [fake_neutron_port], fake_share_server) + + @ddt.data({'fix_ips': [{'ip_address': 'test_ip'}, + {'ip_address': '10.78.223.129'}], + 'ip_version': 4}, + {'fix_ips': [{'ip_address': 'test_ip'}, + {'ip_address': 'ad80::abaa:0:c2:2'}], + 'ip_version': 6}, + {'fix_ips': [{'ip_address': '10.78.223.129'}, + {'ip_address': 'ad80::abaa:0:c2:2'}], + 'ip_version': 6}, + ) + @ddt.unpack + def test__get_matched_ip_address(self, fix_ips, ip_version): + result = self.bind_plugin._get_matched_ip_address(fix_ips, ip_version) + self.assertEqual(fix_ips[1]['ip_address'], result) + + @ddt.data({'fix_ips': [{'ip_address': 'test_ip_1'}, + {'ip_address': 'test_ip_2'}], + 'ip_version': (4, 6)}, + {'fix_ips': [{'ip_address': 'ad80::abaa:0:c2:1'}, + {'ip_address': 'ad80::abaa:0:c2:2'}], + 'ip_version': (4, )}, + {'fix_ips': [{'ip_address': '192.0.0.2'}, + {'ip_address': '192.0.0.3'}], + 'ip_version': (6, )}, + {'fix_ips': [{'ip_address': '192.0.0.2/12'}, + {'ip_address': '192.0.0.330'}, + {'ip_address': 'ad80::001::ad80'}, + {'ip_address': 'ad80::abaa:0:c2:2/64'}], + 'ip_version': (4, 6)}, + ) + @ddt.unpack + def test__get_matched_ip_address_illegal(self, fix_ips, ip_version): + for version in ip_version: + self.assertRaises(exception.NetworkBadConfigurationException, + self.bind_plugin._get_matched_ip_address, + fix_ips, version) diff --git a/manila/tests/network/test_standalone_network_plugin.py b/manila/tests/network/test_standalone_network_plugin.py index d79a70ec27..ec7410e086 100644 --- a/manila/tests/network/test_standalone_network_plugin.py +++ b/manila/tests/network/test_standalone_network_plugin.py @@ -70,7 +70,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'standalone_network_plugin_segmentation_id': 1001, 'standalone_network_plugin_allowed_ip_ranges': ( '10.0.0.3-10.0.0.7,10.0.0.69-10.0.0.157,10.0.0.213'), - 'standalone_network_plugin_ip_version': 4, + 'network_plugin_ipv4_enabled': True, }, } allowed_cidrs = [ @@ -104,7 +104,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'standalone_network_plugin_gateway': ( '2001:cdba::3257:9652'), 'standalone_network_plugin_mask': '48', - 'standalone_network_plugin_ip_version': 6, + 'network_plugin_ipv6_enabled': True, }, } with test_utils.create_temp_config_with_opts(data): @@ -138,7 +138,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'standalone_network_plugin_segmentation_id': 3999, 'standalone_network_plugin_allowed_ip_ranges': ( '2001:db8::-2001:db8:0000:0000:0000:007f:ffff:ffff'), - 'standalone_network_plugin_ip_version': 6, + 'network_plugin_ipv6_enabled': True, }, } with test_utils.create_temp_config_with_opts(data): @@ -168,7 +168,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'standalone_network_plugin_mask': '255.255.0.0', 'standalone_network_plugin_network_type': network_type, 'standalone_network_plugin_segmentation_id': 1001, - 'standalone_network_plugin_ip_version': 4, + 'network_plugin_ipv4_enabled': True, }, } with test_utils.create_temp_config_with_opts(data): @@ -186,7 +186,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'standalone_network_plugin_mask': '255.255.0.0', 'standalone_network_plugin_network_type': fake_network_type, 'standalone_network_plugin_segmentation_id': 1001, - 'standalone_network_plugin_ip_version': 4, + 'network_plugin_ipv4_enabled': True, }, } with test_utils.create_temp_config_with_opts(data): @@ -255,10 +255,15 @@ class StandaloneNetworkPluginTest(test.TestCase): data = { group_name: { 'standalone_network_plugin_gateway': gateway, - 'standalone_network_plugin_ip_version': vers, 'standalone_network_plugin_mask': '25', }, } + if vers == 4: + data[group_name]['network_plugin_ipv4_enabled'] = True + if vers == 6: + data[group_name]['network_plugin_ipv4_enabled'] = False + data[group_name]['network_plugin_ipv6_enabled'] = True + with test_utils.create_temp_config_with_opts(data): self.assertRaises( exception.NetworkBadConfigurationException, @@ -319,7 +324,7 @@ class StandaloneNetworkPluginTest(test.TestCase): 'DEFAULT': { 'standalone_network_plugin_gateway': '2001:db8::0001', 'standalone_network_plugin_mask': '64', - 'standalone_network_plugin_ip_version': 6, + 'network_plugin_ipv6_enabled': True, }, } with test_utils.create_temp_config_with_opts(data): diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py index 36a18c2161..6bc2c5b6cc 100644 --- a/manila/tests/scheduler/test_host_manager.py +++ b/manila/tests/scheduler/test_host_manager.py @@ -637,7 +637,9 @@ class HostStateTestCase(test.TestCase): share_capability = {'total_capacity_gb': 0, 'free_capacity_gb': 100, 'reserved_percentage': 0, - 'timestamp': None} + 'timestamp': None, + 'ipv4_support': True, + 'ipv6_support': False} fake_host = host_manager.HostState('host1', share_capability) self.assertIsNone(fake_host.free_capacity_gb) @@ -646,9 +648,13 @@ class HostStateTestCase(test.TestCase): # Backend level stats remain uninitialized self.assertEqual(0, fake_host.total_capacity_gb) self.assertIsNone(fake_host.free_capacity_gb) + self.assertTrue(fake_host.ipv4_support) + self.assertFalse(fake_host.ipv6_support) # Pool stats has been updated self.assertEqual(0, fake_host.pools['_pool0'].total_capacity_gb) self.assertEqual(100, fake_host.pools['_pool0'].free_capacity_gb) + self.assertTrue(fake_host.pools['_pool0'].ipv4_support) + self.assertFalse(fake_host.pools['_pool0'].ipv6_support) # Test update for existing host state share_capability.update(dict(total_capacity_gb=1000)) @@ -674,6 +680,8 @@ class HostStateTestCase(test.TestCase): 'vendor_name': 'OpenStack', 'driver_version': '1.1', 'storage_protocol': 'NFS_CIFS', + 'ipv4_support': True, + 'ipv6_support': False, 'pools': [ {'pool_name': 'pool1', 'total_capacity_gb': 500, @@ -707,6 +715,8 @@ class HostStateTestCase(test.TestCase): self.assertEqual('NFS_CIFS', fake_host.storage_protocol) self.assertEqual('OpenStack', fake_host.vendor_name) self.assertEqual('1.1', fake_host.driver_version) + self.assertTrue(fake_host.ipv4_support) + self.assertFalse(fake_host.ipv6_support) # Backend level stats remain uninitialized self.assertEqual(0, fake_host.total_capacity_gb) @@ -716,8 +726,12 @@ class HostStateTestCase(test.TestCase): self.assertEqual(500, fake_host.pools['pool1'].total_capacity_gb) self.assertEqual(230, fake_host.pools['pool1'].free_capacity_gb) + self.assertTrue(fake_host.pools['pool1'].ipv4_support) + self.assertFalse(fake_host.pools['pool1'].ipv6_support) self.assertEqual(1024, fake_host.pools['pool2'].total_capacity_gb) self.assertEqual(1024, fake_host.pools['pool2'].free_capacity_gb) + self.assertTrue(fake_host.pools['pool2'].ipv4_support) + self.assertFalse(fake_host.pools['pool2'].ipv6_support) capability = { 'share_backend_name': 'Backend1', @@ -872,14 +886,17 @@ class PoolStateTestCase(test.TestCase): 'share_capability': {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 'reserved_percentage': 0, 'timestamp': None, - 'cap1': 'val1', 'cap2': 'val2'}, + 'cap1': 'val1', 'cap2': 'val2', 'ipv4_support': True, + 'ipv6_support': False}, 'instances': [] }, { 'share_capability': {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 'allocated_capacity_gb': 256, 'reserved_percentage': 0, - 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, + 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2', + 'ipv4_support': False, 'ipv6_support': True + }, 'instances': [ { @@ -894,14 +911,17 @@ class PoolStateTestCase(test.TestCase): 'share_capability': {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 'allocated_capacity_gb': 256, 'reserved_percentage': 0, - 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, + 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2', + 'ipv4_support': True, 'ipv6_support': True}, 'instances': [] }, { 'share_capability': {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 'provisioned_capacity_gb': 256, 'reserved_percentage': 0, - 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, + 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2', + 'ipv4_support': False, 'ipv6_support': False + }, 'instances': [ { @@ -976,3 +996,9 @@ class PoolStateTestCase(test.TestCase): fake_pool.allocated_capacity_gb) self.assertEqual(share_capability['provisioned_capacity_gb'], fake_pool.provisioned_capacity_gb) + if 'ipv4_support' in share_capability: + self.assertEqual(share_capability['ipv4_support'], + fake_pool.ipv4_support) + if 'ipv6_support' in share_capability: + self.assertEqual(share_capability['ipv6_support'], + fake_pool.ipv6_support) diff --git a/manila/tests/share/drivers/cephfs/test_driver.py b/manila/tests/share/drivers/cephfs/test_driver.py index 326a24bd8c..4ec1e86e35 100644 --- a/manila/tests/share/drivers/cephfs/test_driver.py +++ b/manila/tests/share/drivers/cephfs/test_driver.py @@ -336,6 +336,8 @@ class CephFSDriverTestCase(test.TestCase): self._driver._update_share_stats() result = self._driver._stats + self.assertTrue(result['ipv4_support']) + self.assertFalse(result['ipv6_support']) self.assertEqual("CEPHFS", result['storage_protocol']) def test_module_missing(self): diff --git a/manila/tests/share/drivers/container/test_driver.py b/manila/tests/share/drivers/container/test_driver.py index 57877ff028..787f026283 100644 --- a/manila/tests/share/drivers/container/test_driver.py +++ b/manila/tests/share/drivers/container/test_driver.py @@ -105,6 +105,8 @@ class ContainerShareDriverTestCase(test.TestCase): self.assertEqual('ContainerShareDriver', self._driver._stats['driver_name']) self.assertEqual('test-pool', self._driver._stats['pools']) + self.assertTrue(self._driver._stats['ipv4_support']) + self.assertFalse(self._driver._stats['ipv6_support']) def test_create_share(self): helper = mock.Mock() diff --git a/manila/tests/share/drivers/dell_emc/test_driver.py b/manila/tests/share/drivers/dell_emc/test_driver.py index f3837b64fd..1a259d7cb4 100644 --- a/manila/tests/share/drivers/dell_emc/test_driver.py +++ b/manila/tests/share/drivers/dell_emc/test_driver.py @@ -16,7 +16,6 @@ import mock from stevedore import extension -from manila import network from manila.share import configuration as conf from manila.share.drivers.dell_emc import driver as emcdriver from manila.share.drivers.dell_emc.plugins import base @@ -98,7 +97,6 @@ class EMCShareFrameworkTestCase(test.TestCase): self.configuration.append_config_values = mock.Mock(return_value=0) self.configuration.share_backend_name = FAKE_BACKEND self.mock_object(self.configuration, 'safe_get', self._fake_safe_get) - self.mock_object(network, 'API') self.driver = emcdriver.EMCShareDriver( configuration=self.configuration) @@ -133,6 +131,8 @@ class EMCShareFrameworkTestCase(test.TestCase): data['goodness_function'] = None data['snapshot_support'] = True data['create_share_from_snapshot_support'] = True + data['ipv4_support'] = True + data['ipv6_support'] = False self.assertEqual(data, self.driver._stats) def _fake_safe_get(self, value): diff --git a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py index 103d76da0c..98193892b2 100644 --- a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py +++ b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py @@ -265,6 +265,8 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'ipv4_support': True, + 'ipv6_support': False, } self.assertEqual(test_data, self._driver._stats) diff --git a/manila/tests/share/drivers/hdfs/test_hdfs_native.py b/manila/tests/share/drivers/hdfs/test_hdfs_native.py index f7f71465bf..8debfbcc15 100644 --- a/manila/tests/share/drivers/hdfs/test_hdfs_native.py +++ b/manila/tests/share/drivers/hdfs/test_hdfs_native.py @@ -409,9 +409,12 @@ class HDFSNativeShareDriverTestCase(test.TestCase): 'free_capacity_gb', 'total_capacity_gb', 'driver_handles_share_servers', 'reserved_percentage', 'vendor_name', 'storage_protocol', + 'ipv4_support', 'ipv6_support' ] for key in expected_keys: self.assertIn(key, result) + self.assertTrue(result['ipv4_support']) + self.assertFalse(False, result['ipv6_support']) self.assertEqual('HDFS', result['storage_protocol']) self._driver._get_available_capacity.assert_called_once_with() diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py index d23ed365b7..bad6971f44 100644 --- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py +++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py @@ -747,6 +747,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'ipv4_support': True, + 'ipv6_support': False, } result = self.driver.get_share_stats(refresh=True) @@ -822,6 +824,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'ipv4_support': True, + 'ipv6_support': False, } result = self.driver.get_share_stats(refresh=True) @@ -864,6 +868,8 @@ class HPE3ParDriverTestCase(test.TestCase): 'replication_domain': None, 'filter_function': None, 'goodness_function': None, + 'ipv4_support': True, + 'ipv6_support': False, } result = self.driver.get_share_stats(refresh=True) diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index f38a6a2b82..b6e74b202f 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -2431,6 +2431,8 @@ class HuaweiShareDriverTestCase(test.TestCase): "goodness_function": None, "pools": [], "share_group_stats": {"consistent_snapshot_support": None}, + "ipv4_support": True, + "ipv6_support": False, } if replication_support: diff --git a/manila/tests/share/drivers/test_helpers.py b/manila/tests/share/drivers/test_helpers.py index 404640dda1..a4822aa1b7 100644 --- a/manila/tests/share/drivers/test_helpers.py +++ b/manila/tests/share/drivers/test_helpers.py @@ -81,26 +81,55 @@ class NFSHelperTestCase(test.TestCase): self.server, ['sudo', 'exportfs']) @ddt.data( - {"public_address": "1.2.3.4"}, - {"public_address": "1.2.3.4", "admin_ip": "5.6.7.8"}, - {"public_address": "1.2.3.4", "ip": "9.10.11.12"}, + {"server": {"public_address": "1.2.3.4"}, "version": 4}, + {"server": {"public_address": "1001::1002"}, "version": 6}, + {"server": {"public_address": "1.2.3.4", "admin_ip": "5.6.7.8"}, + "version": 4}, + {"server": {"public_address": "1.2.3.4", "ip": "9.10.11.12"}, + "version": 4}, + {"server": {"public_address": "1001::1001", "ip": "1001::1002"}, + "version": 6}, + {"server": {"public_address": "1001::1002", "admin_ip": "1001::1002"}, + "version": 6}, + {"server": {"public_addresses": ["1001::1002"]}, "version": 6}, + {"server": {"public_addresses": ["1.2.3.4", "1001::1002"]}, + "version": {"1.2.3.4": 4, "1001::1002": 6}}, ) - def test_create_exports(self, server): + @ddt.unpack + def test_create_exports(self, server, version): result = self._helper.create_exports(server, self.share_name) expected_export_locations = [] path = os.path.join(CONF.share_mount_path, self.share_name) service_address = server.get("admin_ip", server.get("ip")) - for ip, is_admin in ((server['public_address'], False), - (service_address, True)): - if ip: - expected_export_locations.append({ - "path": "%s:%s" % (ip, path), - "is_admin_only": is_admin, - "metadata": { - "export_location_metadata_example": "example", - }, - }) + version_copy = version + + def convert_address(address, version): + if version == 4: + return address + return "[%s]" % address + + if 'public_addresses' in server: + pairs = list(map(lambda addr: (addr, False), + server['public_addresses'])) + else: + pairs = [(server['public_address'], False)] + + service_address = server.get("admin_ip", server.get("ip")) + if service_address: + pairs.append((service_address, True)) + + for ip, is_admin in pairs: + if isinstance(version_copy, dict): + version = version_copy.get(ip) + + expected_export_locations.append({ + "path": "%s:%s" % (convert_address(ip, version), path), + "is_admin_only": is_admin, + "metadata": { + "export_location_metadata_example": "example", + }, + }) self.assertEqual(expected_export_locations, result) @ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO) @@ -135,19 +164,36 @@ class NFSHelperTestCase(test.TestCase): mock.call(self.server, ['sudo', 'exportfs', '-u', ':'.join(['3.3.3.3', local_path])]), mock.call(self.server, ['sudo', 'exportfs', '-u', - ':'.join(['6.6.6.6/0.0.0.0', + ':'.join(['6.6.6.6/0', local_path])]), mock.call(self.server, ['sudo', 'exportfs', '-o', expected_mount_options % access_level, ':'.join(['2.2.2.2', local_path])]), mock.call(self.server, ['sudo', 'exportfs', '-o', expected_mount_options % access_level, - ':'.join(['5.5.5.5/255.255.255.0', + ':'.join(['5.5.5.5/24', local_path])]), ]) self._helper._sync_nfs_temp_and_perm_files.assert_has_calls([ mock.call(self.server), mock.call(self.server)]) + @ddt.data({'access': '10.0.0.1', 'result': '10.0.0.1'}, + {'access': '10.0.0.1/32', 'result': '10.0.0.1'}, + {'access': '10.0.0.0/24', 'result': '10.0.0.0/24'}, + {'access': '1001::1001', 'result': '[1001::1001]'}, + {'access': '1001::1000/128', 'result': '[1001::1000]'}, + {'access': '1001::1000/124', 'result': '[1001::1000]/124'}) + @ddt.unpack + def test__get_parsed_address_or_cidr(self, access, result): + self.assertEqual(result, + self._helper._get_parsed_address_or_cidr(access)) + + @ddt.data('10.0.0.265', '10.0.0.1/33', '1001::10069', '1001::1000/129') + def test__get_parsed_address_or_cidr_with_invalid_access(self, access): + self.assertRaises(exception.InvalidInput, + self._helper._get_parsed_address_or_cidr, + access) + def test_update_access_invalid_type(self): access_rules = [test_generic.get_fake_access_rule( '2.2.2.2', const.ACCESS_LEVEL_RW, access_type='fake'), ] @@ -215,7 +261,8 @@ class NFSHelperTestCase(test.TestCase): self._helper._ssh_exec.assert_has_calls( [mock.call(self.server, mock.ANY) for i in range(1)]) - @ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.9:/foo/quuz') + @ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.9:/foo/quuz', + '[1001::1001]:/foo/bar', '[1001::1000]/:124:/foo/bar') def test_get_exports_for_share_single_ip(self, export_location): server = dict(public_address='1.2.3.4') @@ -257,7 +304,8 @@ class NFSHelperTestCase(test.TestCase): exception.ManilaException, self._helper.get_exports_for_share, server, export_location) - @ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar') + @ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar', + '[1001::1002]:/foo/bar', '[1001::1000]/124:/foo/bar') def test_get_share_path_by_export_location(self, export_location): result = self._helper.get_share_path_by_export_location( dict(), export_location) diff --git a/manila/tests/share/drivers/test_lvm.py b/manila/tests/share/drivers/test_lvm.py index 3708add38a..f10fe91fbc 100644 --- a/manila/tests/share/drivers/test_lvm.py +++ b/manila/tests/share/drivers/test_lvm.py @@ -416,6 +416,23 @@ class LVMShareDriverTestCase(test.TestCase): self.server, self.share['name'], access_rules, add_rules=add_rules, delete_rules=delete_rules)) + @ddt.data(('1001::1001/129', None, False), ('1.1.1.256', None, False), + ('1001::1001', None, [6]), ('1.1.1.0', None, [4]), + (None, ['1001::1001', '1.1.1.0'], [6, 4]), + (None, ['1001::1001'], [6]), (None, ['1.1.1.0'], [4]), + (None, ['1001::1001/129', '1.1.1.0'], False)) + @ddt.unpack + def test_get_configured_ip_version( + self, configured_ip, configured_ips, configured_ip_version): + CONF.set_default('lvm_share_export_ip', configured_ip) + CONF.set_default('lvm_share_export_ips', configured_ips) + if configured_ip_version: + self.assertEqual(configured_ip_version, + self._driver.get_configured_ip_version()) + else: + self.assertRaises(exception.InvalidInput, + self._driver.get_configured_ip_version) + def test_mount_device(self): mount_path = self._get_mount_path(self.share) ret = self._driver._mount_device(self.share, 'fakedevice') @@ -541,7 +558,10 @@ class LVMShareDriverTestCase(test.TestCase): 'count=1024', 'bs=1M', run_as_root=True) - def test_update_share_stats(self): + @ddt.data(('1.1.1.1', 4), ('1001::1001', 6)) + @ddt.unpack + def test_update_share_stats(self, configured_ip, version): + CONF.set_default('lvm_share_export_ip', configured_ip) self.mock_object(self._driver, 'get_share_server_pools', mock.Mock(return_value='test-pool')) @@ -552,6 +572,8 @@ class LVMShareDriverTestCase(test.TestCase): self.assertTrue(self._driver._stats['snapshot_support']) self.assertEqual('LVMShareDriver', self._driver._stats['driver_name']) self.assertEqual('test-pool', self._driver._stats['pools']) + self.assertEqual(version == 4, self._driver._stats['ipv4_support']) + self.assertEqual(version == 6, self._driver._stats['ipv6_support']) def test_revert_to_snapshot(self): mock_update_access = self.mock_object(self._helper_nfs, diff --git a/manila/tests/share/drivers/test_service_instance.py b/manila/tests/share/drivers/test_service_instance.py index 1a06cfa19a..da34dc889c 100644 --- a/manila/tests/share/drivers/test_service_instance.py +++ b/manila/tests/share/drivers/test_service_instance.py @@ -779,8 +779,10 @@ class ServiceInstanceManagerTestCase(test.TestCase): fake_server_details) @ddt.data( - *[{'s': s, 't': t, 'server': server} - for s, t in ( + *[{'service_config': service_config, + 'tenant_config': tenant_config, + 'server': server} + for service_config, tenant_config in ( ('fake_net_s', 'fake_net_t'), ('fake_net_s', '12.34.56.78'), ('98.76.54.123', 'fake_net_t'), @@ -800,18 +802,25 @@ class ServiceInstanceManagerTestCase(test.TestCase): {'addr': 'fake4'}], }})]) @ddt.unpack - def test_get_common_server_valid_cases(self, s, t, server): - self._get_common_server(s, t, server, True) + def test_get_common_server_valid_cases(self, service_config, + tenant_config, server): + self._get_common_server(service_config, tenant_config, server, + '98.76.54.123', '12.34.56.78', True) @ddt.data( - *[{'s': s, 't': t, 'server': server} - for s, t in ( + *[{'service_config': service_config, + 'tenant_config': tenant_config, + 'server': server} + for service_config, tenant_config in ( ('fake_net_s', 'fake'), ('fake', 'fake_net_t'), ('fake', 'fake'), ('98.76.54.123', '12.12.12.1212'), ('12.12.12.1212', '12.34.56.78'), - ('12.12.12.1212', '12.12.12.1212')) + ('12.12.12.1212', '12.12.12.1212'), + ('1001::1001', '1001::100G'), + ('1001::10G1', '1001::1001'), + ) for server in ( {'networks': { 'fake_net_s': ['foo', '98.76.54.123', 'bar'], @@ -827,15 +836,38 @@ class ServiceInstanceManagerTestCase(test.TestCase): {'addr': 'fake4'}], }})]) @ddt.unpack - def test_get_common_server_invalid_cases(self, s, t, server): - self._get_common_server(s, t, server, False) + def test_get_common_server_invalid_cases(self, service_config, + tenant_config, server): + self._get_common_server(service_config, tenant_config, server, + '98.76.54.123', '12.34.56.78', False) - def _get_common_server(self, s, t, server, is_valid=True): + @ddt.data( + *[{'service_config': service_config, + 'tenant_config': tenant_config, + 'server': server} + for service_config, tenant_config in ( + ('fake_net_s', '1001::1002'), + ('1001::1001', 'fake_net_t'), + ('1001::1001', '1001::1002')) + for server in ( + {'networks': { + 'fake_net_s': ['foo', '1001::1001'], + 'fake_net_t': ['bar', '1001::1002']}}, + {'addresses': { + 'fake_net_s': [{'addr': 'foo'}, {'addr': '1001::1001'}], + 'fake_net_t': [{'addr': 'bar'}, {'addr': '1001::1002'}]}})]) + @ddt.unpack + def test_get_common_server_valid_ipv6_address(self, service_config, + tenant_config, server): + self._get_common_server(service_config, tenant_config, server, + '1001::1001', '1001::1002', True) + + def _get_common_server(self, service_config, tenant_config, + server, service_address, network_address, + is_valid=True): fake_instance_id = 'fake_instance_id' fake_user = 'fake_user' fake_pass = 'fake_pass' - fake_addr_s = '98.76.54.123' - fake_addr_t = '12.34.56.78' fake_server = {'id': fake_instance_id} fake_server.update(server) expected = { @@ -843,17 +875,17 @@ class ServiceInstanceManagerTestCase(test.TestCase): 'username': fake_user, 'password': fake_pass, 'pk_path': self._manager.path_to_private_key, - 'ip': fake_addr_s, - 'public_address': fake_addr_t, + 'ip': service_address, + 'public_address': network_address, 'instance_id': fake_instance_id, } } def fake_get_config_option(attr): if attr == 'service_net_name_or_ip': - return s + return service_config elif attr == 'tenant_net_name_or_ip': - return t + return tenant_config elif attr == 'service_instance_name_or_id': return fake_instance_id elif attr == 'service_instance_user': diff --git a/manila/tests/share/drivers/zfsonlinux/test_driver.py b/manila/tests/share/drivers/zfsonlinux/test_driver.py index eb4b048e05..a91c970b42 100644 --- a/manila/tests/share/drivers/zfsonlinux/test_driver.py +++ b/manila/tests/share/drivers/zfsonlinux/test_driver.py @@ -362,6 +362,8 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase): 'vendor_name': 'Open Source', 'filter_function': None, 'goodness_function': None, + 'ipv4_support': True, + 'ipv6_support': False, } if replication_domain: expected['replication_type'] = 'readable' diff --git a/manila/tests/share/test_access.py b/manila/tests/share/test_access.py index 5eac98fd5a..3234a01133 100644 --- a/manila/tests/share/test_access.py +++ b/manila/tests/share/test_access.py @@ -720,3 +720,56 @@ class ShareInstanceAccessTestCase(test.TestCase): else: self.assertEqual(states[0], rule_1['state']) self.assertEqual(states[-1], rule_4['state']) + + @ddt.data(('nfs', True), ('cifs', False), ('ceph', False)) + @ddt.unpack + def test__filter_ipv6_rules(self, proto, filtered): + pass_rules = [ + { + 'access_type': 'ip', + 'access_to': '1.1.1.1' + }, + { + 'access_type': 'ip', + 'access_to': '1.1.1.0/24' + }, + { + 'access_type': 'user', + 'access_to': 'fake_user' + }, + ] + fail_rules = [ + { + 'access_type': 'ip', + 'access_to': '1001::1001' + }, + { + 'access_type': 'ip', + 'access_to': '1001::/64' + }, + ] + test_rules = pass_rules + fail_rules + filtered_rules = self.access_helper._filter_ipv6_rules( + test_rules, proto) + if filtered: + self.assertEqual(pass_rules, filtered_rules) + else: + self.assertEqual(test_rules, filtered_rules) + + def test__get_rules_to_send_to_driver(self): + self.driver.ipv6_implemented = False + + share = db_utils.create_share(status=constants.STATUS_AVAILABLE) + share_instance = share['instance'] + db_utils.create_access(share_id=share['id'], access_to='1001::/64', + state=constants.ACCESS_STATE_ACTIVE) + self.mock_object( + self.access_helper, 'get_and_update_share_instance_access_rules', + mock.Mock(side_effect=self.access_helper. + get_and_update_share_instance_access_rules)) + + access_rules_to_be_on_share, add_rules, delete_rules = ( + self.access_helper._get_rules_to_send_to_driver( + self.context, share_instance)) + self.assertEqual([], add_rules) + self.assertEqual([], delete_rules) diff --git a/manila/tests/share/test_driver.py b/manila/tests/share/test_driver.py index fed846b50b..4a390850da 100644 --- a/manila/tests/share/test_driver.py +++ b/manila/tests/share/test_driver.py @@ -19,6 +19,7 @@ import time import ddt import mock +from mock import PropertyMock from manila import exception from manila import network @@ -1083,3 +1084,71 @@ class ShareDriverTestCase(test.TestCase): share_driver.snapshot_update_access, 'fake_context', 'fake_snapshot', ['r1', 'r2'], [], []) + + @ddt.data({'capability': (True, True), + 'user_admin_networks': [[4], [4]], + 'expected': {'ipv4': True, 'ipv6': False}}, + {'capability': (True, True), + 'user_admin_networks': [[6], [6]], + 'expected': {'ipv4': False, 'ipv6': True}}, + {'capability': (False, False), + 'user_admin_networks': [[4], [4]], + 'expected': {'ipv4': False, 'ipv6': False}}, + {'capability': (True, True), + 'user_admin_networks': [[4], [6]], + 'expected': {'ipv4': False, 'ipv6': False}}, + {'capability': (False, False), + 'user_admin_networks': [[6], [4]], + 'expected': {'ipv4': False, 'ipv6': False}},) + @ddt.unpack + def test_add_ip_version_capability_if_dhss_true(self, capability, + user_admin_networks, + expected): + share_driver = self._instantiate_share_driver(None, True) + version = PropertyMock(side_effect=user_admin_networks) + type(share_driver.network_api).enabled_ip_version = version + data = {'share_backend_name': 'fake_backend', + 'ipv4_support': capability[0], + 'ipv6_support': capability[1]} + + result = share_driver.add_ip_version_capability(data) + + self.assertIsNotNone(result['ipv4_support']) + self.assertEqual(expected['ipv4'], result['ipv4_support']) + self.assertIsNotNone(result['ipv6_support']) + self.assertEqual(expected['ipv6'], result['ipv6_support']) + + @ddt.data({'capability': (True, False), + 'conf': [4], + 'expected': {'ipv4': True, 'ipv6': False}}, + {'capability': (True, True), + 'conf': [6], + 'expected': {'ipv4': False, 'ipv6': True}}, + {'capability': (False, False), + 'conf': [4], + 'expected': {'ipv4': False, 'ipv6': False}}, + {'capability': (False, True), + 'conf': [4], + 'expected': {'ipv4': False, 'ipv6': False}}, + {'capability': (False, True), + 'conf': [6], + 'expected': {'ipv4': False, 'ipv6': True}}, + {'capability': (True, True), + 'conf': [4, 6], + 'expected': {'ipv4': True, 'ipv6': True}}, + ) + @ddt.unpack + def test_add_ip_version_capability_if_dhss_false(self, capability, + conf, expected): + share_driver = self._instantiate_share_driver(None, False) + self.mock_object(share_driver, 'get_configured_ip_version', + mock.Mock(return_value=conf)) + data = {'share_backend_name': 'fake_backend', + 'ipv4_support': capability[0], + 'ipv6_support': capability[1]} + result = share_driver.add_ip_version_capability(data) + + self.assertIsNotNone(result['ipv4_support']) + self.assertEqual(expected['ipv4'], result['ipv4_support']) + self.assertIsNotNone(result['ipv6_support']) + self.assertEqual(expected['ipv6'], result['ipv6_support']) diff --git a/manila/tests/test_network.py b/manila/tests/test_network.py index 8390de98d2..b3b03b2adb 100644 --- a/manila/tests/test_network.py +++ b/manila/tests/test_network.py @@ -128,3 +128,30 @@ class NetworkBaseAPITestCase(test.TestCase): self.assertRaises( exception.NetworkBadConfigurationException, result._verify_share_network, 'foo_id', None) + + @ddt.data((True, False, 6), (False, True, 4), + (True, True, 6), (None, None, False)) + @ddt.unpack + def test_enabled_ip_version(self, network_plugin_ipv6_enabled, + network_plugin_ipv4_enabled, + enable_ip_version): + class FakeNetworkAPI(network.NetworkBaseAPI): + def allocate_network(self, *args, **kwargs): + pass + + def deallocate_network(self, *args, **kwargs): + pass + + network.CONF.set_default('network_plugin_ipv6_enabled', + network_plugin_ipv6_enabled) + network.CONF.set_default('network_plugin_ipv4_enabled', + network_plugin_ipv4_enabled) + + result = FakeNetworkAPI() + + if enable_ip_version: + self.assertTrue(hasattr(result, 'enabled_ip_version')) + self.assertEqual(enable_ip_version, result.enabled_ip_version) + else: + self.assertRaises(exception.NetworkBadConfigurationException, + getattr, result, 'enabled_ip_version') diff --git a/manila/utils.py b/manila/utils.py index 3be8e74886..731d2ad144 100644 --- a/manila/utils.py +++ b/manila/utils.py @@ -386,14 +386,22 @@ def cidr_to_netmask(cidr): def is_valid_ip_address(ip_address, ip_version): - if int(ip_version) == 4: - return netutils.is_valid_ipv4(ip_address) - elif int(ip_version) == 6: - return netutils.is_valid_ipv6(ip_address) - else: + ip_version = ([int(ip_version)] if not isinstance(ip_version, list) + else ip_version) + + if not set(ip_version).issubset(set([4, 6])): raise exception.ManilaException( _("Provided improper IP version '%s'.") % ip_version) + if 4 in ip_version: + if netutils.is_valid_ipv4(ip_address): + return True + if 6 in ip_version: + if netutils.is_valid_ipv6(ip_address): + return True + + return False + class IsAMatcher(object): def __init__(self, expected_value=None): diff --git a/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml b/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml new file mode 100644 index 0000000000..d96a1056e2 --- /dev/null +++ b/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml @@ -0,0 +1,8 @@ +--- +features: + - Added optional extra spec 'ipv4_support' and 'ipv6_support' for share + type. + - Added new capabilities 'ipv4_support' and 'ipv6_support' for IP based + drivers. + - Added IPv6 support in network plugins. (support either IPv6 or IPv4) + - Added IPv6 support in the lvm driver. (support both IPv6 and IPv4)