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:
parent
9cc5c3bf56
commit
2b40e5618f
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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,7 +243,8 @@ class NFSHelper(NASHelperBase):
|
||||
server,
|
||||
['sudo', 'exportfs', '-o',
|
||||
rules_options % access['access_level'],
|
||||
':'.join((self._get_parsed_access_to(access['access_to']),
|
||||
':'.join((
|
||||
self._get_parsed_address_or_cidr(access['access_to']),
|
||||
local_path))])
|
||||
self._sync_nfs_temp_and_perm_files(server)
|
||||
# Adding/Deleting specific rules
|
||||
@ -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 = []
|
||||
|
@ -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.
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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()
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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:
|
||||
|
@ -81,21 +81,50 @@ 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:
|
||||
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" % (ip, path),
|
||||
"path": "%s:%s" % (convert_address(ip, version), path),
|
||||
"is_admin_only": is_admin,
|
||||
"metadata": {
|
||||
"export_location_metadata_example": "example",
|
||||
@ -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)
|
||||
|
@ -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,
|
||||
|
@ -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':
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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'])
|
||||
|
@ -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')
|
||||
|
@ -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):
|
||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user