Merge "Fix automatic subnet CIDR generation"
diff --git a/neutron_tempest_plugin/api/base.py b/neutron_tempest_plugin/api/base.py
index 51a7d3e..fdd8ba9 100644
--- a/neutron_tempest_plugin/api/base.py
+++ b/neutron_tempest_plugin/api/base.py
@@ -58,7 +58,7 @@
credentials = ['primary']
# Default to ipv4.
- _ip_version = 4
+ _ip_version = const.IP_VERSION_4
@classmethod
def get_client_manager(cls, credential_type=None, roles=None,
@@ -79,7 +79,8 @@
super(BaseNetworkTest, cls).skip_checks()
if not CONF.service_available.neutron:
raise cls.skipException("Neutron support is required")
- if cls._ip_version == 6 and not CONF.network_feature_enabled.ipv6:
+ if (cls._ip_version == const.IP_VERSION_6 and
+ not CONF.network_feature_enabled.ipv6):
raise cls.skipException("IPv6 Tests are disabled.")
for req_ext in getattr(cls, 'required_extensions', []):
if not tutils.is_extension_enabled(req_ext, 'network'):
@@ -122,6 +123,7 @@
cls.security_groups = []
cls.projects = []
cls.log_objects = []
+ cls.reserved_subnet_cidrs = set()
@classmethod
def resource_cleanup(cls):
@@ -282,50 +284,73 @@
return network
@classmethod
- def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
+ def create_subnet(cls, network, gateway=None, cidr=None, mask_bits=None,
ip_version=None, client=None, **kwargs):
- """Wrapper utility that returns a test subnet."""
+ """Wrapper utility that returns a test subnet.
+
+ Convenient wrapper for client.create_subnet method. It reserves and
+ allocates CIDRs to avoid creating overlapping subnets.
+
+ :param network: network where to create the subnet
+ network['id'] must contain the ID of the network
+
+ :param gateway: gateway IP address
+ It can be a str or a netaddr.IPAddress
+ If gateway is not given, then it will use default address for
+ given subnet CIDR, like "192.168.0.1" for "192.168.0.0/24" CIDR
+
+ :param cidr: CIDR of the subnet to create
+ It can be either None, a str or a netaddr.IPNetwork instance
+
+ :param mask_bits: CIDR prefix length
+ It can be either None or a numeric value.
+ If cidr parameter is given then mask_bits is used to determinate a
+ sequence of valid CIDR to use as generated.
+ Please see netaddr.IPNetwork.subnet method documentation[1]
+
+ :param ip_version: ip version of generated subnet CIDRs
+ It can be None, IP_VERSION_4 or IP_VERSION_6
+ It has to match given either given CIDR and gateway
+
+ :param ip_version: numeric value (either IP_VERSION_4 or IP_VERSION_6)
+ this value must match CIDR and gateway IP versions if any of them is
+ given
+
+ :param client: client to be used to connect to network service
+
+ :param **kwargs: optional parameters to be forwarded to wrapped method
+
+ [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
+ """
# allow tests to use admin client
if not client:
client = cls.client
- # The cidr and mask_bits depend on the ip version.
- ip_version = ip_version if ip_version is not None else cls._ip_version
- gateway_not_set = gateway == ''
- if ip_version == 4:
- cidr = cidr or netaddr.IPNetwork(
- config.safe_get_config_value(
- 'network', 'project_network_cidr'))
- mask_bits = (
- mask_bits or config.safe_get_config_value(
- 'network', 'project_network_mask_bits'))
- elif ip_version == 6:
- cidr = (
- cidr or netaddr.IPNetwork(
- config.safe_get_config_value(
- 'network', 'project_network_v6_cidr')))
- mask_bits = (
- mask_bits or config.safe_get_config_value(
- 'network', 'project_network_v6_mask_bits'))
- # Find a cidr that is not in use yet and create a subnet with it
- for subnet_cidr in cidr.subnet(mask_bits):
- if gateway_not_set:
- gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
+ if gateway:
+ gateway_ip = netaddr.IPAddress(gateway)
+ if ip_version:
+ if ip_version != gateway_ip.version:
+ raise ValueError(
+ "Gateway IP version doesn't match IP version")
else:
- gateway_ip = gateway
- try:
- body = client.create_subnet(
- network_id=network['id'],
- cidr=str(subnet_cidr),
- ip_version=ip_version,
- gateway_ip=gateway_ip,
- **kwargs)
- break
- except lib_exc.BadRequest as e:
- is_overlapping_cidr = 'overlaps with another subnet' in str(e)
- if not is_overlapping_cidr:
- raise
+ ip_version = gateway_ip.version
+
+ for subnet_cidr in cls.get_subnet_cidrs(
+ ip_version=ip_version, cidr=cidr, mask_bits=mask_bits):
+ if cls.try_reserve_subnet_cidr(subnet_cidr):
+ gateway_ip = gateway or str(subnet_cidr.ip + 1)
+ try:
+ body = client.create_subnet(
+ network_id=network['id'],
+ cidr=str(subnet_cidr),
+ ip_version=subnet_cidr.version,
+ gateway_ip=str(gateway_ip),
+ **kwargs)
+ break
+ except lib_exc.BadRequest as e:
+ if 'overlaps with another subnet' not in str(e):
+ raise
else:
message = 'Available CIDR for subnet creation could not be found'
raise ValueError(message)
@@ -337,6 +362,93 @@
return subnet
@classmethod
+ def reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
+ """Reserve given subnet CIDR making sure it is not used by create_subnet
+
+ :param addr: the CIDR address to be reserved
+ It can be a str or netaddr.IPNetwork instance
+
+ :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
+ parameters
+ """
+
+ if not cls.try_reserve_subnet_cidr(addr, **ipnetwork_kwargs):
+ raise ValueError('Subnet CIDR already reserved: %r'.format(
+ addr))
+
+ @classmethod
+ def try_reserve_subnet_cidr(cls, addr, **ipnetwork_kwargs):
+ """Reserve given subnet CIDR if it hasn't been reserved before
+
+ :param addr: the CIDR address to be reserved
+ It can be a str or netaddr.IPNetwork instance
+
+ :param **ipnetwork_kwargs: optional netaddr.IPNetwork constructor
+ parameters
+
+ :return: True if it wasn't reserved before, False elsewhere.
+ """
+
+ subnet_cidr = netaddr.IPNetwork(addr, **ipnetwork_kwargs)
+ if subnet_cidr in cls.reserved_subnet_cidrs:
+ return False
+ else:
+ cls.reserved_subnet_cidrs.add(subnet_cidr)
+ return True
+
+ @classmethod
+ def get_subnet_cidrs(
+ cls, cidr=None, mask_bits=None, ip_version=None):
+ """Iterate over a sequence of unused subnet CIDR for IP version
+
+ :param cidr: CIDR of the subnet to create
+ It can be either None, a str or a netaddr.IPNetwork instance
+
+ :param mask_bits: CIDR prefix length
+ It can be either None or a numeric value.
+ If cidr parameter is given then mask_bits is used to determinate a
+ sequence of valid CIDR to use as generated.
+ Please see netaddr.IPNetwork.subnet method documentation[1]
+
+ :param ip_version: ip version of generated subnet CIDRs
+ It can be None, IP_VERSION_4 or IP_VERSION_6
+ It has to match given CIDR if given
+
+ :return: iterator over reserved CIDRs of type netaddr.IPNetwork
+
+ [1] http://netaddr.readthedocs.io/en/latest/tutorial_01.html#supernets-and-subnets # noqa
+ """
+
+ if cidr:
+ # Generate subnet CIDRs starting from given CIDR
+ # checking it is of requested IP version
+ cidr = netaddr.IPNetwork(cidr, version=ip_version)
+ else:
+ # Generate subnet CIDRs starting from configured values
+ ip_version = ip_version or cls._ip_version
+ if ip_version == const.IP_VERSION_4:
+ mask_bits = mask_bits or config.safe_get_config_value(
+ 'network', 'project_network_mask_bits')
+ cidr = netaddr.IPNetwork(config.safe_get_config_value(
+ 'network', 'project_network_cidr'))
+ elif ip_version == const.IP_VERSION_6:
+ mask_bits = config.safe_get_config_value(
+ 'network', 'project_network_v6_mask_bits')
+ cidr = netaddr.IPNetwork(config.safe_get_config_value(
+ 'network', 'project_network_v6_cidr'))
+ else:
+ raise ValueError('Invalid IP version: {!r}'.format(ip_version))
+
+ if mask_bits:
+ subnet_cidrs = cidr.subnet(mask_bits)
+ else:
+ subnet_cidrs = iter([cidr])
+
+ for subnet_cidr in subnet_cidrs:
+ if subnet_cidr not in cls.reserved_subnet_cidrs:
+ yield subnet_cidr
+
+ @classmethod
def create_port(cls, network, **kwargs):
"""Wrapper utility that returns a test port."""
if CONF.network.port_vnic_type and 'binding:vnic_type' not in kwargs:
diff --git a/neutron_tempest_plugin/api/test_allowed_address_pair.py b/neutron_tempest_plugin/api/test_allowed_address_pair.py
index 1c6abcc..f34cc5b 100644
--- a/neutron_tempest_plugin/api/test_allowed_address_pair.py
+++ b/neutron_tempest_plugin/api/test_allowed_address_pair.py
@@ -13,11 +13,9 @@
# License for the specific language governing permissions and limitations
# under the License.
-import netaddr
from tempest.lib import decorators
from neutron_tempest_plugin.api import base
-from neutron_tempest_plugin import config
class AllowedAddressPairTestJSON(base.BaseNetworkTest):
@@ -94,9 +92,7 @@
@decorators.idempotent_id('4d6d178f-34f6-4bff-a01c-0a2f8fe909e4')
def test_update_port_with_cidr_address_pair(self):
# Update allowed address pair with cidr
- cidr = str(
- netaddr.IPNetwork(config.safe_get_config_value(
- 'network', 'project_network_cidr')))
+ cidr = str(next(self.get_subnet_cidrs()))
self._update_port_with_address(cidr)
@decorators.idempotent_id('b3f20091-6cd5-472b-8487-3516137df933')
diff --git a/neutron_tempest_plugin/api/test_network_ip_availability.py b/neutron_tempest_plugin/api/test_network_ip_availability.py
index 0b8ac23..ed56363 100644
--- a/neutron_tempest_plugin/api/test_network_ip_availability.py
+++ b/neutron_tempest_plugin/api/test_network_ip_availability.py
@@ -22,7 +22,6 @@
from tempest.lib import exceptions as lib_exc
from neutron_tempest_plugin.api import base
-from neutron_tempest_plugin import config
from neutron_lib import constants as lib_constants
@@ -76,25 +75,6 @@
self.assertEqual(expected_total, availability['total_ips'])
self.assertEqual(expected_used, availability['used_ips'])
- def _create_subnet(self, network, ip_version):
- if ip_version == lib_constants.IP_VERSION_4:
- cidr = netaddr.IPNetwork('20.0.0.0/24')
- mask_bits = config.safe_get_config_value(
- 'network', 'project_network_mask_bits')
- elif ip_version == lib_constants.IP_VERSION_6:
- cidr = netaddr.IPNetwork('20:db8::/64')
- mask_bits = config.safe_get_config_value(
- 'network', 'project_network_v6_mask_bits')
-
- subnet_cidr = next(cidr.subnet(mask_bits))
- prefix_len = subnet_cidr.prefixlen
- subnet = self.create_subnet(network,
- cidr=subnet_cidr,
- enable_dhcp=False,
- mask_bits=mask_bits,
- ip_version=ip_version)
- return subnet, prefix_len
-
def calc_total_ips(prefix, ip_version):
# will calculate total ips after removing reserved.
@@ -122,7 +102,8 @@
net_name = data_utils.rand_name('network')
network = self.create_network(network_name=net_name)
self.addCleanup(self.client.delete_network, network['id'])
- subnet, prefix = self._create_subnet(network, self._ip_version)
+ subnet = self.create_subnet(network, enable_dhcp=False)
+ prefix = netaddr.IPNetwork(subnet['cidr']).prefixlen
self.addCleanup(self.client.delete_subnet, subnet['id'])
body = self.admin_client.list_network_ip_availabilities()
used_ip = self._get_used_ips(network, body)
@@ -141,7 +122,7 @@
net_name = data_utils.rand_name('network')
network = self.create_network(network_name=net_name)
self.addCleanup(self.client.delete_network, network['id'])
- subnet, prefix = self._create_subnet(network, self._ip_version)
+ subnet = self.create_subnet(network, enable_dhcp=False)
self.addCleanup(self.client.delete_subnet, subnet['id'])
port = self.client.create_port(network_id=network['id'])
self.addCleanup(self._cleanUp_port, port['port']['id'])
diff --git a/neutron_tempest_plugin/scenario/test_floatingip.py b/neutron_tempest_plugin/scenario/test_floatingip.py
index 94ce482..251f21c 100644
--- a/neutron_tempest_plugin/scenario/test_floatingip.py
+++ b/neutron_tempest_plugin/scenario/test_floatingip.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import netaddr
from neutron_lib import constants as lib_constants
from neutron_lib.services.qos import constants as qos_consts
from tempest.common import utils
@@ -76,8 +75,7 @@
@classmethod
def _create_dest_network(cls):
network = cls.create_network()
- subnet = cls.create_subnet(network,
- cidr=netaddr.IPNetwork('10.10.0.0/24'))
+ subnet = cls.create_subnet(network)
cls.create_router_interface(cls.router['id'], subnet['id'])
return network
diff --git a/neutron_tempest_plugin/scenario/test_mtu.py b/neutron_tempest_plugin/scenario/test_mtu.py
index 932c645..8f1c9ed 100644
--- a/neutron_tempest_plugin/scenario/test_mtu.py
+++ b/neutron_tempest_plugin/scenario/test_mtu.py
@@ -12,7 +12,6 @@
# 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 neutron_lib.api.definitions import provider_net
from tempest.common import utils
@@ -83,16 +82,14 @@
def _create_setup(self):
self.admin_client = self.os_admin.network_client
net_kwargs = {'tenant_id': self.client.tenant_id}
- for sub, net_type in (
- ('10.100.0.0/16', 'vxlan'), ('10.200.0.0/16', 'gre')):
+ for net_type in ['vxlan', 'gre']:
net_kwargs['name'] = '-'.join([net_type, 'net'])
net_kwargs['provider:network_type'] = net_type
network = self.admin_client.create_network(**net_kwargs)[
'network']
self.networks.append(network)
self.addCleanup(self.admin_client.delete_network, network['id'])
- cidr = netaddr.IPNetwork(sub)
- subnet = self.create_subnet(network, cidr=cidr)
+ subnet = self.create_subnet(network)
self.create_router_interface(self.router['id'], subnet['id'])
self.addCleanup(self.client.remove_router_interface_with_subnet_id,
self.router['id'], subnet['id'])
@@ -165,14 +162,13 @@
self.admin_client = self.os_admin.network_client
net_kwargs = {'tenant_id': self.client.tenant_id,
'provider:network_type': 'vxlan'}
- for sub in ('10.100.0.0/16', '10.200.0.0/16'):
+ for _ in range(2):
net_kwargs['name'] = data_utils.rand_name('net')
network = self.admin_client.create_network(**net_kwargs)[
'network']
self.networks.append(network)
self.addCleanup(self.admin_client.delete_network, network['id'])
- cidr = netaddr.IPNetwork(sub)
- subnet = self.create_subnet(network, cidr=cidr)
+ subnet = self.create_subnet(network)
self.create_router_interface(self.router['id'], subnet['id'])
self.addCleanup(self.client.remove_router_interface_with_subnet_id,
self.router['id'], subnet['id'])
diff --git a/neutron_tempest_plugin/scenario/test_trunk.py b/neutron_tempest_plugin/scenario/test_trunk.py
index 44f5ba7..77a2844 100644
--- a/neutron_tempest_plugin/scenario/test_trunk.py
+++ b/neutron_tempest_plugin/scenario/test_trunk.py
@@ -12,7 +12,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import netaddr
from oslo_log import log as logging
from tempest.common import utils as tutils
from tempest.common import waiters
@@ -38,10 +37,6 @@
'dhclient $IFACE.%(tag)d"')
-def get_next_subnet(cidr):
- return netaddr.IPNetwork(cidr).next()
-
-
class TrunkTest(base.BaseTempestTestCase):
credentials = ['primary']
force_tenant_isolation = False
@@ -233,9 +228,7 @@
vlan_tag = 10
vlan_network = self.create_network()
- new_subnet_cidr = get_next_subnet(
- config.safe_get_config_value('network', 'project_network_cidr'))
- self.create_subnet(vlan_network, gateway=None, cidr=new_subnet_cidr)
+ self.create_subnet(vlan_network)
servers = [
self._create_server_with_port_and_subport(vlan_network, vlan_tag)