summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2017-07-27 15:53:08 +0000
committerGerrit Code Review <review@openstack.org>2017-07-27 15:53:08 +0000
commit2f1536aec4fca04938d2bf5430cb2d1df745f35b (patch)
tree9b54767f7c752a382ab10c3531172582a751a0bf
parent244284fb6ee268edc6d31bd96a598582aa37dac6 (diff)
parent2b40e5618f2c1039bbbbd1a3e31b72e104b5436b (diff)
Merge "Enable IPv6 in manila(network plugins and drivers)"5.0.0.0b3
-rw-r--r--manila/network/__init__.py36
-rw-r--r--manila/network/neutron/neutron_network_plugin.py28
-rw-r--r--manila/network/standalone_network_plugin.py46
-rw-r--r--manila/scheduler/host_manager.py12
-rw-r--r--manila/scheduler/utils.py2
-rw-r--r--manila/share/access.py23
-rw-r--r--manila/share/driver.py62
-rw-r--r--manila/share/drivers/helpers.py48
-rw-r--r--manila/share/drivers/lvm.py31
-rw-r--r--manila/share/drivers/service_instance.py18
-rw-r--r--manila/tests/network/neutron/test_neutron_plugin.py40
-rw-r--r--manila/tests/network/test_standalone_network_plugin.py19
-rw-r--r--manila/tests/scheduler/test_host_manager.py36
-rw-r--r--manila/tests/share/drivers/cephfs/test_driver.py2
-rw-r--r--manila/tests/share/drivers/container/test_driver.py2
-rw-r--r--manila/tests/share/drivers/dell_emc/test_driver.py4
-rw-r--r--manila/tests/share/drivers/glusterfs/test_glusterfs_native.py2
-rw-r--r--manila/tests/share/drivers/hdfs/test_hdfs_native.py3
-rw-r--r--manila/tests/share/drivers/hpe/test_hpe_3par_driver.py6
-rw-r--r--manila/tests/share/drivers/huawei/test_huawei_nas.py2
-rw-r--r--manila/tests/share/drivers/test_helpers.py84
-rw-r--r--manila/tests/share/drivers/test_lvm.py24
-rw-r--r--manila/tests/share/drivers/test_service_instance.py64
-rw-r--r--manila/tests/share/drivers/zfsonlinux/test_driver.py2
-rw-r--r--manila/tests/share/test_access.py53
-rw-r--r--manila/tests/share/test_driver.py69
-rw-r--r--manila/tests/test_network.py27
-rw-r--r--manila/utils.py18
-rw-r--r--releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml8
29 files changed, 686 insertions, 85 deletions
diff --git a/manila/network/__init__.py b/manila/network/__init__.py
index dee16b5..d3369df 100644
--- a/manila/network/__init__.py
+++ b/manila/network/__init__.py
@@ -32,6 +32,19 @@ network_opts = [
32 help='The full class name of the Networking API class to use.'), 32 help='The full class name of the Networking API class to use.'),
33] 33]
34 34
35network_base_opts = [
36 cfg.BoolOpt(
37 'network_plugin_ipv4_enabled',
38 default=True,
39 help="Whether to support IPv4 network resource, Default=True."),
40 cfg.BoolOpt(
41 'network_plugin_ipv6_enabled',
42 default=False,
43 help="Whether to support IPv6 network resource, Default=False. "
44 "If this option is True, the value of "
45 "'network_plugin_ipv4_enabled' will be ignored."),
46]
47
35CONF = cfg.CONF 48CONF = cfg.CONF
36 49
37 50
@@ -55,7 +68,14 @@ def API(config_group_name=None, label='user'):
55class NetworkBaseAPI(db_base.Base): 68class NetworkBaseAPI(db_base.Base):
56 """User network plugin for setting up main net interfaces.""" 69 """User network plugin for setting up main net interfaces."""
57 70
58 def __init__(self, db_driver=None): 71 def __init__(self, config_group_name=None, db_driver=None):
72 if config_group_name:
73 CONF.register_opts(network_base_opts,
74 group=config_group_name)
75 else:
76 CONF.register_opts(network_base_opts)
77 self.configuration = getattr(CONF,
78 six.text_type(config_group_name), CONF)
59 super(NetworkBaseAPI, self).__init__(db_driver=db_driver) 79 super(NetworkBaseAPI, self).__init__(db_driver=db_driver)
60 80
61 def _verify_share_network(self, share_server_id, share_network): 81 def _verify_share_network(self, share_server_id, share_network):
@@ -84,3 +104,17 @@ class NetworkBaseAPI(db_base.Base):
84 @abc.abstractmethod 104 @abc.abstractmethod
85 def deallocate_network(self, context, share_server_id): 105 def deallocate_network(self, context, share_server_id):
86 pass 106 pass
107
108 @property
109 def enabled_ip_version(self):
110 if not hasattr(self, '_enabled_ip_version'):
111 if self.configuration.network_plugin_ipv6_enabled:
112 self._enabled_ip_version = 6
113 elif self.configuration.network_plugin_ipv4_enabled:
114 self._enabled_ip_version = 4
115 else:
116 msg = _("Either 'network_plugin_ipv4_enabled' or "
117 "'network_plugin_ipv6_enabled' "
118 "should be configured to 'True'.")
119 raise exception.NetworkBadConfigurationException(reason=msg)
120 return self._enabled_ip_version
diff --git a/manila/network/neutron/neutron_network_plugin.py b/manila/network/neutron/neutron_network_plugin.py
index c4f9ce0..6b831f9 100644
--- a/manila/network/neutron/neutron_network_plugin.py
+++ b/manila/network/neutron/neutron_network_plugin.py
@@ -14,6 +14,8 @@
14# License for the specific language governing permissions and limitations 14# License for the specific language governing permissions and limitations
15# under the License. 15# under the License.
16 16
17import ipaddress
18import six
17import socket 19import socket
18 20
19from oslo_config import cfg 21from oslo_config import cfg
@@ -99,7 +101,10 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
99 101
100 def __init__(self, *args, **kwargs): 102 def __init__(self, *args, **kwargs):
101 db_driver = kwargs.pop('db_driver', None) 103 db_driver = kwargs.pop('db_driver', None)
102 super(NeutronNetworkPlugin, self).__init__(db_driver=db_driver) 104 config_group_name = kwargs.get('config_group_name', 'DEFAULT')
105 super(NeutronNetworkPlugin,
106 self).__init__(config_group_name=config_group_name,
107 db_driver=db_driver)
103 self._neutron_api = None 108 self._neutron_api = None
104 self._neutron_api_args = args 109 self._neutron_api_args = args
105 self._neutron_api_kwargs = kwargs 110 self._neutron_api_kwargs = kwargs
@@ -156,6 +161,23 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
156 161
157 return ports 162 return ports
158 163
164 def _get_matched_ip_address(self, fixed_ips, ip_version):
165 """Get first ip address which matches the specified ip_version."""
166
167 for ip in fixed_ips:
168 try:
169 address = ipaddress.ip_address(six.text_type(ip['ip_address']))
170 if address.version == ip_version:
171 return ip['ip_address']
172 except ValueError:
173 LOG.error("%(address)s isn't a valid ip "
174 "address, omitted."), {'address':
175 ip['ip_address']}
176 msg = _("Can not find any IP address with configured IP "
177 "version %(version)s in share-network.") % {'version':
178 ip_version}
179 raise exception.NetworkBadConfigurationException(reason=msg)
180
159 def deallocate_network(self, context, share_server_id): 181 def deallocate_network(self, context, share_server_id):
160 """Deallocate neutron network resources for the given share server. 182 """Deallocate neutron network resources for the given share server.
161 183
@@ -188,10 +210,12 @@ class NeutronNetworkPlugin(network.NetworkBaseAPI):
188 port = self.neutron_api.create_port( 210 port = self.neutron_api.create_port(
189 share_network['project_id'], **create_args) 211 share_network['project_id'], **create_args)
190 212
213 ip_address = self._get_matched_ip_address(port['fixed_ips'],
214 share_network['ip_version'])
191 port_dict = { 215 port_dict = {
192 'id': port['id'], 216 'id': port['id'],
193 'share_server_id': share_server['id'], 217 'share_server_id': share_server['id'],
194 'ip_address': port['fixed_ips'][0]['ip_address'], 218 'ip_address': ip_address,
195 'gateway': share_network['gateway'], 219 'gateway': share_network['gateway'],
196 'mac_address': port['mac_address'], 220 'mac_address': port['mac_address'],
197 'status': constants.STATUS_ACTIVE, 221 'status': constants.STATUS_ACTIVE,
diff --git a/manila/network/standalone_network_plugin.py b/manila/network/standalone_network_plugin.py
index f1bfcf5..1e09cce 100644
--- a/manila/network/standalone_network_plugin.py
+++ b/manila/network/standalone_network_plugin.py
@@ -27,7 +27,7 @@ from manila import utils
27standalone_network_plugin_opts = [ 27standalone_network_plugin_opts = [
28 cfg.StrOpt( 28 cfg.StrOpt(
29 'standalone_network_plugin_gateway', 29 'standalone_network_plugin_gateway',
30 help="Gateway IPv4 address that should be used. Required.", 30 help="Gateway address that should be used. Required.",
31 deprecated_group='DEFAULT'), 31 deprecated_group='DEFAULT'),
32 cfg.StrOpt( 32 cfg.StrOpt(
33 'standalone_network_plugin_mask', 33 'standalone_network_plugin_mask',
@@ -63,7 +63,12 @@ standalone_network_plugin_opts = [
63 'standalone_network_plugin_ip_version', 63 'standalone_network_plugin_ip_version',
64 default=4, 64 default=4,
65 help="IP version of network. Optional." 65 help="IP version of network. Optional."
66 "Allowed values are '4' and '6'. Default value is '4'.", 66 "Allowed values are '4' and '6'. Default value is '4'. "
67 "Note: This option is no longer used and has no effect",
68 deprecated_for_removal=True,
69 deprecated_reason="This option has been replaced by "
70 "'network_plugin_ipv4_enabled' and "
71 "'network_plugin_ipv6_enabled' options.",
67 deprecated_group='DEFAULT'), 72 deprecated_group='DEFAULT'),
68 cfg.IntOpt( 73 cfg.IntOpt(
69 'standalone_network_plugin_mtu', 74 'standalone_network_plugin_mtu',
@@ -89,8 +94,10 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
89 """ 94 """
90 95
91 def __init__(self, config_group_name=None, db_driver=None, label='user'): 96 def __init__(self, config_group_name=None, db_driver=None, label='user'):
92 super(StandaloneNetworkPlugin, self).__init__(db_driver=db_driver)
93 self.config_group_name = config_group_name or 'DEFAULT' 97 self.config_group_name = config_group_name or 'DEFAULT'
98 super(StandaloneNetworkPlugin,
99 self).__init__(config_group_name=self.config_group_name,
100 db_driver=db_driver)
94 CONF.register_opts( 101 CONF.register_opts(
95 standalone_network_plugin_opts, 102 standalone_network_plugin_opts,
96 group=self.config_group_name) 103 group=self.config_group_name)
@@ -125,6 +132,34 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
125 132
126 def _set_persistent_network_data(self): 133 def _set_persistent_network_data(self):
127 """Sets persistent data for whole plugin.""" 134 """Sets persistent data for whole plugin."""
135 # NOTE(tommylikehu): Standalone plugin could only support
136 # either IPv4 or IPv6, so if both network_plugin_ipv4_enabled
137 # and network_plugin_ipv6_enabled are configured True
138 # we would only support IPv6.
139 ipv4_enabled = getattr(self.configuration,
140 'network_plugin_ipv4_enabled', None)
141 ipv6_enabled = getattr(self.configuration,
142 'network_plugin_ipv6_enabled', None)
143
144 if ipv4_enabled:
145 ip_version = 4
146 if ipv6_enabled:
147 ip_version = 6
148 if ipv4_enabled and ipv6_enabled:
149 LOG.warning("Only IPv6 is enabled, although both "
150 "'network_plugin_ipv4_enabled' and "
151 "'network_plugin_ipv6_enabled' are "
152 "configured True.")
153
154 if not (ipv4_enabled or ipv6_enabled):
155 ip_version = int(
156 self.configuration.standalone_network_plugin_ip_version)
157 LOG.warning("You're using a deprecated option that may"
158 " be removed and silently ignored in the future. "
159 "Please use 'network_plugin_ipv4_enabled' or "
160 "'network_plugin_ipv6_enabled' instead of "
161 "'standalone_network_plugin_ip_version'.")
162
128 self.network_type = ( 163 self.network_type = (
129 self.configuration.standalone_network_plugin_network_type) 164 self.configuration.standalone_network_plugin_network_type)
130 self.segmentation_id = ( 165 self.segmentation_id = (
@@ -133,8 +168,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
133 self.mask = self.configuration.standalone_network_plugin_mask 168 self.mask = self.configuration.standalone_network_plugin_mask
134 self.allowed_ip_ranges = ( 169 self.allowed_ip_ranges = (
135 self.configuration.standalone_network_plugin_allowed_ip_ranges) 170 self.configuration.standalone_network_plugin_allowed_ip_ranges)
136 self.ip_version = int( 171 self.ip_version = ip_version
137 self.configuration.standalone_network_plugin_ip_version)
138 self.net = self._get_network() 172 self.net = self._get_network()
139 self.allowed_cidrs = self._get_list_of_allowed_addresses() 173 self.allowed_cidrs = self._get_list_of_allowed_addresses()
140 self.reserved_addresses = ( 174 self.reserved_addresses = (
@@ -191,7 +225,7 @@ class StandaloneNetworkPlugin(network.NetworkBaseAPI):
191 msg = _("Config option " 225 msg = _("Config option "
192 "'standalone_network_plugin_allowed_ip_ranges' " 226 "'standalone_network_plugin_allowed_ip_ranges' "
193 "has incorrect value " 227 "has incorrect value "
194 "'%s'") % self.allowed_ip_ranges 228 "'%s'.") % self.allowed_ip_ranges
195 raise exception.NetworkBadConfigurationException( 229 raise exception.NetworkBadConfigurationException(
196 reason=msg) 230 reason=msg)
197 231
diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py
index 3d1d9ee..b831ee2 100644
--- a/manila/scheduler/host_manager.py
+++ b/manila/scheduler/host_manager.py
@@ -143,6 +143,8 @@ class HostState(object):
143 self.compression = False 143 self.compression = False
144 self.replication_type = None 144 self.replication_type = None
145 self.replication_domain = None 145 self.replication_domain = None
146 self.ipv4_support = None
147 self.ipv6_support = None
146 148
147 # PoolState for all pools 149 # PoolState for all pools
148 self.pools = {} 150 self.pools = {}
@@ -332,6 +334,12 @@ class HostState(object):
332 pool_cap['sg_consistent_snapshot_support'] = ( 334 pool_cap['sg_consistent_snapshot_support'] = (
333 self.sg_consistent_snapshot_support) 335 self.sg_consistent_snapshot_support)
334 336
337 if self.ipv4_support is not None:
338 pool_cap['ipv4_support'] = self.ipv4_support
339
340 if self.ipv6_support is not None:
341 pool_cap['ipv6_support'] = self.ipv6_support
342
335 def update_backend(self, capability): 343 def update_backend(self, capability):
336 self.share_backend_name = capability.get('share_backend_name') 344 self.share_backend_name = capability.get('share_backend_name')
337 self.vendor_name = capability.get('vendor_name') 345 self.vendor_name = capability.get('vendor_name')
@@ -351,6 +359,10 @@ class HostState(object):
351 self.replication_domain = capability.get('replication_domain') 359 self.replication_domain = capability.get('replication_domain')
352 self.sg_consistent_snapshot_support = capability.get( 360 self.sg_consistent_snapshot_support = capability.get(
353 'share_group_stats', {}).get('consistent_snapshot_support') 361 'share_group_stats', {}).get('consistent_snapshot_support')
362 if capability.get('ipv4_support') is not None:
363 self.ipv4_support = capability['ipv4_support']
364 if capability.get('ipv6_support') is not None:
365 self.ipv6_support = capability['ipv6_support']
354 366
355 def consume_from_share(self, share): 367 def consume_from_share(self, share):
356 """Incrementally update host state from an share.""" 368 """Incrementally update host state from an share."""
diff --git a/manila/scheduler/utils.py b/manila/scheduler/utils.py
index 10bfaad..afc6622 100644
--- a/manila/scheduler/utils.py
+++ b/manila/scheduler/utils.py
@@ -55,6 +55,8 @@ def generate_stats(host_state, properties):
55 host_state.max_over_subscription_ratio, 55 host_state.max_over_subscription_ratio,
56 'sg_consistent_snapshot_support': ( 56 'sg_consistent_snapshot_support': (
57 host_state.sg_consistent_snapshot_support), 57 host_state.sg_consistent_snapshot_support),
58 'ipv4_support': host_state.ipv4_support,
59 'ipv6_support': host_state.ipv6_support,
58 } 60 }
59 61
60 host_caps = host_state.capabilities 62 host_caps = host_state.capabilities
diff --git a/manila/share/access.py b/manila/share/access.py
index 7b0149c..d8b2816 100644
--- a/manila/share/access.py
+++ b/manila/share/access.py
@@ -14,6 +14,7 @@
14# under the License. 14# under the License.
15 15
16import copy 16import copy
17import ipaddress
17 18
18from oslo_log import log 19from oslo_log import log
19 20
@@ -21,6 +22,8 @@ from manila.common import constants
21from manila.i18n import _ 22from manila.i18n import _
22from manila import utils 23from manila import utils
23 24
25import six
26
24LOG = log.getLogger(__name__) 27LOG = log.getLogger(__name__)
25 28
26 29
@@ -459,6 +462,18 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin):
459 462
460 return access_rules_to_be_on_share 463 return access_rules_to_be_on_share
461 464
465 @staticmethod
466 def _filter_ipv6_rules(rules, share_instance_proto):
467 filtered = []
468 for rule in rules:
469 if rule['access_type'] == 'ip' and share_instance_proto == 'nfs':
470 ip_version = ipaddress.ip_network(
471 six.text_type(rule['access_to'])).version
472 if 6 == ip_version:
473 continue
474 filtered.append(rule)
475 return filtered
476
462 def _get_rules_to_send_to_driver(self, context, share_instance): 477 def _get_rules_to_send_to_driver(self, context, share_instance):
463 add_rules = [] 478 add_rules = []
464 delete_rules = [] 479 delete_rules = []
@@ -472,6 +487,7 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin):
472 share_instance_id=share_instance['id']) 487 share_instance_id=share_instance['id'])
473 # Update queued rules to transitional states 488 # Update queued rules to transitional states
474 for rule in existing_rules_in_db: 489 for rule in existing_rules_in_db:
490
475 if rule['state'] == constants.ACCESS_STATE_APPLYING: 491 if rule['state'] == constants.ACCESS_STATE_APPLYING:
476 add_rules.append(rule) 492 add_rules.append(rule)
477 elif rule['state'] == constants.ACCESS_STATE_DENYING: 493 elif rule['state'] == constants.ACCESS_STATE_DENYING:
@@ -480,6 +496,13 @@ class ShareInstanceAccess(ShareInstanceAccessDatabaseMixin):
480 access_rules_to_be_on_share = [ 496 access_rules_to_be_on_share = [
481 r for r in existing_rules_in_db if r['id'] not in delete_rule_ids 497 r for r in existing_rules_in_db if r['id'] not in delete_rule_ids
482 ] 498 ]
499 share = self.db.share_get(context, share_instance['share_id'])
500 si_proto = share['share_proto'].lower()
501 if not self.driver.ipv6_implemented:
502 add_rules = self._filter_ipv6_rules(add_rules, si_proto)
503 delete_rules = self._filter_ipv6_rules(delete_rules, si_proto)
504 access_rules_to_be_on_share = self._filter_ipv6_rules(
505 access_rules_to_be_on_share, si_proto)
483 return access_rules_to_be_on_share, add_rules, delete_rules 506 return access_rules_to_be_on_share, add_rules, delete_rules
484 507
485 def _check_needs_refresh(self, context, share_instance_id): 508 def _check_needs_refresh(self, context, share_instance_id):
diff --git a/manila/share/driver.py b/manila/share/driver.py
index 5ddb6ee..49bad69 100644
--- a/manila/share/driver.py
+++ b/manila/share/driver.py
@@ -252,6 +252,8 @@ class ShareDriver(object):
252 self.configuration = kwargs.get('configuration', None) 252 self.configuration = kwargs.get('configuration', None)
253 self.initialized = False 253 self.initialized = False
254 self._stats = {} 254 self._stats = {}
255 self.ip_version = None
256 self.ipv6_implemented = False
255 257
256 self.pools = [] 258 self.pools = []
257 if self.configuration: 259 if self.configuration:
@@ -1116,6 +1118,7 @@ class ShareDriver(object):
1116 replication_domain=self.replication_domain, 1118 replication_domain=self.replication_domain,
1117 filter_function=self.get_filter_function(), 1119 filter_function=self.get_filter_function(),
1118 goodness_function=self.get_goodness_function(), 1120 goodness_function=self.get_goodness_function(),
1121 ipv4_support=True,
1119 ) 1122 )
1120 if isinstance(data, dict): 1123 if isinstance(data, dict):
1121 common.update(data) 1124 common.update(data)
@@ -1126,6 +1129,7 @@ class ShareDriver(object):
1126 'consistent_snapshot_support'), 1129 'consistent_snapshot_support'),
1127 } 1130 }
1128 1131
1132 self.add_ip_version_capability(common)
1129 self._stats = common 1133 self._stats = common
1130 1134
1131 def get_share_server_pools(self, share_server): 1135 def get_share_server_pools(self, share_server):
@@ -2446,3 +2450,61 @@ class ShareDriver(object):
2446 LOG.debug("This backend does not support gathering 'used_size' of " 2450 LOG.debug("This backend does not support gathering 'used_size' of "
2447 "shares created on it.") 2451 "shares created on it.")
2448 return [] 2452 return []
2453
2454 def get_configured_ip_version(self):
2455 """"Get Configured IP versions when DHSS is false.
2456
2457 The supported versions are returned with list, possible
2458 values are: [4], [6] or [4, 6]
2459 Each driver could override the method to return the IP version
2460 which represents its self configuration.
2461 """
2462
2463 # For drivers that haven't implemented IPv6, assume legacy behavior
2464 if not self.ipv6_implemented:
2465 return [4]
2466
2467 raise NotImplementedError()
2468
2469 def add_ip_version_capability(self, data):
2470 """Add IP version support capabilities.
2471
2472 When DHSS is true, the capabilities are determined by driver
2473 and configured network plugin.
2474 When DHSS is false, the capabilities are determined by driver and its
2475 configuration.
2476 :param data: the capability dictionary
2477 :returns: capability data
2478 """
2479 ipv4_support = data.get('ipv4_support', False)
2480 ipv6_support = data.get('ipv6_support', False)
2481 if self.ip_version is None:
2482 if self.driver_handles_share_servers:
2483 user_network_version = self.network_api.enabled_ip_version
2484 if self.admin_network_api:
2485 if (user_network_version ==
2486 self.admin_network_api.enabled_ip_version):
2487 self.ip_version = user_network_version
2488 else:
2489 LOG.warning("The enabled IP version for the admin "
2490 "network plugin is different from "
2491 "that of user network plugin, this "
2492 "may lead to the backend never being "
2493 "chosen by the scheduler when ip "
2494 "version is specified in the share "
2495 "type.")
2496 else:
2497 self.ip_version = user_network_version
2498 else:
2499 self.ip_version = self.get_configured_ip_version()
2500
2501 if not isinstance(self.ip_version, list):
2502 self.ip_version = [self.ip_version]
2503
2504 data['ipv4_support'] = (4 in self.ip_version) and ipv4_support
2505 data['ipv6_support'] = (6 in self.ip_version) and ipv6_support
2506 if not (data['ipv4_support'] or data['ipv6_support']):
2507 LOG.error("Backend %s capabilities 'ipv4_support' "
2508 "and 'ipv6_support' are both False.",
2509 data['share_backend_name'])
2510 return data
diff --git a/manila/share/drivers/helpers.py b/manila/share/drivers/helpers.py
index 0e37443..96452a5 100644
--- a/manila/share/drivers/helpers.py
+++ b/manila/share/drivers/helpers.py
@@ -13,6 +13,8 @@
13# License for the specific language governing permissions and limitations 13# License for the specific language governing permissions and limitations
14# under the License. 14# under the License.
15 15
16import copy
17import netaddr
16import os 18import os
17import re 19import re
18 20
@@ -183,7 +185,20 @@ class NFSHelper(NASHelperBase):
183 185
184 def create_exports(self, server, share_name, recreate=False): 186 def create_exports(self, server, share_name, recreate=False):
185 path = os.path.join(self.configuration.share_mount_path, share_name) 187 path = os.path.join(self.configuration.share_mount_path, share_name)
186 return self.get_exports_for_share(server, path) 188 server_copy = copy.copy(server)
189 public_addresses = []
190 if 'public_addresses' in server_copy:
191 for address in server_copy['public_addresses']:
192 public_addresses.append(
193 self._get_parsed_address_or_cidr(address))
194 server_copy['public_addresses'] = public_addresses
195
196 for t in ['public_address', 'admin_ip', 'ip']:
197 address = server_copy.get(t)
198 if address is not None:
199 server_copy[t] = self._get_parsed_address_or_cidr(address)
200
201 return self.get_exports_for_share(server_copy, path)
187 202
188 def init_helper(self, server): 203 def init_helper(self, server):
189 try: 204 try:
@@ -198,12 +213,6 @@ class NFSHelper(NASHelperBase):
198 def remove_exports(self, server, share_name): 213 def remove_exports(self, server, share_name):
199 """Remove exports.""" 214 """Remove exports."""
200 215
201 def _get_parsed_access_to(self, access_to):
202 netmask = utils.cidr_to_netmask(access_to)
203 if netmask == '255.255.255.255':
204 return access_to.split('/')[0]
205 return access_to.split('/')[0] + '/' + netmask
206
207 @nfs_synchronized 216 @nfs_synchronized
208 def update_access(self, server, share_name, access_rules, add_rules, 217 def update_access(self, server, share_name, access_rules, add_rules,
209 delete_rules): 218 delete_rules):
@@ -234,8 +243,9 @@ class NFSHelper(NASHelperBase):
234 server, 243 server,
235 ['sudo', 'exportfs', '-o', 244 ['sudo', 'exportfs', '-o',
236 rules_options % access['access_level'], 245 rules_options % access['access_level'],
237 ':'.join((self._get_parsed_access_to(access['access_to']), 246 ':'.join((
238 local_path))]) 247 self._get_parsed_address_or_cidr(access['access_to']),
248 local_path))])
239 self._sync_nfs_temp_and_perm_files(server) 249 self._sync_nfs_temp_and_perm_files(server)
240 # Adding/Deleting specific rules 250 # Adding/Deleting specific rules
241 else: 251 else:
@@ -245,7 +255,7 @@ class NFSHelper(NASHelperBase):
245 (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW)) 255 (const.ACCESS_LEVEL_RO, const.ACCESS_LEVEL_RW))
246 256
247 for access in delete_rules: 257 for access in delete_rules:
248 access['access_to'] = self._get_parsed_access_to( 258 access['access_to'] = self._get_parsed_address_or_cidr(
249 access['access_to']) 259 access['access_to'])
250 try: 260 try:
251 self.validate_access_rules( 261 self.validate_access_rules(
@@ -265,7 +275,7 @@ class NFSHelper(NASHelperBase):
265 if delete_rules: 275 if delete_rules:
266 self._sync_nfs_temp_and_perm_files(server) 276 self._sync_nfs_temp_and_perm_files(server)
267 for access in add_rules: 277 for access in add_rules:
268 access['access_to'] = self._get_parsed_access_to( 278 access['access_to'] = self._get_parsed_address_or_cidr(
269 access['access_to']) 279 access['access_to'])
270 found_item = re.search( 280 found_item = re.search(
271 re.escape(local_path) + '[\s\n]*' + re.escape( 281 re.escape(local_path) + '[\s\n]*' + re.escape(
@@ -290,6 +300,22 @@ class NFSHelper(NASHelperBase):
290 if add_rules: 300 if add_rules:
291 self._sync_nfs_temp_and_perm_files(server) 301 self._sync_nfs_temp_and_perm_files(server)
292 302
303 def _get_parsed_address_or_cidr(self, access_to):
304 try:
305 network = netaddr.IPNetwork(access_to)
306 except netaddr.AddrFormatError:
307 raise exception.InvalidInput(
308 reason=_("Invalid address or cidr supplied %s.") % access_to)
309 mask_length = network.netmask.netmask_bits()
310 address = access_to.split('/')[0]
311 if network.version == 4:
312 if mask_length == 32:
313 return address
314 return '%s/%s' % (address, mask_length)
315 if mask_length == 128:
316 return "[%s]" % address
317 return "[%s]/%s" % (address, mask_length)
318
293 @staticmethod 319 @staticmethod
294 def get_host_list(output, local_path): 320 def get_host_list(output, local_path):
295 entries = [] 321 entries = []
diff --git a/manila/share/drivers/lvm.py b/manila/share/drivers/lvm.py
index b8212e1..d74c4ba 100644
--- a/manila/share/drivers/lvm.py
+++ b/manila/share/drivers/lvm.py
@@ -18,6 +18,7 @@ LVM Driver for shares.
18 18
19""" 19"""
20 20
21import ipaddress
21import math 22import math
22import os 23import os
23import re 24import re
@@ -154,6 +155,7 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
154 self.configuration.share_mount_path = ( 155 self.configuration.share_mount_path = (
155 self.configuration.lvm_share_export_root) 156 self.configuration.lvm_share_export_root)
156 self._helpers = None 157 self._helpers = None
158 self.configured_ip_version = None
157 self.backend_name = self.configuration.safe_get( 159 self.backend_name = self.configuration.safe_get(
158 'share_backend_name') or 'LVM' 160 'share_backend_name') or 'LVM'
159 # Set of parameters used for compatibility with 161 # Set of parameters used for compatibility with
@@ -168,6 +170,7 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
168 else: 170 else:
169 self.share_server['public_addresses'] = ( 171 self.share_server['public_addresses'] = (
170 self.configuration.lvm_share_export_ips) 172 self.configuration.lvm_share_export_ips)
173 self.ipv6_implemented = True
171 174
172 def _ssh_exec_as_root(self, server, command, check_exit_code=True): 175 def _ssh_exec_as_root(self, server, command, check_exit_code=True):
173 kwargs = {} 176 kwargs = {}
@@ -212,7 +215,8 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
212 'revert_to_snapshot_support': True, 215 'revert_to_snapshot_support': True,
213 'mount_snapshot_support': True, 216 'mount_snapshot_support': True,
214 'driver_name': 'LVMShareDriver', 217 'driver_name': 'LVMShareDriver',
215 'pools': self.get_share_server_pools() 218 'pools': self.get_share_server_pools(),
219 'ipv6_support': True
216 } 220 }
217 super(LVMShareDriver, self)._update_share_stats(data) 221 super(LVMShareDriver, self)._update_share_stats(data)
218 222
@@ -431,6 +435,31 @@ class LVMShareDriver(LVMMixin, driver.ShareDriver):
431 super(LVMShareDriver, self).delete_snapshot(context, snapshot, 435 super(LVMShareDriver, self).delete_snapshot(context, snapshot,
432 share_server) 436 share_server)
433 437
438 def get_configured_ip_version(self):
439 """"Get Configured IP versions when DHSS is false."""
440 if self.configured_ip_version is None:
441 try:
442 self.configured_ip_version = []
443 if self.configuration.lvm_share_export_ip:
444 self.configured_ip_version.append(ipaddress.ip_address(
445 six.text_type(
446 self.configuration.lvm_share_export_ip)).version)
447 else:
448 for ip in self.configuration.lvm_share_export_ips:
449 self.configured_ip_version.append(
450 ipaddress.ip_address(six.text_type(ip)).version)
451 except Exception:
452 if self.configuration.lvm_share_export_ip:
453 message = (_("Invalid 'lvm_share_export_ip' option "
454 "supplied %s.") %
455 self.configuration.lvm_share_export_ip)
456 else:
457 message = (_("Invalid 'lvm_share_export_ips' option "
458 "supplied %s.") %
459 self.configuration.lvm_share_export_ips)
460 raise exception.InvalidInput(reason=message)
461 return self.configured_ip_version
462
434 def snapshot_update_access(self, context, snapshot, access_rules, 463 def snapshot_update_access(self, context, snapshot, access_rules,
435 add_rules, delete_rules, share_server=None): 464 add_rules, delete_rules, share_server=None):
436 """Update access rules for given snapshot. 465 """Update access rules for given snapshot.
diff --git a/manila/share/drivers/service_instance.py b/manila/share/drivers/service_instance.py
index cbd84d9..e1630bb 100644
--- a/manila/share/drivers/service_instance.py
+++ b/manila/share/drivers/service_instance.py
@@ -121,13 +121,13 @@ no_share_servers_handling_mode_opts = [
121 "service_net_name_or_ip", 121 "service_net_name_or_ip",
122 help="Can be either name of network that is used by service " 122 help="Can be either name of network that is used by service "
123 "instance within Nova to get IP address or IP address itself " 123 "instance within Nova to get IP address or IP address itself "
124 "for managing shares there. " 124 "(either IPv4 or IPv6) for managing shares there. "
125 "Used only when share servers handling is disabled."), 125 "Used only when share servers handling is disabled."),
126 cfg.HostAddressOpt( 126 cfg.HostAddressOpt(
127 "tenant_net_name_or_ip", 127 "tenant_net_name_or_ip",
128 help="Can be either name of network that is used by service " 128 help="Can be either name of network that is used by service "
129 "instance within Nova to get IP address or IP address itself " 129 "instance within Nova to get IP address or IP address itself "
130 "for exporting shares. " 130 "(either IPv4 or IPv6) for exporting shares. "
131 "Used only when share servers handling is disabled."), 131 "Used only when share servers handling is disabled."),
132] 132]
133 133
@@ -241,13 +241,13 @@ class ServiceInstanceManager(object):
241 self.admin_context, 241 self.admin_context,
242 self.get_config_option('service_instance_name_or_id')) 242 self.get_config_option('service_instance_name_or_id'))
243 243
244 if netutils.is_valid_ipv4(data['service_net_name_or_ip']): 244 if netutils.is_valid_ip(data['service_net_name_or_ip']):
245 data['private_address'] = [data['service_net_name_or_ip']] 245 data['private_address'] = [data['service_net_name_or_ip']]
246 else: 246 else:
247 data['private_address'] = self._get_addresses_by_network_name( 247 data['private_address'] = self._get_addresses_by_network_name(
248 data['service_net_name_or_ip'], data['instance']) 248 data['service_net_name_or_ip'], data['instance'])
249 249
250 if netutils.is_valid_ipv4(data['tenant_net_name_or_ip']): 250 if netutils.is_valid_ip(data['tenant_net_name_or_ip']):
251 data['public_address'] = [data['tenant_net_name_or_ip']] 251 data['public_address'] = [data['tenant_net_name_or_ip']]
252 else: 252 else:
253 data['public_address'] = self._get_addresses_by_network_name( 253 data['public_address'] = self._get_addresses_by_network_name(
@@ -267,13 +267,13 @@ class ServiceInstanceManager(object):
267 'instance_id': data['instance']['id'], 267 'instance_id': data['instance']['id'],
268 } 268 }
269 for key in ('private_address', 'public_address'): 269 for key in ('private_address', 'public_address'):
270 data[key + '_v4'] = None 270 data[key + '_first'] = None
271 for address in data[key]: 271 for address in data[key]:
272 if netutils.is_valid_ipv4(address): 272 if netutils.is_valid_ip(address):
273 data[key + '_v4'] = address 273 data[key + '_first'] = address
274 break 274 break
275 share_server['ip'] = data['private_address_v4'] 275 share_server['ip'] = data['private_address_first']
276 share_server['public_address'] = data['public_address_v4'] 276 share_server['public_address'] = data['public_address_first']
277 return {'backend_details': share_server} 277 return {'backend_details': share_server}
278 278
279 def _get_addresses_by_network_name(self, net_name, server): 279 def _get_addresses_by_network_name(self, net_name, server):
diff --git a/manila/tests/network/neutron/test_neutron_plugin.py b/manila/tests/network/neutron/test_neutron_plugin.py
index a0db5d1..896cb7d 100644
--- a/manila/tests/network/neutron/test_neutron_plugin.py
+++ b/manila/tests/network/neutron/test_neutron_plugin.py
@@ -44,7 +44,7 @@ fake_neutron_port = {
44 "binding:capabilities": {"port_filter": True}, 44 "binding:capabilities": {"port_filter": True},
45 "mac_address": "test_mac", 45 "mac_address": "test_mac",
46 "fixed_ips": [ 46 "fixed_ips": [
47 {"subnet_id": "test_subnet_id", "ip_address": "test_ip"}, 47 {"subnet_id": "test_subnet_id", "ip_address": "203.0.113.100"},
48 ], 48 ],
49 "id": "test_port_id", 49 "id": "test_port_id",
50 "security_groups": ["fake_sec_group_id"], 50 "security_groups": ["fake_sec_group_id"],
@@ -1505,6 +1505,7 @@ class NeutronBindNetworkPluginWithNormalTypeTest(test.TestCase):
1505 [fake_neutron_port], fake_share_server) 1505 [fake_neutron_port], fake_share_server)
1506 1506
1507 1507
1508@ddt.ddt
1508class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase): 1509class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase):
1509 def setUp(self): 1510 def setUp(self):
1510 super(NeutronBindSingleNetworkPluginWithNormalTypeTest, self).setUp() 1511 super(NeutronBindSingleNetworkPluginWithNormalTypeTest, self).setUp()
@@ -1588,3 +1589,40 @@ class NeutronBindSingleNetworkPluginWithNormalTypeTest(test.TestCase):
1588 1589
1589 self.bind_plugin._wait_for_ports_bind.assert_called_once_with( 1590 self.bind_plugin._wait_for_ports_bind.assert_called_once_with(
1590 [fake_neutron_port], fake_share_server) 1591 [fake_neutron_port], fake_share_server)
1592
1593 @ddt.data({'fix_ips': [{'ip_address': 'test_ip'},
1594 {'ip_address': '10.78.223.129'}],
1595 'ip_version': 4},
1596 {'fix_ips': [{'ip_address': 'test_ip'},
1597 {'ip_address': 'ad80::abaa:0:c2:2'}],
1598 'ip_version': 6},
1599 {'fix_ips': [{'ip_address': '10.78.223.129'},
1600 {'ip_address': 'ad80::abaa:0:c2:2'}],
1601 'ip_version': 6},
1602 )
1603 @ddt.unpack
1604 def test__get_matched_ip_address(self, fix_ips, ip_version):
1605 result = self.bind_plugin._get_matched_ip_address(fix_ips, ip_version)
1606 self.assertEqual(fix_ips[1]['ip_address'], result)
1607
1608 @ddt.data({'fix_ips': [{'ip_address': 'test_ip_1'},
1609 {'ip_address': 'test_ip_2'}],
1610 'ip_version': (4, 6)},
1611 {'fix_ips': [{'ip_address': 'ad80::abaa:0:c2:1'},
1612 {'ip_address': 'ad80::abaa:0:c2:2'}],
1613 'ip_version': (4, )},
1614 {'fix_ips': [{'ip_address': '192.0.0.2'},
1615 {'ip_address': '192.0.0.3'}],
1616 'ip_version': (6, )},
1617 {'fix_ips': [{'ip_address': '192.0.0.2/12'},
1618 {'ip_address': '192.0.0.330'},
1619 {'ip_address': 'ad80::001::ad80'},
1620 {'ip_address': 'ad80::abaa:0:c2:2/64'}],
1621 'ip_version': (4, 6)},
1622 )
1623 @ddt.unpack
1624 def test__get_matched_ip_address_illegal(self, fix_ips, ip_version):
1625 for version in ip_version:
1626 self.assertRaises(exception.NetworkBadConfigurationException,
1627 self.bind_plugin._get_matched_ip_address,
1628 fix_ips, version)
diff --git a/manila/tests/network/test_standalone_network_plugin.py b/manila/tests/network/test_standalone_network_plugin.py
index d79a70e..ec7410e 100644
--- a/manila/tests/network/test_standalone_network_plugin.py
+++ b/manila/tests/network/test_standalone_network_plugin.py
@@ -70,7 +70,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
70 'standalone_network_plugin_segmentation_id': 1001, 70 'standalone_network_plugin_segmentation_id': 1001,
71 'standalone_network_plugin_allowed_ip_ranges': ( 71 'standalone_network_plugin_allowed_ip_ranges': (
72 '10.0.0.3-10.0.0.7,10.0.0.69-10.0.0.157,10.0.0.213'), 72 '10.0.0.3-10.0.0.7,10.0.0.69-10.0.0.157,10.0.0.213'),
73 'standalone_network_plugin_ip_version': 4, 73 'network_plugin_ipv4_enabled': True,
74 }, 74 },
75 } 75 }
76 allowed_cidrs = [ 76 allowed_cidrs = [
@@ -104,7 +104,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
104 'standalone_network_plugin_gateway': ( 104 'standalone_network_plugin_gateway': (
105 '2001:cdba::3257:9652'), 105 '2001:cdba::3257:9652'),
106 'standalone_network_plugin_mask': '48', 106 'standalone_network_plugin_mask': '48',
107 'standalone_network_plugin_ip_version': 6, 107 'network_plugin_ipv6_enabled': True,
108 }, 108 },
109 } 109 }
110 with test_utils.create_temp_config_with_opts(data): 110 with test_utils.create_temp_config_with_opts(data):
@@ -138,7 +138,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
138 'standalone_network_plugin_segmentation_id': 3999, 138 'standalone_network_plugin_segmentation_id': 3999,
139 'standalone_network_plugin_allowed_ip_ranges': ( 139 'standalone_network_plugin_allowed_ip_ranges': (
140 '2001:db8::-2001:db8:0000:0000:0000:007f:ffff:ffff'), 140 '2001:db8::-2001:db8:0000:0000:0000:007f:ffff:ffff'),
141 'standalone_network_plugin_ip_version': 6, 141 'network_plugin_ipv6_enabled': True,
142 }, 142 },
143 } 143 }
144 with test_utils.create_temp_config_with_opts(data): 144 with test_utils.create_temp_config_with_opts(data):
@@ -168,7 +168,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
168 'standalone_network_plugin_mask': '255.255.0.0', 168 'standalone_network_plugin_mask': '255.255.0.0',
169 'standalone_network_plugin_network_type': network_type, 169 'standalone_network_plugin_network_type': network_type,
170 'standalone_network_plugin_segmentation_id': 1001, 170 'standalone_network_plugin_segmentation_id': 1001,
171 'standalone_network_plugin_ip_version': 4, 171 'network_plugin_ipv4_enabled': True,
172 }, 172 },
173 } 173 }
174 with test_utils.create_temp_config_with_opts(data): 174 with test_utils.create_temp_config_with_opts(data):
@@ -186,7 +186,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
186 'standalone_network_plugin_mask': '255.255.0.0', 186 'standalone_network_plugin_mask': '255.255.0.0',
187 'standalone_network_plugin_network_type': fake_network_type, 187 'standalone_network_plugin_network_type': fake_network_type,
188 'standalone_network_plugin_segmentation_id': 1001, 188 'standalone_network_plugin_segmentation_id': 1001,
189 'standalone_network_plugin_ip_version': 4, 189 'network_plugin_ipv4_enabled': True,
190 }, 190 },
191 } 191 }
192 with test_utils.create_temp_config_with_opts(data): 192 with test_utils.create_temp_config_with_opts(data):
@@ -255,10 +255,15 @@ class StandaloneNetworkPluginTest(test.TestCase):
255 data = { 255 data = {
256 group_name: { 256 group_name: {
257 'standalone_network_plugin_gateway': gateway, 257 'standalone_network_plugin_gateway': gateway,
258 'standalone_network_plugin_ip_version': vers,
259 'standalone_network_plugin_mask': '25', 258 'standalone_network_plugin_mask': '25',
260 }, 259 },
261 } 260 }
261 if vers == 4:
262 data[group_name]['network_plugin_ipv4_enabled'] = True
263 if vers == 6:
264 data[group_name]['network_plugin_ipv4_enabled'] = False
265 data[group_name]['network_plugin_ipv6_enabled'] = True
266
262 with test_utils.create_temp_config_with_opts(data): 267 with test_utils.create_temp_config_with_opts(data):
263 self.assertRaises( 268 self.assertRaises(
264 exception.NetworkBadConfigurationException, 269 exception.NetworkBadConfigurationException,
@@ -319,7 +324,7 @@ class StandaloneNetworkPluginTest(test.TestCase):
319 'DEFAULT': { 324 'DEFAULT': {
320 'standalone_network_plugin_gateway': '2001:db8::0001', 325 'standalone_network_plugin_gateway': '2001:db8::0001',
321 'standalone_network_plugin_mask': '64', 326 'standalone_network_plugin_mask': '64',
322 'standalone_network_plugin_ip_version': 6, 327 'network_plugin_ipv6_enabled': True,
323 }, 328 },
324 } 329 }
325 with test_utils.create_temp_config_with_opts(data): 330 with test_utils.create_temp_config_with_opts(data):
diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py
index 36a18c2..6bc2c5b 100644
--- a/manila/tests/scheduler/test_host_manager.py
+++ b/manila/tests/scheduler/test_host_manager.py
@@ -637,7 +637,9 @@ class HostStateTestCase(test.TestCase):
637 share_capability = {'total_capacity_gb': 0, 637 share_capability = {'total_capacity_gb': 0,
638 'free_capacity_gb': 100, 638 'free_capacity_gb': 100,
639 'reserved_percentage': 0, 639 'reserved_percentage': 0,
640 'timestamp': None} 640 'timestamp': None,
641 'ipv4_support': True,
642 'ipv6_support': False}
641 fake_host = host_manager.HostState('host1', share_capability) 643 fake_host = host_manager.HostState('host1', share_capability)
642 self.assertIsNone(fake_host.free_capacity_gb) 644 self.assertIsNone(fake_host.free_capacity_gb)
643 645
@@ -646,9 +648,13 @@ class HostStateTestCase(test.TestCase):
646 # Backend level stats remain uninitialized 648 # Backend level stats remain uninitialized
647 self.assertEqual(0, fake_host.total_capacity_gb) 649 self.assertEqual(0, fake_host.total_capacity_gb)
648 self.assertIsNone(fake_host.free_capacity_gb) 650 self.assertIsNone(fake_host.free_capacity_gb)
651 self.assertTrue(fake_host.ipv4_support)
652 self.assertFalse(fake_host.ipv6_support)
649 # Pool stats has been updated 653 # Pool stats has been updated
650 self.assertEqual(0, fake_host.pools['_pool0'].total_capacity_gb) 654 self.assertEqual(0, fake_host.pools['_pool0'].total_capacity_gb)
651 self.assertEqual(100, fake_host.pools['_pool0'].free_capacity_gb) 655 self.assertEqual(100, fake_host.pools['_pool0'].free_capacity_gb)
656 self.assertTrue(fake_host.pools['_pool0'].ipv4_support)
657 self.assertFalse(fake_host.pools['_pool0'].ipv6_support)
652 658
653 # Test update for existing host state 659 # Test update for existing host state
654 share_capability.update(dict(total_capacity_gb=1000)) 660 share_capability.update(dict(total_capacity_gb=1000))
@@ -674,6 +680,8 @@ class HostStateTestCase(test.TestCase):
674 'vendor_name': 'OpenStack', 680 'vendor_name': 'OpenStack',
675 'driver_version': '1.1', 681 'driver_version': '1.1',
676 'storage_protocol': 'NFS_CIFS', 682 'storage_protocol': 'NFS_CIFS',
683 'ipv4_support': True,
684 'ipv6_support': False,
677 'pools': [ 685 'pools': [
678 {'pool_name': 'pool1', 686 {'pool_name': 'pool1',
679 'total_capacity_gb': 500, 687 'total_capacity_gb': 500,
@@ -707,6 +715,8 @@ class HostStateTestCase(test.TestCase):
707 self.assertEqual('NFS_CIFS', fake_host.storage_protocol) 715 self.assertEqual('NFS_CIFS', fake_host.storage_protocol)
708 self.assertEqual('OpenStack', fake_host.vendor_name) 716 self.assertEqual('OpenStack', fake_host.vendor_name)
709 self.assertEqual('1.1', fake_host.driver_version) 717 self.assertEqual('1.1', fake_host.driver_version)
718 self.assertTrue(fake_host.ipv4_support)
719 self.assertFalse(fake_host.ipv6_support)
710 720
711 # Backend level stats remain uninitialized 721 # Backend level stats remain uninitialized
712 self.assertEqual(0, fake_host.total_capacity_gb) 722 self.assertEqual(0, fake_host.total_capacity_gb)
@@ -716,8 +726,12 @@ class HostStateTestCase(test.TestCase):
716 726
717 self.assertEqual(500, fake_host.pools['pool1'].total_capacity_gb) 727 self.assertEqual(500, fake_host.pools['pool1'].total_capacity_gb)
718 self.assertEqual(230, fake_host.pools['pool1'].free_capacity_gb) 728 self.assertEqual(230, fake_host.pools['pool1'].free_capacity_gb)
729 self.assertTrue(fake_host.pools['pool1'].ipv4_support)
730 self.assertFalse(fake_host.pools['pool1'].ipv6_support)
719 self.assertEqual(1024, fake_host.pools['pool2'].total_capacity_gb) 731 self.assertEqual(1024, fake_host.pools['pool2'].total_capacity_gb)
720 self.assertEqual(1024, fake_host.pools['pool2'].free_capacity_gb) 732 self.assertEqual(1024, fake_host.pools['pool2'].free_capacity_gb)
733 self.assertTrue(fake_host.pools['pool2'].ipv4_support)
734 self.assertFalse(fake_host.pools['pool2'].ipv6_support)
721 735
722 capability = { 736 capability = {
723 'share_backend_name': 'Backend1', 737 'share_backend_name': 'Backend1',
@@ -872,14 +886,17 @@ class PoolStateTestCase(test.TestCase):
872 'share_capability': 886 'share_capability':
873 {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 887 {'total_capacity_gb': 1024, 'free_capacity_gb': 512,
874 'reserved_percentage': 0, 'timestamp': None, 888 'reserved_percentage': 0, 'timestamp': None,
875 'cap1': 'val1', 'cap2': 'val2'}, 889 'cap1': 'val1', 'cap2': 'val2', 'ipv4_support': True,
890 'ipv6_support': False},
876 'instances': [] 891 'instances': []
877 }, 892 },
878 { 893 {
879 'share_capability': 894 'share_capability':
880 {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 895 {'total_capacity_gb': 1024, 'free_capacity_gb': 512,
881 'allocated_capacity_gb': 256, 'reserved_percentage': 0, 896 'allocated_capacity_gb': 256, 'reserved_percentage': 0,
882 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, 897 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2',
898 'ipv4_support': False, 'ipv6_support': True
899 },
883 'instances': 900 'instances':
884 [ 901 [
885 { 902 {
@@ -894,14 +911,17 @@ class PoolStateTestCase(test.TestCase):
894 'share_capability': 911 'share_capability':
895 {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 912 {'total_capacity_gb': 1024, 'free_capacity_gb': 512,
896 'allocated_capacity_gb': 256, 'reserved_percentage': 0, 913 'allocated_capacity_gb': 256, 'reserved_percentage': 0,
897 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, 914 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2',
915 'ipv4_support': True, 'ipv6_support': True},
898 'instances': [] 916 'instances': []
899 }, 917 },
900 { 918 {
901 'share_capability': 919 'share_capability':
902 {'total_capacity_gb': 1024, 'free_capacity_gb': 512, 920 {'total_capacity_gb': 1024, 'free_capacity_gb': 512,
903 'provisioned_capacity_gb': 256, 'reserved_percentage': 0, 921 'provisioned_capacity_gb': 256, 'reserved_percentage': 0,
904 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2'}, 922 'timestamp': None, 'cap1': 'val1', 'cap2': 'val2',
923 'ipv4_support': False, 'ipv6_support': False
924 },
905 'instances': 925 'instances':
906 [ 926 [
907 { 927 {
@@ -976,3 +996,9 @@ class PoolStateTestCase(test.TestCase):
976 fake_pool.allocated_capacity_gb) 996 fake_pool.allocated_capacity_gb)
977 self.assertEqual(share_capability['provisioned_capacity_gb'], 997 self.assertEqual(share_capability['provisioned_capacity_gb'],
978 fake_pool.provisioned_capacity_gb) 998 fake_pool.provisioned_capacity_gb)
999 if 'ipv4_support' in share_capability:
1000 self.assertEqual(share_capability['ipv4_support'],
1001 fake_pool.ipv4_support)
1002 if 'ipv6_support' in share_capability:
1003 self.assertEqual(share_capability['ipv6_support'],
1004 fake_pool.ipv6_support)
diff --git a/manila/tests/share/drivers/cephfs/test_driver.py b/manila/tests/share/drivers/cephfs/test_driver.py
index 326a24b..4ec1e86 100644
--- a/manila/tests/share/drivers/cephfs/test_driver.py
+++ b/manila/tests/share/drivers/cephfs/test_driver.py
@@ -336,6 +336,8 @@ class CephFSDriverTestCase(test.TestCase):
336 self._driver._update_share_stats() 336 self._driver._update_share_stats()
337 result = self._driver._stats 337 result = self._driver._stats
338 338
339 self.assertTrue(result['ipv4_support'])
340 self.assertFalse(result['ipv6_support'])
339 self.assertEqual("CEPHFS", result['storage_protocol']) 341 self.assertEqual("CEPHFS", result['storage_protocol'])
340 342
341 def test_module_missing(self): 343 def test_module_missing(self):
diff --git a/manila/tests/share/drivers/container/test_driver.py b/manila/tests/share/drivers/container/test_driver.py
index 57877ff..787f026 100644
--- a/manila/tests/share/drivers/container/test_driver.py
+++ b/manila/tests/share/drivers/container/test_driver.py
@@ -105,6 +105,8 @@ class ContainerShareDriverTestCase(test.TestCase):
105 self.assertEqual('ContainerShareDriver', 105 self.assertEqual('ContainerShareDriver',
106 self._driver._stats['driver_name']) 106 self._driver._stats['driver_name'])
107 self.assertEqual('test-pool', self._driver._stats['pools']) 107 self.assertEqual('test-pool', self._driver._stats['pools'])
108 self.assertTrue(self._driver._stats['ipv4_support'])
109 self.assertFalse(self._driver._stats['ipv6_support'])
108 110
109 def test_create_share(self): 111 def test_create_share(self):
110 helper = mock.Mock() 112 helper = mock.Mock()
diff --git a/manila/tests/share/drivers/dell_emc/test_driver.py b/manila/tests/share/drivers/dell_emc/test_driver.py
index f3837b6..1a259d7 100644
--- a/manila/tests/share/drivers/dell_emc/test_driver.py
+++ b/manila/tests/share/drivers/dell_emc/test_driver.py
@@ -16,7 +16,6 @@
16import mock 16import mock
17from stevedore import extension 17from stevedore import extension
18 18
19from manila import network
20from manila.share import configuration as conf 19from manila.share import configuration as conf
21from manila.share.drivers.dell_emc import driver as emcdriver 20from manila.share.drivers.dell_emc import driver as emcdriver
22from manila.share.drivers.dell_emc.plugins import base 21from manila.share.drivers.dell_emc.plugins import base
@@ -98,7 +97,6 @@ class EMCShareFrameworkTestCase(test.TestCase):
98 self.configuration.append_config_values = mock.Mock(return_value=0) 97 self.configuration.append_config_values = mock.Mock(return_value=0)
99 self.configuration.share_backend_name = FAKE_BACKEND 98 self.configuration.share_backend_name = FAKE_BACKEND
100 self.mock_object(self.configuration, 'safe_get', self._fake_safe_get) 99 self.mock_object(self.configuration, 'safe_get', self._fake_safe_get)
101 self.mock_object(network, 'API')
102 self.driver = emcdriver.EMCShareDriver( 100 self.driver = emcdriver.EMCShareDriver(
103 configuration=self.configuration) 101 configuration=self.configuration)
104 102
@@ -133,6 +131,8 @@ class EMCShareFrameworkTestCase(test.TestCase):
133 data['goodness_function'] = None 131 data['goodness_function'] = None
134 data['snapshot_support'] = True 132 data['snapshot_support'] = True
135 data['create_share_from_snapshot_support'] = True 133 data['create_share_from_snapshot_support'] = True
134 data['ipv4_support'] = True
135 data['ipv6_support'] = False
136 self.assertEqual(data, self.driver._stats) 136 self.assertEqual(data, self.driver._stats)
137 137
138 def _fake_safe_get(self, value): 138 def _fake_safe_get(self, value):
diff --git a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py
index 103d76d..9819389 100644
--- a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py
+++ b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py
@@ -265,6 +265,8 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase):
265 'replication_domain': None, 265 'replication_domain': None,
266 'filter_function': None, 266 'filter_function': None,
267 'goodness_function': None, 267 'goodness_function': None,
268 'ipv4_support': True,
269 'ipv6_support': False,
268 } 270 }
269 self.assertEqual(test_data, self._driver._stats) 271 self.assertEqual(test_data, self._driver._stats)
270 272
diff --git a/manila/tests/share/drivers/hdfs/test_hdfs_native.py b/manila/tests/share/drivers/hdfs/test_hdfs_native.py
index f7f7146..8debfbc 100644
--- a/manila/tests/share/drivers/hdfs/test_hdfs_native.py
+++ b/manila/tests/share/drivers/hdfs/test_hdfs_native.py
@@ -409,9 +409,12 @@ class HDFSNativeShareDriverTestCase(test.TestCase):
409 'free_capacity_gb', 'total_capacity_gb', 409 'free_capacity_gb', 'total_capacity_gb',
410 'driver_handles_share_servers', 410 'driver_handles_share_servers',
411 'reserved_percentage', 'vendor_name', 'storage_protocol', 411 'reserved_percentage', 'vendor_name', 'storage_protocol',
412 'ipv4_support', 'ipv6_support'
412 ] 413 ]
413 for key in expected_keys: 414 for key in expected_keys:
414 self.assertIn(key, result) 415 self.assertIn(key, result)
416 self.assertTrue(result['ipv4_support'])
417 self.assertFalse(False, result['ipv6_support'])
415 self.assertEqual('HDFS', result['storage_protocol']) 418 self.assertEqual('HDFS', result['storage_protocol'])
416 self._driver._get_available_capacity.assert_called_once_with() 419 self._driver._get_available_capacity.assert_called_once_with()
417 420
diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
index d23ed36..bad6971 100644
--- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
+++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py
@@ -747,6 +747,8 @@ class HPE3ParDriverTestCase(test.TestCase):
747 'replication_domain': None, 747 'replication_domain': None,
748 'filter_function': None, 748 'filter_function': None,
749 'goodness_function': None, 749 'goodness_function': None,
750 'ipv4_support': True,
751 'ipv6_support': False,
750 } 752 }
751 753
752 result = self.driver.get_share_stats(refresh=True) 754 result = self.driver.get_share_stats(refresh=True)
@@ -822,6 +824,8 @@ class HPE3ParDriverTestCase(test.TestCase):
822 'replication_domain': None, 824 'replication_domain': None,
823 'filter_function': None, 825 'filter_function': None,
824 'goodness_function': None, 826 'goodness_function': None,
827 'ipv4_support': True,
828 'ipv6_support': False,
825 } 829 }
826 830
827 result = self.driver.get_share_stats(refresh=True) 831 result = self.driver.get_share_stats(refresh=True)
@@ -864,6 +868,8 @@ class HPE3ParDriverTestCase(test.TestCase):
864 'replication_domain': None, 868 'replication_domain': None,
865 'filter_function': None, 869 'filter_function': None,
866 'goodness_function': None, 870 'goodness_function': None,
871 'ipv4_support': True,
872 'ipv6_support': False,
867 } 873 }
868 874
869 result = self.driver.get_share_stats(refresh=True) 875 result = self.driver.get_share_stats(refresh=True)
diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py
index f38a6a2..b6e74b2 100644
--- a/manila/tests/share/drivers/huawei/test_huawei_nas.py
+++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py
@@ -2431,6 +2431,8 @@ class HuaweiShareDriverTestCase(test.TestCase):
2431 "goodness_function": None, 2431 "goodness_function": None,
2432 "pools": [], 2432 "pools": [],
2433 "share_group_stats": {"consistent_snapshot_support": None}, 2433 "share_group_stats": {"consistent_snapshot_support": None},
2434 "ipv4_support": True,
2435 "ipv6_support": False,
2434 } 2436 }
2435 2437
2436 if replication_support: 2438 if replication_support:
diff --git a/manila/tests/share/drivers/test_helpers.py b/manila/tests/share/drivers/test_helpers.py
index 404640d..a4822aa 100644
--- a/manila/tests/share/drivers/test_helpers.py
+++ b/manila/tests/share/drivers/test_helpers.py
@@ -81,26 +81,55 @@ class NFSHelperTestCase(test.TestCase):
81 self.server, ['sudo', 'exportfs']) 81 self.server, ['sudo', 'exportfs'])
82 82
83 @ddt.data( 83 @ddt.data(
84 {"public_address": "1.2.3.4"}, 84 {"server": {"public_address": "1.2.3.4"}, "version": 4},
85 {"public_address": "1.2.3.4", "admin_ip": "5.6.7.8"}, 85 {"server": {"public_address": "1001::1002"}, "version": 6},
86 {"public_address": "1.2.3.4", "ip": "9.10.11.12"}, 86 {"server": {"public_address": "1.2.3.4", "admin_ip": "5.6.7.8"},
87 "version": 4},
88 {"server": {"public_address": "1.2.3.4", "ip": "9.10.11.12"},
89 "version": 4},
90 {"server": {"public_address": "1001::1001", "ip": "1001::1002"},
91 "version": 6},
92 {"server": {"public_address": "1001::1002", "admin_ip": "1001::1002"},
93 "version": 6},
94 {"server": {"public_addresses": ["1001::1002"]}, "version": 6},
95 {"server": {"public_addresses": ["1.2.3.4", "1001::1002"]},
96 "version": {"1.2.3.4": 4, "1001::1002": 6}},
87 ) 97 )
88 def test_create_exports(self, server): 98 @ddt.unpack
99 def test_create_exports(self, server, version):
89 result = self._helper.create_exports(server, self.share_name) 100 result = self._helper.create_exports(server, self.share_name)
90 101
91 expected_export_locations = [] 102 expected_export_locations = []
92 path = os.path.join(CONF.share_mount_path, self.share_name) 103 path = os.path.join(CONF.share_mount_path, self.share_name)
93 service_address = server.get("admin_ip", server.get("ip")) 104 service_address = server.get("admin_ip", server.get("ip"))
94 for ip, is_admin in ((server['public_address'], False), 105 version_copy = version
95 (service_address, True)): 106
96 if ip: 107 def convert_address(address, version):
97 expected_export_locations.append({ 108 if version == 4:
98 "path": "%s:%s" % (ip, path), 109 return address
99 "is_admin_only": is_admin, 110 return "[%s]" % address
100 "metadata": { 111
101 "export_location_metadata_example": "example", 112 if 'public_addresses' in server:
102 }, 113 pairs = list(map(lambda addr: (addr, False),
103 }) 114 server['public_addresses']))
115 else:
116 pairs = [(server['public_address'], False)]
117
118 service_address = server.get("admin_ip", server.get("ip"))
119 if service_address:
120 pairs.append((service_address, True))
121
122 for ip, is_admin in pairs:
123 if isinstance(version_copy, dict):
124 version = version_copy.get(ip)
125
126 expected_export_locations.append({
127 "path": "%s:%s" % (convert_address(ip, version), path),
128 "is_admin_only": is_admin,
129 "metadata": {
130 "export_location_metadata_example": "example",
131 },
132 })
104 self.assertEqual(expected_export_locations, result) 133 self.assertEqual(expected_export_locations, result)
105 134
106 @ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO) 135 @ddt.data(const.ACCESS_LEVEL_RW, const.ACCESS_LEVEL_RO)
@@ -135,19 +164,36 @@ class NFSHelperTestCase(test.TestCase):
135 mock.call(self.server, ['sudo', 'exportfs', '-u', 164 mock.call(self.server, ['sudo', 'exportfs', '-u',
136 ':'.join(['3.3.3.3', local_path])]), 165 ':'.join(['3.3.3.3', local_path])]),
137 mock.call(self.server, ['sudo', 'exportfs', '-u', 166 mock.call(self.server, ['sudo', 'exportfs', '-u',
138 ':'.join(['6.6.6.6/0.0.0.0', 167 ':'.join(['6.6.6.6/0',
139 local_path])]), 168 local_path])]),
140 mock.call(self.server, ['sudo', 'exportfs', '-o', 169 mock.call(self.server, ['sudo', 'exportfs', '-o',
141 expected_mount_options % access_level, 170 expected_mount_options % access_level,
142 ':'.join(['2.2.2.2', local_path])]), 171 ':'.join(['2.2.2.2', local_path])]),
143 mock.call(self.server, ['sudo', 'exportfs', '-o', 172 mock.call(self.server, ['sudo', 'exportfs', '-o',
144 expected_mount_options % access_level, 173 expected_mount_options % access_level,
145 ':'.join(['5.5.5.5/255.255.255.0', 174 ':'.join(['5.5.5.5/24',
146 local_path])]), 175 local_path])]),
147 ]) 176 ])
148 self._helper._sync_nfs_temp_and_perm_files.assert_has_calls([ 177 self._helper._sync_nfs_temp_and_perm_files.assert_has_calls([
149 mock.call(self.server), mock.call(self.server)]) 178 mock.call(self.server), mock.call(self.server)])
150 179
180 @ddt.data({'access': '10.0.0.1', 'result': '10.0.0.1'},
181 {'access': '10.0.0.1/32', 'result': '10.0.0.1'},
182 {'access': '10.0.0.0/24', 'result': '10.0.0.0/24'},
183 {'access': '1001::1001', 'result': '[1001::1001]'},
184 {'access': '1001::1000/128', 'result': '[1001::1000]'},
185 {'access': '1001::1000/124', 'result': '[1001::1000]/124'})
186 @ddt.unpack
187 def test__get_parsed_address_or_cidr(self, access, result):
188 self.assertEqual(result,
189 self._helper._get_parsed_address_or_cidr(access))
190
191 @ddt.data('10.0.0.265', '10.0.0.1/33', '1001::10069', '1001::1000/129')
192 def test__get_parsed_address_or_cidr_with_invalid_access(self, access):
193 self.assertRaises(exception.InvalidInput,
194 self._helper._get_parsed_address_or_cidr,
195 access)
196
151 def test_update_access_invalid_type(self): 197 def test_update_access_invalid_type(self):
152 access_rules = [test_generic.get_fake_access_rule( 198 access_rules = [test_generic.get_fake_access_rule(
153 '2.2.2.2', const.ACCESS_LEVEL_RW, access_type='fake'), ] 199 '2.2.2.2', const.ACCESS_LEVEL_RW, access_type='fake'), ]
@@ -215,7 +261,8 @@ class NFSHelperTestCase(test.TestCase):
215 self._helper._ssh_exec.assert_has_calls( 261 self._helper._ssh_exec.assert_has_calls(
216 [mock.call(self.server, mock.ANY) for i in range(1)]) 262 [mock.call(self.server, mock.ANY) for i in range(1)])
217 263
218 @ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.9:/foo/quuz') 264 @ddt.data('/foo/bar', '5.6.7.8:/bar/quuz', '5.6.7.9:/foo/quuz',
265 '[1001::1001]:/foo/bar', '[1001::1000]/:124:/foo/bar')
219 def test_get_exports_for_share_single_ip(self, export_location): 266 def test_get_exports_for_share_single_ip(self, export_location):
220 server = dict(public_address='1.2.3.4') 267 server = dict(public_address='1.2.3.4')
221 268
@@ -257,7 +304,8 @@ class NFSHelperTestCase(test.TestCase):
257 exception.ManilaException, 304 exception.ManilaException,
258 self._helper.get_exports_for_share, server, export_location) 305 self._helper.get_exports_for_share, server, export_location)
259 306
260 @ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar') 307 @ddt.data('/foo/bar', '5.6.7.8:/foo/bar', '5.6.7.88:fake:/foo/bar',
308 '[1001::1002]:/foo/bar', '[1001::1000]/124:/foo/bar')
261 def test_get_share_path_by_export_location(self, export_location): 309 def test_get_share_path_by_export_location(self, export_location):
262 result = self._helper.get_share_path_by_export_location( 310 result = self._helper.get_share_path_by_export_location(
263 dict(), export_location) 311 dict(), export_location)
diff --git a/manila/tests/share/drivers/test_lvm.py b/manila/tests/share/drivers/test_lvm.py
index 3708add..f10fe91 100644
--- a/manila/tests/share/drivers/test_lvm.py
+++ b/manila/tests/share/drivers/test_lvm.py
@@ -416,6 +416,23 @@ class LVMShareDriverTestCase(test.TestCase):
416 self.server, self.share['name'], 416 self.server, self.share['name'],
417 access_rules, add_rules=add_rules, delete_rules=delete_rules)) 417 access_rules, add_rules=add_rules, delete_rules=delete_rules))
418 418
419 @ddt.data(('1001::1001/129', None, False), ('1.1.1.256', None, False),
420 ('1001::1001', None, [6]), ('1.1.1.0', None, [4]),
421 (None, ['1001::1001', '1.1.1.0'], [6, 4]),
422 (None, ['1001::1001'], [6]), (None, ['1.1.1.0'], [4]),
423 (None, ['1001::1001/129', '1.1.1.0'], False))
424 @ddt.unpack
425 def test_get_configured_ip_version(
426 self, configured_ip, configured_ips, configured_ip_version):
427 CONF.set_default('lvm_share_export_ip', configured_ip)
428 CONF.set_default('lvm_share_export_ips', configured_ips)
429 if configured_ip_version:
430 self.assertEqual(configured_ip_version,
431 self._driver.get_configured_ip_version())
432 else:
433 self.assertRaises(exception.InvalidInput,
434 self._driver.get_configured_ip_version)
435
419 def test_mount_device(self): 436 def test_mount_device(self):
420 mount_path = self._get_mount_path(self.share) 437 mount_path = self._get_mount_path(self.share)
421 ret = self._driver._mount_device(self.share, 'fakedevice') 438 ret = self._driver._mount_device(self.share, 'fakedevice')
@@ -541,7 +558,10 @@ class LVMShareDriverTestCase(test.TestCase):
541 'count=1024', 'bs=1M', 558 'count=1024', 'bs=1M',
542 run_as_root=True) 559 run_as_root=True)
543 560
544 def test_update_share_stats(self): 561 @ddt.data(('1.1.1.1', 4), ('1001::1001', 6))
562 @ddt.unpack
563 def test_update_share_stats(self, configured_ip, version):
564 CONF.set_default('lvm_share_export_ip', configured_ip)
545 self.mock_object(self._driver, 'get_share_server_pools', 565 self.mock_object(self._driver, 'get_share_server_pools',
546 mock.Mock(return_value='test-pool')) 566 mock.Mock(return_value='test-pool'))
547 567
@@ -552,6 +572,8 @@ class LVMShareDriverTestCase(test.TestCase):
552 self.assertTrue(self._driver._stats['snapshot_support']) 572 self.assertTrue(self._driver._stats['snapshot_support'])
553 self.assertEqual('LVMShareDriver', self._driver._stats['driver_name']) 573 self.assertEqual('LVMShareDriver', self._driver._stats['driver_name'])
554 self.assertEqual('test-pool', self._driver._stats['pools']) 574 self.assertEqual('test-pool', self._driver._stats['pools'])
575 self.assertEqual(version == 4, self._driver._stats['ipv4_support'])
576 self.assertEqual(version == 6, self._driver._stats['ipv6_support'])
555 577
556 def test_revert_to_snapshot(self): 578 def test_revert_to_snapshot(self):
557 mock_update_access = self.mock_object(self._helper_nfs, 579 mock_update_access = self.mock_object(self._helper_nfs,
diff --git a/manila/tests/share/drivers/test_service_instance.py b/manila/tests/share/drivers/test_service_instance.py
index 1a06cfa..da34dc8 100644
--- a/manila/tests/share/drivers/test_service_instance.py
+++ b/manila/tests/share/drivers/test_service_instance.py
@@ -779,8 +779,10 @@ class ServiceInstanceManagerTestCase(test.TestCase):
779 fake_server_details) 779 fake_server_details)
780 780
781 @ddt.data( 781 @ddt.data(
782 *[{'s': s, 't': t, 'server': server} 782 *[{'service_config': service_config,
783 for s, t in ( 783 'tenant_config': tenant_config,
784 'server': server}
785 for service_config, tenant_config in (
784 ('fake_net_s', 'fake_net_t'), 786 ('fake_net_s', 'fake_net_t'),
785 ('fake_net_s', '12.34.56.78'), 787 ('fake_net_s', '12.34.56.78'),
786 ('98.76.54.123', 'fake_net_t'), 788 ('98.76.54.123', 'fake_net_t'),
@@ -800,18 +802,25 @@ class ServiceInstanceManagerTestCase(test.TestCase):
800 {'addr': 'fake4'}], 802 {'addr': 'fake4'}],
801 }})]) 803 }})])
802 @ddt.unpack 804 @ddt.unpack
803 def test_get_common_server_valid_cases(self, s, t, server): 805 def test_get_common_server_valid_cases(self, service_config,
804 self._get_common_server(s, t, server, True) 806 tenant_config, server):
807 self._get_common_server(service_config, tenant_config, server,
808 '98.76.54.123', '12.34.56.78', True)
805 809
806 @ddt.data( 810 @ddt.data(
807 *[{'s': s, 't': t, 'server': server} 811 *[{'service_config': service_config,
808 for s, t in ( 812 'tenant_config': tenant_config,
813 'server': server}
814 for service_config, tenant_config in (
809 ('fake_net_s', 'fake'), 815 ('fake_net_s', 'fake'),
810 ('fake', 'fake_net_t'), 816 ('fake', 'fake_net_t'),
811 ('fake', 'fake'), 817 ('fake', 'fake'),
812 ('98.76.54.123', '12.12.12.1212'), 818 ('98.76.54.123', '12.12.12.1212'),
813 ('12.12.12.1212', '12.34.56.78'), 819 ('12.12.12.1212', '12.34.56.78'),
814 ('12.12.12.1212', '12.12.12.1212')) 820 ('12.12.12.1212', '12.12.12.1212'),
821 ('1001::1001', '1001::100G'),
822 ('1001::10G1', '1001::1001'),
823 )
815 for server in ( 824 for server in (
816 {'networks': { 825 {'networks': {
817 'fake_net_s': ['foo', '98.76.54.123', 'bar'], 826 'fake_net_s': ['foo', '98.76.54.123', 'bar'],
@@ -827,15 +836,38 @@ class ServiceInstanceManagerTestCase(test.TestCase):
827 {'addr': 'fake4'}], 836 {'addr': 'fake4'}],
828 }})]) 837 }})])
829 @ddt.unpack 838 @ddt.unpack
830 def test_get_common_server_invalid_cases(self, s, t, server): 839 def test_get_common_server_invalid_cases(self, service_config,
831 self._get_common_server(s, t, server, False) 840 tenant_config, server):
841 self._get_common_server(service_config, tenant_config, server,
842 '98.76.54.123', '12.34.56.78', False)
832 843
833 def _get_common_server(self, s, t, server, is_valid=True): 844 @ddt.data(
845 *[{'service_config': service_config,
846 'tenant_config': tenant_config,
847 'server': server}
848 for service_config, tenant_config in (
849 ('fake_net_s', '1001::1002'),
850 ('1001::1001', 'fake_net_t'),
851 ('1001::1001', '1001::1002'))
852 for server in (
853 {'networks': {
854 'fake_net_s': ['foo', '1001::1001'],
855 'fake_net_t': ['bar', '1001::1002']}},
856 {'addresses': {
857 'fake_net_s': [{'addr': 'foo'}, {'addr': '1001::1001'}],
858 'fake_net_t': [{'addr': 'bar'}, {'addr': '1001::1002'}]}})])
859 @ddt.unpack
860 def test_get_common_server_valid_ipv6_address(self, service_config,
861 tenant_config, server):
862 self._get_common_server(service_config, tenant_config, server,
863 '1001::1001', '1001::1002', True)
864
865 def _get_common_server(self, service_config, tenant_config,
866 server, service_address, network_address,
867 is_valid=True):
834 fake_instance_id = 'fake_instance_id' 868 fake_instance_id = 'fake_instance_id'
835 fake_user = 'fake_user' 869 fake_user = 'fake_user'
836 fake_pass = 'fake_pass' 870 fake_pass = 'fake_pass'
837 fake_addr_s = '98.76.54.123'
838 fake_addr_t = '12.34.56.78'
839 fake_server = {'id': fake_instance_id} 871 fake_server = {'id': fake_instance_id}
840 fake_server.update(server) 872 fake_server.update(server)
841 expected = { 873 expected = {
@@ -843,17 +875,17 @@ class ServiceInstanceManagerTestCase(test.TestCase):
843 'username': fake_user, 875 'username': fake_user,
844 'password': fake_pass, 876 'password': fake_pass,
845 'pk_path': self._manager.path_to_private_key, 877 'pk_path': self._manager.path_to_private_key,
846 'ip': fake_addr_s, 878 'ip': service_address,
847 'public_address': fake_addr_t, 879 'public_address': network_address,
848 'instance_id': fake_instance_id, 880 'instance_id': fake_instance_id,
849 } 881 }
850 } 882 }
851 883
852 def fake_get_config_option(attr): 884 def fake_get_config_option(attr):
853 if attr == 'service_net_name_or_ip': 885 if attr == 'service_net_name_or_ip':
854 return s 886 return service_config
855 elif attr == 'tenant_net_name_or_ip': 887 elif attr == 'tenant_net_name_or_ip':
856 return t 888 return tenant_config
857 elif attr == 'service_instance_name_or_id': 889 elif attr == 'service_instance_name_or_id':
858 return fake_instance_id 890 return fake_instance_id
859 elif attr == 'service_instance_user': 891 elif attr == 'service_instance_user':
diff --git a/manila/tests/share/drivers/zfsonlinux/test_driver.py b/manila/tests/share/drivers/zfsonlinux/test_driver.py
index eb4b048..a91c970 100644
--- a/manila/tests/share/drivers/zfsonlinux/test_driver.py
+++ b/manila/tests/share/drivers/zfsonlinux/test_driver.py
@@ -362,6 +362,8 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase):
362 'vendor_name': 'Open Source', 362 'vendor_name': 'Open Source',
363 'filter_function': None, 363 'filter_function': None,
364 'goodness_function': None, 364 'goodness_function': None,
365 'ipv4_support': True,
366 'ipv6_support': False,
365 } 367 }
366 if replication_domain: 368 if replication_domain:
367 expected['replication_type'] = 'readable' 369 expected['replication_type'] = 'readable'
diff --git a/manila/tests/share/test_access.py b/manila/tests/share/test_access.py
index 5eac98f..3234a01 100644
--- a/manila/tests/share/test_access.py
+++ b/manila/tests/share/test_access.py
@@ -720,3 +720,56 @@ class ShareInstanceAccessTestCase(test.TestCase):
720 else: 720 else:
721 self.assertEqual(states[0], rule_1['state']) 721 self.assertEqual(states[0], rule_1['state'])
722 self.assertEqual(states[-1], rule_4['state']) 722 self.assertEqual(states[-1], rule_4['state'])
723
724 @ddt.data(('nfs', True), ('cifs', False), ('ceph', False))
725 @ddt.unpack
726 def test__filter_ipv6_rules(self, proto, filtered):
727 pass_rules = [
728 {
729 'access_type': 'ip',
730 'access_to': '1.1.1.1'
731 },
732 {
733 'access_type': 'ip',
734 'access_to': '1.1.1.0/24'
735 },
736 {
737 'access_type': 'user',
738 'access_to': 'fake_user'
739 },
740 ]
741 fail_rules = [
742 {
743 'access_type': 'ip',
744 'access_to': '1001::1001'
745 },
746 {
747 'access_type': 'ip',
748 'access_to': '1001::/64'
749 },
750 ]
751 test_rules = pass_rules + fail_rules
752 filtered_rules = self.access_helper._filter_ipv6_rules(
753 test_rules, proto)
754 if filtered:
755 self.assertEqual(pass_rules, filtered_rules)
756 else:
757 self.assertEqual(test_rules, filtered_rules)
758
759 def test__get_rules_to_send_to_driver(self):
760 self.driver.ipv6_implemented = False
761
762 share = db_utils.create_share(status=constants.STATUS_AVAILABLE)
763 share_instance = share['instance']
764 db_utils.create_access(share_id=share['id'], access_to='1001::/64',
765 state=constants.ACCESS_STATE_ACTIVE)
766 self.mock_object(
767 self.access_helper, 'get_and_update_share_instance_access_rules',
768 mock.Mock(side_effect=self.access_helper.
769 get_and_update_share_instance_access_rules))
770
771 access_rules_to_be_on_share, add_rules, delete_rules = (
772 self.access_helper._get_rules_to_send_to_driver(
773 self.context, share_instance))
774 self.assertEqual([], add_rules)
775 self.assertEqual([], delete_rules)
diff --git a/manila/tests/share/test_driver.py b/manila/tests/share/test_driver.py
index fed846b..4a39085 100644
--- a/manila/tests/share/test_driver.py
+++ b/manila/tests/share/test_driver.py
@@ -19,6 +19,7 @@ import time
19 19
20import ddt 20import ddt
21import mock 21import mock
22from mock import PropertyMock
22 23
23from manila import exception 24from manila import exception
24from manila import network 25from manila import network
@@ -1083,3 +1084,71 @@ class ShareDriverTestCase(test.TestCase):
1083 share_driver.snapshot_update_access, 1084 share_driver.snapshot_update_access,
1084 'fake_context', 'fake_snapshot', ['r1', 'r2'], 1085 'fake_context', 'fake_snapshot', ['r1', 'r2'],
1085 [], []) 1086 [], [])
1087
1088 @ddt.data({'capability': (True, True),
1089 'user_admin_networks': [[4], [4]],
1090 'expected': {'ipv4': True, 'ipv6': False}},
1091 {'capability': (True, True),
1092 'user_admin_networks': [[6], [6]],
1093 'expected': {'ipv4': False, 'ipv6': True}},
1094 {'capability': (False, False),
1095 'user_admin_networks': [[4], [4]],
1096 'expected': {'ipv4': False, 'ipv6': False}},
1097 {'capability': (True, True),
1098 'user_admin_networks': [[4], [6]],
1099 'expected': {'ipv4': False, 'ipv6': False}},
1100 {'capability': (False, False),
1101 'user_admin_networks': [[6], [4]],
1102 'expected': {'ipv4': False, 'ipv6': False}},)
1103 @ddt.unpack
1104 def test_add_ip_version_capability_if_dhss_true(self, capability,
1105 user_admin_networks,
1106 expected):
1107 share_driver = self._instantiate_share_driver(None, True)
1108 version = PropertyMock(side_effect=user_admin_networks)
1109 type(share_driver.network_api).enabled_ip_version = version
1110 data = {'share_backend_name': 'fake_backend',
1111 'ipv4_support': capability[0],
1112 'ipv6_support': capability[1]}
1113
1114 result = share_driver.add_ip_version_capability(data)
1115
1116 self.assertIsNotNone(result['ipv4_support'])
1117 self.assertEqual(expected['ipv4'], result['ipv4_support'])
1118 self.assertIsNotNone(result['ipv6_support'])
1119 self.assertEqual(expected['ipv6'], result['ipv6_support'])
1120
1121 @ddt.data({'capability': (True, False),
1122 'conf': [4],
1123 'expected': {'ipv4': True, 'ipv6': False}},
1124 {'capability': (True, True),
1125 'conf': [6],
1126 'expected': {'ipv4': False, 'ipv6': True}},
1127 {'capability': (False, False),
1128 'conf': [4],
1129 'expected': {'ipv4': False, 'ipv6': False}},
1130 {'capability': (False, True),
1131 'conf': [4],
1132 'expected': {'ipv4': False, 'ipv6': False}},
1133 {'capability': (False, True),
1134 'conf': [6],
1135 'expected': {'ipv4': False, 'ipv6': True}},
1136 {'capability': (True, True),
1137 'conf': [4, 6],
1138 'expected': {'ipv4': True, 'ipv6': True}},
1139 )
1140 @ddt.unpack
1141 def test_add_ip_version_capability_if_dhss_false(self, capability,
1142 conf, expected):
1143 share_driver = self._instantiate_share_driver(None, False)
1144 self.mock_object(share_driver, 'get_configured_ip_version',
1145 mock.Mock(return_value=conf))
1146 data = {'share_backend_name': 'fake_backend',
1147 'ipv4_support': capability[0],
1148 'ipv6_support': capability[1]}
1149 result = share_driver.add_ip_version_capability(data)
1150
1151 self.assertIsNotNone(result['ipv4_support'])
1152 self.assertEqual(expected['ipv4'], result['ipv4_support'])
1153 self.assertIsNotNone(result['ipv6_support'])
1154 self.assertEqual(expected['ipv6'], result['ipv6_support'])
diff --git a/manila/tests/test_network.py b/manila/tests/test_network.py
index 8390de9..b3b03b2 100644
--- a/manila/tests/test_network.py
+++ b/manila/tests/test_network.py
@@ -128,3 +128,30 @@ class NetworkBaseAPITestCase(test.TestCase):
128 self.assertRaises( 128 self.assertRaises(
129 exception.NetworkBadConfigurationException, 129 exception.NetworkBadConfigurationException,
130 result._verify_share_network, 'foo_id', None) 130 result._verify_share_network, 'foo_id', None)
131
132 @ddt.data((True, False, 6), (False, True, 4),
133 (True, True, 6), (None, None, False))
134 @ddt.unpack
135 def test_enabled_ip_version(self, network_plugin_ipv6_enabled,
136 network_plugin_ipv4_enabled,
137 enable_ip_version):
138 class FakeNetworkAPI(network.NetworkBaseAPI):
139 def allocate_network(self, *args, **kwargs):
140 pass
141
142 def deallocate_network(self, *args, **kwargs):
143 pass
144
145 network.CONF.set_default('network_plugin_ipv6_enabled',
146 network_plugin_ipv6_enabled)
147 network.CONF.set_default('network_plugin_ipv4_enabled',
148 network_plugin_ipv4_enabled)
149
150 result = FakeNetworkAPI()
151
152 if enable_ip_version:
153 self.assertTrue(hasattr(result, 'enabled_ip_version'))
154 self.assertEqual(enable_ip_version, result.enabled_ip_version)
155 else:
156 self.assertRaises(exception.NetworkBadConfigurationException,
157 getattr, result, 'enabled_ip_version')
diff --git a/manila/utils.py b/manila/utils.py
index 3be8e74..731d2ad 100644
--- a/manila/utils.py
+++ b/manila/utils.py
@@ -386,14 +386,22 @@ def cidr_to_netmask(cidr):
386 386
387 387
388def is_valid_ip_address(ip_address, ip_version): 388def is_valid_ip_address(ip_address, ip_version):
389 if int(ip_version) == 4: 389 ip_version = ([int(ip_version)] if not isinstance(ip_version, list)
390 return netutils.is_valid_ipv4(ip_address) 390 else ip_version)
391 elif int(ip_version) == 6: 391
392 return netutils.is_valid_ipv6(ip_address) 392 if not set(ip_version).issubset(set([4, 6])):
393 else:
394 raise exception.ManilaException( 393 raise exception.ManilaException(
395 _("Provided improper IP version '%s'.") % ip_version) 394 _("Provided improper IP version '%s'.") % ip_version)
396 395
396 if 4 in ip_version:
397 if netutils.is_valid_ipv4(ip_address):
398 return True
399 if 6 in ip_version:
400 if netutils.is_valid_ipv6(ip_address):
401 return True
402
403 return False
404
397 405
398class IsAMatcher(object): 406class IsAMatcher(object):
399 def __init__(self, expected_value=None): 407 def __init__(self, expected_value=None):
diff --git a/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml b/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml
new file mode 100644
index 0000000..d96a105
--- /dev/null
+++ b/releasenotes/notes/support-ipv6-in-drivers-and-network-plugins-1833121513edb13d.yaml
@@ -0,0 +1,8 @@
1---
2features:
3 - Added optional extra spec 'ipv4_support' and 'ipv6_support' for share
4 type.
5 - Added new capabilities 'ipv4_support' and 'ipv6_support' for IP based
6 drivers.
7 - Added IPv6 support in network plugins. (support either IPv6 or IPv4)
8 - Added IPv6 support in the lvm driver. (support both IPv6 and IPv4)