Add standalone network plugin
This network plugin can be used with any network platform. Features: - Serves flat networks as well as segmented. - Does not require some specific network services in OpenStack like Neutron or Nova. - Can handle IPv6 as well as IPv4 - The only thing that plugin does is reservation and release of IP addresses from some network. Can be configured using following options: - standalone_network_plugin_gateway [required] - standalone_network_plugin_mask [required] - standalone_network_plugin_segmentation_id [optional] [default: None] - standalone_network_plugin_allowed_ip_ranges [optional] [default: None] - standalone_network_plugin_ip_version [optional] [default: 4] Implements blueprint standalone-network-plugin Change-Id: Ic9947dead1af2114ae0834b644ab19c7020232c3
This commit is contained in:
parent
fd5e0f8eb9
commit
bfa1bbac78
@ -552,6 +552,11 @@ def network_allocations_get_for_share_server(context, share_server_id,
|
|||||||
session=session)
|
session=session)
|
||||||
|
|
||||||
|
|
||||||
|
def network_allocations_get_by_ip_address(context, ip_address):
|
||||||
|
"""Get network allocations by IP address."""
|
||||||
|
return IMPL.network_allocations_get_by_ip_address(context, ip_address)
|
||||||
|
|
||||||
|
|
||||||
##################
|
##################
|
||||||
|
|
||||||
|
|
||||||
|
@ -1959,6 +1959,7 @@ def share_server_backend_details_get(context, share_server_id,
|
|||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def network_allocation_create(context, values):
|
def network_allocation_create(context, values):
|
||||||
|
values['id'] = values.get('id', six.text_type(uuid.uuid4()))
|
||||||
alloc_ref = models.NetworkAllocation()
|
alloc_ref = models.NetworkAllocation()
|
||||||
alloc_ref.update(values)
|
alloc_ref.update(values)
|
||||||
session = get_session()
|
session = get_session()
|
||||||
@ -1986,6 +1987,14 @@ def network_allocation_get(context, id, session=None):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
@require_context
|
||||||
|
def network_allocations_get_by_ip_address(context, ip_address):
|
||||||
|
session = get_session()
|
||||||
|
result = model_query(context, models.NetworkAllocation, session=session).\
|
||||||
|
filter_by(ip_address=ip_address).all()
|
||||||
|
return result or []
|
||||||
|
|
||||||
|
|
||||||
@require_context
|
@require_context
|
||||||
def network_allocations_get_for_share_server(context, share_server_id,
|
def network_allocations_get_for_share_server(context, share_server_id,
|
||||||
session=None):
|
session=None):
|
||||||
|
262
manila/network/standalone_network_plugin.py
Normal file
262
manila/network/standalone_network_plugin.py
Normal file
@ -0,0 +1,262 @@
|
|||||||
|
# Copyright 2015 Mirantis, Inc.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import netaddr
|
||||||
|
from oslo_config import cfg
|
||||||
|
import six
|
||||||
|
|
||||||
|
from manila.common import constants
|
||||||
|
from manila import exception
|
||||||
|
from manila.i18n import _
|
||||||
|
from manila import network
|
||||||
|
from manila.openstack.common import log as logging
|
||||||
|
from manila import utils
|
||||||
|
|
||||||
|
standalone_network_plugin_opts = [
|
||||||
|
cfg.StrOpt(
|
||||||
|
'standalone_network_plugin_gateway',
|
||||||
|
help="Gateway IPv4 address that should be used. Required.",
|
||||||
|
deprecated_group='DEFAULT'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'standalone_network_plugin_mask',
|
||||||
|
help="Network mask that will be used. Can be either decimal "
|
||||||
|
"like '24' or binary like '255.255.255.0'. Required.",
|
||||||
|
deprecated_group='DEFAULT'),
|
||||||
|
cfg.StrOpt(
|
||||||
|
'standalone_network_plugin_segmentation_id',
|
||||||
|
help="Set it if network has segmentation (VLAN, VXLAN, etc...). "
|
||||||
|
"It will be assigned to share-network and share drivers will be "
|
||||||
|
"able to use this for network interfaces within provisioned "
|
||||||
|
"share servers. Optional. Example: 1001",
|
||||||
|
deprecated_group='DEFAULT'),
|
||||||
|
cfg.ListOpt(
|
||||||
|
'standalone_network_plugin_allowed_ip_ranges',
|
||||||
|
help="Can be IP address, range of IP addresses or list of addresses "
|
||||||
|
"or ranges. Contains addresses from IP network that are allowed "
|
||||||
|
"to be used. If empty, then will be assumed that all host "
|
||||||
|
"addresses from network can be used. Optional. "
|
||||||
|
"Examples: 10.0.0.10 or 10.0.0.10-10.0.0.20 or "
|
||||||
|
"10.0.0.10-10.0.0.20,10.0.0.30-10.0.0.40,10.0.0.50",
|
||||||
|
deprecated_group='DEFAULT'),
|
||||||
|
cfg.IntOpt(
|
||||||
|
'standalone_network_plugin_ip_version',
|
||||||
|
default=4,
|
||||||
|
help="IP version of network. Optional."
|
||||||
|
"Allowed values are '4' and '6'. Default value is '4'.",
|
||||||
|
deprecated_group='DEFAULT'),
|
||||||
|
]
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class StandaloneNetworkPlugin(network.NetworkBaseAPI):
|
||||||
|
"""Standalone network plugin for share drivers.
|
||||||
|
|
||||||
|
This network plugin can be used with any network platform.
|
||||||
|
It can serve flat networks as well as segmented.
|
||||||
|
It does not require some specific network services in OpenStack like
|
||||||
|
Neutron or Nova.
|
||||||
|
The only thing that plugin does is reservation and release of IP addresses
|
||||||
|
from some network.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, config_group_name=None, db_driver=None):
|
||||||
|
super(StandaloneNetworkPlugin, self).__init__(db_driver=db_driver)
|
||||||
|
self.config_group_name = config_group_name or 'DEFAULT'
|
||||||
|
CONF.register_opts(
|
||||||
|
standalone_network_plugin_opts,
|
||||||
|
group=self.config_group_name)
|
||||||
|
self.configuration = getattr(CONF, self.config_group_name, CONF)
|
||||||
|
self._set_persistent_network_data()
|
||||||
|
LOG.debug(
|
||||||
|
"\nStandalone network plugin data for config group "
|
||||||
|
"'%(config_group)s': \n"
|
||||||
|
"IP version - %(ip_version)s\n"
|
||||||
|
"Used network - %(net)s\n"
|
||||||
|
"Used gateway - %(gateway)s\n"
|
||||||
|
"Used segmentation ID - %(segmentation_id)s\n"
|
||||||
|
"Allowed CIDRs - %(cidrs)s\n"
|
||||||
|
"Original allowed IP ranges - %(ip_ranges)s\n"
|
||||||
|
"Reserved IP addresses - %(reserved)s\n",
|
||||||
|
dict(
|
||||||
|
config_group=self.config_group_name,
|
||||||
|
ip_version=self.ip_version,
|
||||||
|
net=six.text_type(self.net),
|
||||||
|
gateway=self.gateway,
|
||||||
|
segmentation_id=self.segmentation_id,
|
||||||
|
cidrs=self.allowed_cidrs,
|
||||||
|
ip_ranges=self.allowed_ip_ranges,
|
||||||
|
reserved=self.reserved_addresses))
|
||||||
|
|
||||||
|
def _set_persistent_network_data(self):
|
||||||
|
"""Sets persistent data for whole plugin."""
|
||||||
|
self.segmentation_id = (
|
||||||
|
self.configuration.standalone_network_plugin_segmentation_id)
|
||||||
|
self.gateway = self.configuration.standalone_network_plugin_gateway
|
||||||
|
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.net = self._get_network()
|
||||||
|
self.allowed_cidrs = self._get_list_of_allowed_addresses()
|
||||||
|
self.reserved_addresses = (
|
||||||
|
six.text_type(self.net.network),
|
||||||
|
self.gateway,
|
||||||
|
six.text_type(self.net.broadcast))
|
||||||
|
|
||||||
|
def _get_network(self):
|
||||||
|
"""Returns IPNetwork object calculated from gateway and netmask."""
|
||||||
|
if not isinstance(self.gateway, six.string_types):
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
_("Configuration option 'standalone_network_plugin_gateway' "
|
||||||
|
"is required and has improper value '%s'.") % self.gateway)
|
||||||
|
if not isinstance(self.mask, six.string_types):
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
_("Configuration option 'standalone_network_plugin_mask' is "
|
||||||
|
"required and has improper value '%s'.") % self.mask)
|
||||||
|
try:
|
||||||
|
return netaddr.IPNetwork(self.gateway + '/' + self.mask)
|
||||||
|
except netaddr.AddrFormatError as e:
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
reason=e)
|
||||||
|
|
||||||
|
def _get_list_of_allowed_addresses(self):
|
||||||
|
"""Returns list of CIDRs that can be used for getting IP addresses.
|
||||||
|
|
||||||
|
Reads information provided via configuration, such as gateway,
|
||||||
|
netmask, segmentation ID and allowed IP ranges, then performs
|
||||||
|
validation of provided data.
|
||||||
|
|
||||||
|
:returns: list of CIDRs as text types.
|
||||||
|
:raises: exception.NetworkBadConfigurationException
|
||||||
|
"""
|
||||||
|
cidrs = []
|
||||||
|
if self.allowed_ip_ranges:
|
||||||
|
for ip_range in self.allowed_ip_ranges:
|
||||||
|
ip_range_start = ip_range_end = None
|
||||||
|
if utils.is_valid_ip_address(ip_range, self.ip_version):
|
||||||
|
ip_range_start = ip_range_end = ip_range
|
||||||
|
elif '-' in ip_range:
|
||||||
|
ip_range_list = ip_range.split('-')
|
||||||
|
if len(ip_range_list) == 2:
|
||||||
|
ip_range_start = ip_range_list[0]
|
||||||
|
ip_range_end = ip_range_list[1]
|
||||||
|
for ip in ip_range_list:
|
||||||
|
utils.is_valid_ip_address(ip, self.ip_version)
|
||||||
|
else:
|
||||||
|
msg = _("Wrong value for IP range "
|
||||||
|
"'%s' was provided.") % ip_range
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
reason=msg)
|
||||||
|
else:
|
||||||
|
msg = _("Config option "
|
||||||
|
"'standalone_network_plugin_allowed_ip_ranges' "
|
||||||
|
"has incorrect value "
|
||||||
|
"'%s'") % self.allowed_ip_ranges
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
reason=msg)
|
||||||
|
|
||||||
|
range_instance = netaddr.IPRange(ip_range_start, ip_range_end)
|
||||||
|
|
||||||
|
if range_instance not in self.net:
|
||||||
|
data = dict(
|
||||||
|
range=six.text_type(range_instance),
|
||||||
|
net=six.text_type(self.net),
|
||||||
|
gateway=self.gateway,
|
||||||
|
netmask=self.net.netmask)
|
||||||
|
msg = _("One of provided allowed IP ranges ('%(range)s') "
|
||||||
|
"does not fit network '%(net)s' combined from "
|
||||||
|
"gateway '%(gateway)s' and netmask "
|
||||||
|
"'%(netmask)s'.") % data
|
||||||
|
raise exception.NetworkBadConfigurationException(
|
||||||
|
reason=msg)
|
||||||
|
|
||||||
|
cidrs.extend(
|
||||||
|
six.text_type(cidr) for cidr in range_instance.cidrs())
|
||||||
|
else:
|
||||||
|
if self.net.version != self.ip_version:
|
||||||
|
msg = _("Configured invalid IP version '%(conf_v)s', network "
|
||||||
|
"has version ""'%(net_v)s'") % dict(
|
||||||
|
conf_v=self.ip_version, net_v=self.net.version)
|
||||||
|
raise exception.NetworkBadConfigurationException(reason=msg)
|
||||||
|
cidrs.append(six.text_type(self.net))
|
||||||
|
|
||||||
|
return cidrs
|
||||||
|
|
||||||
|
def _get_available_ips(self, context, amount):
|
||||||
|
"""Returns IP addresses from allowed IP range if there are unused IPs.
|
||||||
|
|
||||||
|
:returns: IP addresses as list of text types
|
||||||
|
:raises: exception.NetworkBadConfigurationException
|
||||||
|
"""
|
||||||
|
ips = []
|
||||||
|
if amount < 1:
|
||||||
|
return ips
|
||||||
|
iterator = netaddr.iter_unique_ips(*self.allowed_cidrs)
|
||||||
|
for ip in iterator:
|
||||||
|
ip = six.text_type(ip)
|
||||||
|
if (ip in self.reserved_addresses or
|
||||||
|
self.db.network_allocations_get_by_ip_address(context,
|
||||||
|
ip)):
|
||||||
|
continue
|
||||||
|
else:
|
||||||
|
ips.append(ip)
|
||||||
|
if len(ips) == amount:
|
||||||
|
return ips
|
||||||
|
msg = _("No available IP addresses left in CIDRs %(cidrs)s. "
|
||||||
|
"Requested amount of IPs to be provided '%(amount)s', "
|
||||||
|
"available only '%(available)s'.") % {
|
||||||
|
'cidrs': self.allowed_cidrs,
|
||||||
|
'amount': amount,
|
||||||
|
'available': len(ips)}
|
||||||
|
raise exception.NetworkBadConfigurationException(reason=msg)
|
||||||
|
|
||||||
|
def _save_network_info(self, context, share_network):
|
||||||
|
"""Update share-network with plugin specific data."""
|
||||||
|
data = dict(
|
||||||
|
segmentation_id=self.segmentation_id,
|
||||||
|
cidr=self.net.cidr,
|
||||||
|
ip_version=self.ip_version)
|
||||||
|
self.db.share_network_update(context, share_network['id'], data)
|
||||||
|
|
||||||
|
@utils.synchronized(
|
||||||
|
"allocate_network_for_standalone_network_plugin", external=True)
|
||||||
|
def allocate_network(self, context, share_server, share_network, **kwargs):
|
||||||
|
"""Allocate network resources using one dedicated network.
|
||||||
|
|
||||||
|
This one has interprocess lock to avoid concurrency in creation of
|
||||||
|
share servers with same IP addresses using different share-networks.
|
||||||
|
"""
|
||||||
|
allocation_count = kwargs.get('count', 1)
|
||||||
|
self._save_network_info(context, share_network)
|
||||||
|
allocations = []
|
||||||
|
ip_addresses = self._get_available_ips(context, allocation_count)
|
||||||
|
for ip_address in ip_addresses:
|
||||||
|
data = dict(
|
||||||
|
share_server_id=share_server['id'],
|
||||||
|
ip_address=ip_address,
|
||||||
|
status=constants.STATUS_ACTIVE)
|
||||||
|
allocations.append(
|
||||||
|
self.db.network_allocation_create(context, data))
|
||||||
|
return allocations
|
||||||
|
|
||||||
|
def deallocate_network(self, context, share_server_id):
|
||||||
|
"""Deallocate network resources for share server."""
|
||||||
|
allocations = self.db.network_allocations_get_for_share_server(
|
||||||
|
context, share_server_id)
|
||||||
|
for allocation in allocations:
|
||||||
|
self.db.network_allocation_delete(context, allocation['id'])
|
@ -34,6 +34,7 @@ import manila.network
|
|||||||
import manila.network.linux.interface
|
import manila.network.linux.interface
|
||||||
import manila.network.neutron.api
|
import manila.network.neutron.api
|
||||||
import manila.network.neutron.neutron_network_plugin
|
import manila.network.neutron.neutron_network_plugin
|
||||||
|
import manila.network.standalone_network_plugin
|
||||||
import manila.openstack.common.eventlet_backdoor
|
import manila.openstack.common.eventlet_backdoor
|
||||||
import manila.openstack.common.log
|
import manila.openstack.common.log
|
||||||
import manila.openstack.common.policy
|
import manila.openstack.common.policy
|
||||||
@ -85,6 +86,7 @@ _global_opt_lists = [
|
|||||||
manila.network.neutron.api.neutron_opts,
|
manila.network.neutron.api.neutron_opts,
|
||||||
manila.network.neutron.neutron_network_plugin.
|
manila.network.neutron.neutron_network_plugin.
|
||||||
neutron_single_network_plugin_opts,
|
neutron_single_network_plugin_opts,
|
||||||
|
manila.network.standalone_network_plugin.standalone_network_plugin_opts,
|
||||||
manila.openstack.common.eventlet_backdoor.eventlet_backdoor_opts,
|
manila.openstack.common.eventlet_backdoor.eventlet_backdoor_opts,
|
||||||
manila.openstack.common.log.common_cli_opts,
|
manila.openstack.common.log.common_cli_opts,
|
||||||
manila.openstack.common.log.generic_log_opts,
|
manila.openstack.common.log.generic_log_opts,
|
||||||
|
389
manila/tests/network/test_standalone_network_plugin.py
Normal file
389
manila/tests/network/test_standalone_network_plugin.py
Normal file
@ -0,0 +1,389 @@
|
|||||||
|
# Copyright 2015 Mirantis, Inc.
|
||||||
|
# All Rights Reserved
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
import mock
|
||||||
|
import netaddr
|
||||||
|
from oslo_config import cfg
|
||||||
|
|
||||||
|
from manila.common import constants
|
||||||
|
from manila import context
|
||||||
|
from manila import exception
|
||||||
|
from manila.network import standalone_network_plugin as plugin
|
||||||
|
from manila import test
|
||||||
|
from manila.tests import utils as test_utils
|
||||||
|
|
||||||
|
CONF = cfg.CONF
|
||||||
|
|
||||||
|
fake_context = context.RequestContext(
|
||||||
|
user_id='fake user', project_id='fake project', is_admin=False)
|
||||||
|
fake_share_server = dict(id='fake_share_server_id')
|
||||||
|
fake_share_network = dict(id='fake_share_network_id')
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class StandaloneNetworkPluginTest(test.TestCase):
|
||||||
|
|
||||||
|
@ddt.data('custom_config_group_name', 'DEFAULT')
|
||||||
|
def test_init_only_with_required_data_v4(self, group_name):
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '24',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin(
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
self.assertEqual('10.0.0.1', instance.gateway)
|
||||||
|
self.assertEqual('24', instance.mask)
|
||||||
|
self.assertEqual(None, instance.segmentation_id)
|
||||||
|
self.assertEqual(None, instance.allowed_ip_ranges)
|
||||||
|
self.assertEqual(4, instance.ip_version)
|
||||||
|
self.assertEqual(netaddr.IPNetwork('10.0.0.1/24'), instance.net)
|
||||||
|
self.assertEqual(['10.0.0.1/24'], instance.allowed_cidrs)
|
||||||
|
self.assertEqual(
|
||||||
|
('10.0.0.0', '10.0.0.1', '10.0.0.255'),
|
||||||
|
instance.reserved_addresses)
|
||||||
|
|
||||||
|
@ddt.data('custom_config_group_name', 'DEFAULT')
|
||||||
|
def test_init_with_all_data_v4(self, group_name):
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '255.255.0.0',
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
allowed_cidrs = [
|
||||||
|
'10.0.0.3/32', '10.0.0.4/30', '10.0.0.69/32', '10.0.0.70/31',
|
||||||
|
'10.0.0.72/29', '10.0.0.80/28', '10.0.0.96/27', '10.0.0.128/28',
|
||||||
|
'10.0.0.144/29', '10.0.0.152/30', '10.0.0.156/31', '10.0.0.213/32',
|
||||||
|
]
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin(
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
self.assertEqual(4, instance.ip_version)
|
||||||
|
self.assertEqual('10.0.0.1', instance.gateway)
|
||||||
|
self.assertEqual('255.255.0.0', instance.mask)
|
||||||
|
self.assertEqual('1001', instance.segmentation_id)
|
||||||
|
self.assertEqual(allowed_cidrs, instance.allowed_cidrs)
|
||||||
|
self.assertEqual(
|
||||||
|
['10.0.0.3-10.0.0.7', '10.0.0.69-10.0.0.157', '10.0.0.213'],
|
||||||
|
instance.allowed_ip_ranges)
|
||||||
|
self.assertEqual(
|
||||||
|
netaddr.IPNetwork('10.0.0.1/255.255.0.0'), instance.net)
|
||||||
|
self.assertEqual(
|
||||||
|
('10.0.0.0', '10.0.0.1', '10.0.255.255'),
|
||||||
|
instance.reserved_addresses)
|
||||||
|
|
||||||
|
@ddt.data('custom_config_group_name', 'DEFAULT')
|
||||||
|
def test_init_only_with_required_data_v6(self, group_name):
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': (
|
||||||
|
'2001:cdba::3257:9652'),
|
||||||
|
'standalone_network_plugin_mask': '48',
|
||||||
|
'standalone_network_plugin_ip_version': 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin(
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
self.assertEqual(
|
||||||
|
'2001:cdba::3257:9652', instance.gateway)
|
||||||
|
self.assertEqual('48', instance.mask)
|
||||||
|
self.assertEqual(None, instance.segmentation_id)
|
||||||
|
self.assertEqual(None, instance.allowed_ip_ranges)
|
||||||
|
self.assertEqual(6, instance.ip_version)
|
||||||
|
self.assertEqual(
|
||||||
|
netaddr.IPNetwork('2001:cdba::3257:9652/48'),
|
||||||
|
instance.net)
|
||||||
|
self.assertEqual(
|
||||||
|
['2001:cdba::3257:9652/48'], instance.allowed_cidrs)
|
||||||
|
self.assertEqual(
|
||||||
|
('2001:cdba::', '2001:cdba::3257:9652',
|
||||||
|
'2001:cdba:0:ffff:ffff:ffff:ffff:ffff'),
|
||||||
|
instance.reserved_addresses)
|
||||||
|
|
||||||
|
@ddt.data('custom_config_group_name', 'DEFAULT')
|
||||||
|
def test_init_with_all_data_v6(self, group_name):
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': '2001:db8::0001',
|
||||||
|
'standalone_network_plugin_mask': '88',
|
||||||
|
'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,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin(
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
self.assertEqual(6, instance.ip_version)
|
||||||
|
self.assertEqual('2001:db8::0001', instance.gateway)
|
||||||
|
self.assertEqual('88', instance.mask)
|
||||||
|
self.assertEqual('3999', instance.segmentation_id)
|
||||||
|
self.assertEqual(['2001:db8::/89'], instance.allowed_cidrs)
|
||||||
|
self.assertEqual(
|
||||||
|
['2001:db8::-2001:db8:0000:0000:0000:007f:ffff:ffff'],
|
||||||
|
instance.allowed_ip_ranges)
|
||||||
|
self.assertEqual(
|
||||||
|
netaddr.IPNetwork('2001:db8::0001/88'), instance.net)
|
||||||
|
self.assertEqual(
|
||||||
|
('2001:db8::', '2001:db8::0001', '2001:db8::ff:ffff:ffff'),
|
||||||
|
instance.reserved_addresses)
|
||||||
|
|
||||||
|
@ddt.data('custom_config_group_name', 'DEFAULT')
|
||||||
|
def test_invalid_init_without_any_config_definitions(self, group_name):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkBadConfigurationException,
|
||||||
|
plugin.StandaloneNetworkPlugin,
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{},
|
||||||
|
{'gateway': '20.0.0.1'},
|
||||||
|
{'mask': '8'},
|
||||||
|
{'gateway': '20.0.0.1', 'mask': '33'},
|
||||||
|
{'gateway': '20.0.0.256', 'mask': '16'})
|
||||||
|
def test_invalid_init_required_data_improper(self, data):
|
||||||
|
group_name = 'custom_group_name'
|
||||||
|
if 'gateway' in data:
|
||||||
|
data['standalone_network_plugin_gateway'] = data.pop('gateway')
|
||||||
|
if 'mask' in data:
|
||||||
|
data['standalone_network_plugin_mask'] = data.pop('mask')
|
||||||
|
data = {group_name: data}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkBadConfigurationException,
|
||||||
|
plugin.StandaloneNetworkPlugin,
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
'fake',
|
||||||
|
'11.0.0.0-11.0.0.5-11.0.0.11',
|
||||||
|
'11.0.0.0-11.0.0.5',
|
||||||
|
'10.0.10.0-10.0.10.5',
|
||||||
|
'10.0.0.0-10.0.0.5,fake',
|
||||||
|
'10.0.10.0-10.0.10.5,10.0.0.0-10.0.0.5',
|
||||||
|
'10.0.10.0-10.0.10.5,10.0.0.10-10.0.10.5',
|
||||||
|
'10.0.0.0-10.0.0.5,10.0.10.0-10.0.10.5')
|
||||||
|
def test_invalid_init_incorrect_allowed_ip_ranges_v4(self, ip_range):
|
||||||
|
group_name = 'DEFAULT'
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '255.255.255.0',
|
||||||
|
'standalone_network_plugin_allowed_ip_ranges': ip_range,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkBadConfigurationException,
|
||||||
|
plugin.StandaloneNetworkPlugin,
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{'gateway': '2001:db8::0001', 'vers': 4},
|
||||||
|
{'gateway': '10.0.0.1', 'vers': 6})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_invalid_init_mismatch_of_versions(self, gateway, vers):
|
||||||
|
group_name = 'DEFAULT'
|
||||||
|
data = {
|
||||||
|
group_name: {
|
||||||
|
'standalone_network_plugin_gateway': gateway,
|
||||||
|
'standalone_network_plugin_ip_version': vers,
|
||||||
|
'standalone_network_plugin_mask': '25',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkBadConfigurationException,
|
||||||
|
plugin.StandaloneNetworkPlugin,
|
||||||
|
config_group_name=group_name)
|
||||||
|
|
||||||
|
def test_deallocate_network(self):
|
||||||
|
share_server_id = 'fake_share_server_id'
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '24',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
fake_allocations = [{'id': 'fake1'}, {'id': 'fake2'}]
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(
|
||||||
|
instance.db, 'network_allocations_get_for_share_server',
|
||||||
|
mock.Mock(return_value=fake_allocations))
|
||||||
|
self.mock_object(instance.db, 'network_allocation_delete')
|
||||||
|
|
||||||
|
instance.deallocate_network(fake_context, share_server_id)
|
||||||
|
|
||||||
|
instance.db.network_allocations_get_for_share_server.\
|
||||||
|
assert_called_once_with(fake_context, share_server_id)
|
||||||
|
instance.db.network_allocation_delete.\
|
||||||
|
assert_has_calls([
|
||||||
|
mock.call(fake_context, 'fake1'),
|
||||||
|
mock.call(fake_context, 'fake2'),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_allocate_network_zero_addresses_ipv4(self):
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '24',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(instance.db, 'share_network_update')
|
||||||
|
|
||||||
|
allocations = instance.allocate_network(
|
||||||
|
fake_context, fake_share_server, fake_share_network, count=0)
|
||||||
|
|
||||||
|
self.assertEqual([], allocations)
|
||||||
|
instance.db.share_network_update.assert_called_once_wth(
|
||||||
|
fake_context, fake_share_network['id'],
|
||||||
|
dict(segmentation_id=None, cidr=instance.net.cidr, ip_version=4))
|
||||||
|
|
||||||
|
def test_allocate_network_zero_addresses_ipv6(self):
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '2001:db8::0001',
|
||||||
|
'standalone_network_plugin_mask': '64',
|
||||||
|
'standalone_network_plugin_ip_version': 6,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(instance.db, 'share_network_update')
|
||||||
|
|
||||||
|
allocations = instance.allocate_network(
|
||||||
|
fake_context, fake_share_server, fake_share_network, count=0)
|
||||||
|
|
||||||
|
self.assertEqual([], allocations)
|
||||||
|
instance.db.share_network_update.assert_called_once_with(
|
||||||
|
fake_context, fake_share_network['id'],
|
||||||
|
dict(segmentation_id=None, cidr=instance.net.cidr, ip_version=6))
|
||||||
|
|
||||||
|
def test_allocate_network_one_ip_address_ipv4_no_usages_exist(self):
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '24',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(instance.db, 'share_network_update')
|
||||||
|
self.mock_object(instance.db, 'network_allocation_create')
|
||||||
|
self.mock_object(
|
||||||
|
instance.db, 'network_allocations_get_by_ip_address',
|
||||||
|
mock.Mock(return_value=[]))
|
||||||
|
|
||||||
|
allocations = instance.allocate_network(
|
||||||
|
fake_context, fake_share_server, fake_share_network)
|
||||||
|
|
||||||
|
self.assertEqual(1, len(allocations))
|
||||||
|
instance.db.share_network_update.assert_called_once_with(
|
||||||
|
fake_context, fake_share_network['id'],
|
||||||
|
dict(segmentation_id=None, cidr=instance.net.cidr, ip_version=4))
|
||||||
|
instance.db.network_allocations_get_by_ip_address.assert_has_calls(
|
||||||
|
[mock.call(fake_context, '10.0.0.2')])
|
||||||
|
instance.db.network_allocation_create.assert_called_once_with(
|
||||||
|
fake_context,
|
||||||
|
dict(share_server_id=fake_share_server['id'],
|
||||||
|
ip_address='10.0.0.2', status=constants.STATUS_ACTIVE))
|
||||||
|
|
||||||
|
def test_allocate_network_two_ip_addresses_ipv4_two_usages_exist(self):
|
||||||
|
ctxt = type('FakeCtxt', (object,), {'fake': ['10.0.0.2', '10.0.0.4']})
|
||||||
|
|
||||||
|
def fake_get_allocations_by_ip_address(context, ip_address):
|
||||||
|
if ip_address not in context.fake:
|
||||||
|
context.fake.append(ip_address)
|
||||||
|
return []
|
||||||
|
else:
|
||||||
|
return context.fake
|
||||||
|
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '24',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(instance.db, 'share_network_update')
|
||||||
|
self.mock_object(instance.db, 'network_allocation_create')
|
||||||
|
self.mock_object(
|
||||||
|
instance.db, 'network_allocations_get_by_ip_address',
|
||||||
|
mock.Mock(side_effect=fake_get_allocations_by_ip_address))
|
||||||
|
|
||||||
|
allocations = instance.allocate_network(
|
||||||
|
ctxt, fake_share_server, fake_share_network, count=2)
|
||||||
|
|
||||||
|
self.assertEqual(2, len(allocations))
|
||||||
|
instance.db.share_network_update.assert_called_once_with(
|
||||||
|
ctxt, fake_share_network['id'],
|
||||||
|
dict(segmentation_id=None, cidr=instance.net.cidr, ip_version=4))
|
||||||
|
instance.db.network_allocations_get_by_ip_address.assert_has_calls(
|
||||||
|
[mock.call(ctxt, '10.0.0.2'), mock.call(ctxt, '10.0.0.3'),
|
||||||
|
mock.call(ctxt, '10.0.0.4'), mock.call(ctxt, '10.0.0.5')])
|
||||||
|
instance.db.network_allocation_create.assert_has_calls([
|
||||||
|
mock.call(
|
||||||
|
ctxt,
|
||||||
|
dict(share_server_id=fake_share_server['id'],
|
||||||
|
ip_address='10.0.0.3', status=constants.STATUS_ACTIVE)),
|
||||||
|
mock.call(
|
||||||
|
ctxt,
|
||||||
|
dict(share_server_id=fake_share_server['id'],
|
||||||
|
ip_address='10.0.0.5', status=constants.STATUS_ACTIVE)),
|
||||||
|
])
|
||||||
|
|
||||||
|
def test_allocate_network_no_available_ipv4_addresses(self):
|
||||||
|
data = {
|
||||||
|
'DEFAULT': {
|
||||||
|
'standalone_network_plugin_gateway': '10.0.0.1',
|
||||||
|
'standalone_network_plugin_mask': '30',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
with test_utils.create_temp_config_with_opts(data):
|
||||||
|
instance = plugin.StandaloneNetworkPlugin()
|
||||||
|
self.mock_object(instance.db, 'share_network_update')
|
||||||
|
self.mock_object(instance.db, 'network_allocation_create')
|
||||||
|
self.mock_object(
|
||||||
|
instance.db, 'network_allocations_get_by_ip_address',
|
||||||
|
mock.Mock(return_value=['not empty list']))
|
||||||
|
|
||||||
|
self.assertRaises(
|
||||||
|
exception.NetworkBadConfigurationException,
|
||||||
|
instance.allocate_network,
|
||||||
|
fake_context, fake_share_server, fake_share_network)
|
||||||
|
|
||||||
|
instance.db.share_network_update.assert_called_once_with(
|
||||||
|
fake_context, fake_share_network['id'],
|
||||||
|
dict(segmentation_id=None, cidr=instance.net.cidr, ip_version=4))
|
||||||
|
instance.db.network_allocations_get_by_ip_address.assert_has_calls(
|
||||||
|
[mock.call(fake_context, '10.0.0.2')])
|
@ -23,6 +23,7 @@ import socket
|
|||||||
import tempfile
|
import tempfile
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
|
import ddt
|
||||||
import mock
|
import mock
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
from oslo_utils import timeutils
|
from oslo_utils import timeutils
|
||||||
@ -583,3 +584,46 @@ class CidrToNetmaskTestCase(test.TestCase):
|
|||||||
def test_cidr_to_netmask_invalid_04(self):
|
def test_cidr_to_netmask_invalid_04(self):
|
||||||
cidr = '10.0.0.555/33'
|
cidr = '10.0.0.555/33'
|
||||||
self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr)
|
self.assertRaises(exception.InvalidInput, utils.cidr_to_netmask, cidr)
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
|
class IsValidIPVersion(test.TestCase):
|
||||||
|
"""Test suite for function 'is_valid_ip_address'."""
|
||||||
|
|
||||||
|
@ddt.data('0.0.0.0', '255.255.255.255', '192.168.0.1')
|
||||||
|
def test_valid_v4(self, addr):
|
||||||
|
for vers in (4, '4'):
|
||||||
|
self.assertTrue(utils.is_valid_ip_address(addr, vers))
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
'2001:cdba:0000:0000:0000:0000:3257:9652',
|
||||||
|
'2001:cdba:0:0:0:0:3257:9652',
|
||||||
|
'2001:cdba::3257:9652')
|
||||||
|
def test_valid_v6(self, addr):
|
||||||
|
for vers in (6, '6'):
|
||||||
|
self.assertTrue(utils.is_valid_ip_address(addr, vers))
|
||||||
|
|
||||||
|
@ddt.data(
|
||||||
|
{'addr': '1.1.1.1', 'vers': 3},
|
||||||
|
{'addr': '1.1.1.1', 'vers': 5},
|
||||||
|
{'addr': '1.1.1.1', 'vers': 7},
|
||||||
|
{'addr': '2001:cdba::3257:9652', 'vers': '3'},
|
||||||
|
{'addr': '2001:cdba::3257:9652', 'vers': '5'},
|
||||||
|
{'addr': '2001:cdba::3257:9652', 'vers': '7'})
|
||||||
|
@ddt.unpack
|
||||||
|
def test_provided_invalid_version(self, addr, vers):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.ManilaException, utils.is_valid_ip_address, addr, vers)
|
||||||
|
|
||||||
|
def test_provided_none_version(self):
|
||||||
|
self.assertRaises(TypeError, utils.is_valid_ip_address, '', None)
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake', '1.1.1.1')
|
||||||
|
def test_provided_invalid_v6_address(self, addr):
|
||||||
|
for vers in (6, '6'):
|
||||||
|
self.assertFalse(utils.is_valid_ip_address(addr, vers))
|
||||||
|
|
||||||
|
@ddt.data(None, 'fake', '255.255.255.256', '2001:cdba::3257:9652')
|
||||||
|
def test_provided_invalid_v4_address(self, addr):
|
||||||
|
for vers in (4, '4'):
|
||||||
|
self.assertFalse(utils.is_valid_ip_address(addr, vers))
|
||||||
|
@ -494,6 +494,16 @@ def cidr_to_netmask(cidr):
|
|||||||
raise exception.InvalidInput(_("Invalid cidr supplied %s") % cidr)
|
raise exception.InvalidInput(_("Invalid cidr supplied %s") % cidr)
|
||||||
|
|
||||||
|
|
||||||
|
def is_valid_ip_address(ip_address, ip_version):
|
||||||
|
if int(ip_version) == 4:
|
||||||
|
return netaddr.valid_ipv4(ip_address)
|
||||||
|
elif int(ip_version) == 6:
|
||||||
|
return netaddr.valid_ipv6(ip_address)
|
||||||
|
else:
|
||||||
|
raise exception.ManilaException(
|
||||||
|
_("Provided improper IP version '%s'.") % ip_version)
|
||||||
|
|
||||||
|
|
||||||
class IsAMatcher(object):
|
class IsAMatcher(object):
|
||||||
def __init__(self, expected_value=None):
|
def __init__(self, expected_value=None):
|
||||||
self.expected_value = expected_value
|
self.expected_value = expected_value
|
||||||
|
@ -12,6 +12,7 @@ greenlet>=0.3.2
|
|||||||
httplib2>=0.7.5
|
httplib2>=0.7.5
|
||||||
iso8601>=0.1.9
|
iso8601>=0.1.9
|
||||||
lxml>=2.3
|
lxml>=2.3
|
||||||
|
netaddr>=0.7.12
|
||||||
oslo.config>=1.6.0 # Apache-2.0
|
oslo.config>=1.6.0 # Apache-2.0
|
||||||
oslo.context>=0.1.0 # Apache-2.0
|
oslo.context>=0.1.0 # Apache-2.0
|
||||||
oslo.db>=1.4.1 # Apache-2.0
|
oslo.db>=1.4.1 # Apache-2.0
|
||||||
|
Loading…
Reference in New Issue
Block a user