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:
Valeriy Ponomaryov 2015-01-29 11:46:33 +02:00
parent fd5e0f8eb9
commit bfa1bbac78
8 changed files with 722 additions and 0 deletions

View File

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

View File

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

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

View File

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

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

View File

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

View File

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

View File

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