Enable IPv6 in manila(network plugins and drivers)

Please read spec for design detail [1].

Support IPv6 in IP drivers, networks and share
type extra specs.

Co-Authored-By: TommyLikeHu(tommylikehu@gmail.com)
Co-Authored-By: Ben Swartzlander <ben@swartzlander.org>

[1] f7202a6cfe32a057f752a4e393f848f8a0211c36

DocImpact

Partial-Implements: blueprint support-ipv6-access
Change-Id: I96d3389262e9829b8b4344870cdf5c76abd22828
This commit is contained in:
zhongjun2 2016-12-05 15:41:33 +08:00 committed by zhongjun
parent 9cc5c3bf56
commit 2b40e5618f
29 changed files with 686 additions and 85 deletions

View File

@ -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

View File

@ -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,

View File

@ -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)

View File

@ -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."""

View File

@ -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

View File

@ -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):

View File

@ -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

View File

@ -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 = []

View File

@ -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.

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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)

View File

@ -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):

View File

@ -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()

View File

@ -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):

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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,

View File

@ -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':

View File

@ -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'

View File

@ -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)

View File

@ -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'])

View File

@ -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')

View File

@ -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):

View File

@ -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)