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)
|
||||
|
||||
|
||||
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
|
||||
def network_allocation_create(context, values):
|
||||
values['id'] = values.get('id', six.text_type(uuid.uuid4()))
|
||||
alloc_ref = models.NetworkAllocation()
|
||||
alloc_ref.update(values)
|
||||
session = get_session()
|
||||
|
@ -1986,6 +1987,14 @@ def network_allocation_get(context, id, session=None):
|
|||
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
|
||||
def network_allocations_get_for_share_server(context, share_server_id,
|
||||
session=None):
|
||||
|
|
|
@ -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.neutron.api
|
||||
import manila.network.neutron.neutron_network_plugin
|
||||
import manila.network.standalone_network_plugin
|
||||
import manila.openstack.common.eventlet_backdoor
|
||||
import manila.openstack.common.log
|
||||
import manila.openstack.common.policy
|
||||
|
@ -85,6 +86,7 @@ _global_opt_lists = [
|
|||
manila.network.neutron.api.neutron_opts,
|
||||
manila.network.neutron.neutron_network_plugin.
|
||||
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.log.common_cli_opts,
|
||||
manila.openstack.common.log.generic_log_opts,
|
||||
|
|
|
@ -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 uuid
|
||||
|
||||
import ddt
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_utils import timeutils
|
||||
|
@ -583,3 +584,46 @@ class CidrToNetmaskTestCase(test.TestCase):
|
|||
def test_cidr_to_netmask_invalid_04(self):
|
||||
cidr = '10.0.0.555/33'
|
||||
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)
|
||||
|
||||
|
||||
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):
|
||||
def __init__(self, expected_value=None):
|
||||
self.expected_value = expected_value
|
||||
|
|
|
@ -12,6 +12,7 @@ greenlet>=0.3.2
|
|||
httplib2>=0.7.5
|
||||
iso8601>=0.1.9
|
||||
lxml>=2.3
|
||||
netaddr>=0.7.12
|
||||
oslo.config>=1.6.0 # Apache-2.0
|
||||
oslo.context>=0.1.0 # Apache-2.0
|
||||
oslo.db>=1.4.1 # Apache-2.0
|
||||
|
|
Loading…
Reference in New Issue