Use Subnet pools to avoid cidr concurrency issues
This patch removes the tobiko.openstack.neutron._cidr module that was used to generate CIDRs whenever tobiko was going to create a new subnet. That method had problems when several tobiko workers running in parallel tried to create subnets. Collisions happened sometimes, resulting in the creation of two subnets with a common CIDR that could fail if they were connected to a common router. This patch uses the neutron subnet pool feature [1]. When Tobiko creates a subnet, it will not calculate a free IPv4/IPv6 CIDR, but will associate that subnet to common Subnet Pools, shared by the different tests. When the subnet is created, it is neutron responsibiltiy to allocate a free CIDR for that subnet, based on the created Subnet Pools. The method that creates the Subnet Pools uses an @oslo_concurrency.lockutils.synchronized decorator to guarantee that two Tobiko workers do not create the same Subnet Pool at the same time. [1] https://docs.openstack.org/neutron/latest/admin/config-subnet-pools.html Change-Id: I3b09e468310e06f8c4b7d7dbec02e4b2e6f67530
This commit is contained in:
parent
2ad24dd2f0
commit
a6e7a522df
@ -10,6 +10,7 @@ mock==3.0.5
|
|||||||
netaddr==0.8.0
|
netaddr==0.8.0
|
||||||
neutron-lib==2.7.0
|
neutron-lib==2.7.0
|
||||||
openstacksdk==0.31.2
|
openstacksdk==0.31.2
|
||||||
|
oslo.concurrency==3.26.0
|
||||||
oslo.config==8.4.0
|
oslo.config==8.4.0
|
||||||
oslo.log==4.4.0
|
oslo.log==4.4.0
|
||||||
packaging==20.4
|
packaging==20.4
|
||||||
|
@ -10,6 +10,7 @@ metalsmith>=1.6.2 # Apache-2.0
|
|||||||
netaddr>=0.8.0 # BSD
|
netaddr>=0.8.0 # BSD
|
||||||
neutron-lib>=2.7.0 # Apache-2.0
|
neutron-lib>=2.7.0 # Apache-2.0
|
||||||
openstacksdk>=0.31.2 # Apache-2.0
|
openstacksdk>=0.31.2 # Apache-2.0
|
||||||
|
oslo.concurrency>=3.26.0 # Apache-2.0
|
||||||
oslo.config>=8.4.0 # Apache-2.0
|
oslo.config>=8.4.0 # Apache-2.0
|
||||||
oslo.log>=4.4.0 # Apache-2.0
|
oslo.log>=4.4.0 # Apache-2.0
|
||||||
packaging>=20.4 # Apache-2.0
|
packaging>=20.4 # Apache-2.0
|
||||||
|
@ -19,6 +19,14 @@
|
|||||||
until: result.rc == 0
|
until: result.rc == 0
|
||||||
ignore_errors: yes
|
ignore_errors: yes
|
||||||
|
|
||||||
|
- name: "cleanup subnet pools created by Tobiko tests"
|
||||||
|
shell: |
|
||||||
|
source {{ stackrc_file }}
|
||||||
|
openstack subnet pool list -f value -c 'Name' | \
|
||||||
|
grep "^tobiko\." | \
|
||||||
|
xargs -r openstack subnet pool delete
|
||||||
|
ignore_errors: yes
|
||||||
|
|
||||||
- name: "cleanup Glance images created by Tobiko tests"
|
- name: "cleanup Glance images created by Tobiko tests"
|
||||||
shell: |
|
shell: |
|
||||||
source {{ stackrc_file }}
|
source {{ stackrc_file }}
|
||||||
|
@ -86,7 +86,10 @@ COMMON_GROUP_NAME = 'common'
|
|||||||
COMMON_OPTIONS = [
|
COMMON_OPTIONS = [
|
||||||
cfg.StrOpt('shelves_dir',
|
cfg.StrOpt('shelves_dir',
|
||||||
default='~/.tobiko/cache/shelves',
|
default='~/.tobiko/cache/shelves',
|
||||||
help=("Default directory where to look for shelves.")),
|
help="Directory where to look for shelves."),
|
||||||
|
cfg.StrOpt('lock_dir',
|
||||||
|
default='~/.tobiko/cache/lock',
|
||||||
|
help="Directory where lock persistent files will be saved"),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,7 +15,6 @@ from __future__ import absolute_import
|
|||||||
|
|
||||||
from tobiko.openstack.neutron import _agent
|
from tobiko.openstack.neutron import _agent
|
||||||
from tobiko.openstack.neutron import _client
|
from tobiko.openstack.neutron import _client
|
||||||
from tobiko.openstack.neutron import _cidr
|
|
||||||
from tobiko.openstack.neutron import _extension
|
from tobiko.openstack.neutron import _extension
|
||||||
from tobiko.openstack.neutron import _floating_ip
|
from tobiko.openstack.neutron import _floating_ip
|
||||||
from tobiko.openstack.neutron import _port
|
from tobiko.openstack.neutron import _port
|
||||||
@ -24,6 +23,7 @@ from tobiko.openstack.neutron import _network
|
|||||||
from tobiko.openstack.neutron import _router
|
from tobiko.openstack.neutron import _router
|
||||||
from tobiko.openstack.neutron import _security_group
|
from tobiko.openstack.neutron import _security_group
|
||||||
from tobiko.openstack.neutron import _subnet
|
from tobiko.openstack.neutron import _subnet
|
||||||
|
from tobiko.openstack.neutron import _subnet_pool
|
||||||
|
|
||||||
|
|
||||||
SERVER = 'neutron-server'
|
SERVER = 'neutron-server'
|
||||||
@ -65,10 +65,6 @@ NeutronClientType = _client.NeutronClientType
|
|||||||
neutron_client = _client.neutron_client
|
neutron_client = _client.neutron_client
|
||||||
get_neutron_client = _client.get_neutron_client
|
get_neutron_client = _client.get_neutron_client
|
||||||
|
|
||||||
new_ipv4_cidr = _cidr.new_ipv4_cidr
|
|
||||||
new_ipv6_cidr = _cidr.new_ipv6_cidr
|
|
||||||
list_subnet_cidrs = _cidr.list_subnet_cidrs
|
|
||||||
|
|
||||||
get_networking_extensions = _extension.get_networking_extensions
|
get_networking_extensions = _extension.get_networking_extensions
|
||||||
missing_networking_extensions = _extension.missing_networking_extensions
|
missing_networking_extensions = _extension.missing_networking_extensions
|
||||||
has_networking_extensions = _extension.has_networking_extensions
|
has_networking_extensions = _extension.has_networking_extensions
|
||||||
@ -140,6 +136,14 @@ SubnetType = _subnet.SubnetType
|
|||||||
SubnetIdType = _subnet.SubnetIdType
|
SubnetIdType = _subnet.SubnetIdType
|
||||||
NoSuchSubnet = _subnet.NoSuchSubnet
|
NoSuchSubnet = _subnet.NoSuchSubnet
|
||||||
|
|
||||||
|
SubnetPoolType = _subnet_pool.SubnetPoolType
|
||||||
|
SubnetPoolIdType = _subnet_pool.SubnetPoolIdType
|
||||||
|
NoSuchSubnetPool = _subnet_pool.NoSuchSubnetPool
|
||||||
|
get_subnet_pool = _subnet_pool.get_subnet_pool
|
||||||
|
create_subnet_pool = _subnet_pool.create_subnet_pool
|
||||||
|
delete_subnet_pool = _subnet_pool.delete_subnet_pool
|
||||||
|
find_subnet_pool = _subnet_pool.find_subnet_pool
|
||||||
|
|
||||||
list_security_groups = _security_group.list_security_groups
|
list_security_groups = _security_group.list_security_groups
|
||||||
get_security_group = _security_group.get_security_group
|
get_security_group = _security_group.get_security_group
|
||||||
get_default_security_group = _security_group.get_default_security_group
|
get_default_security_group = _security_group.get_default_security_group
|
||||||
|
@ -1,142 +0,0 @@
|
|||||||
# Copyright 2019 Red Hat
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
from __future__ import absolute_import
|
|
||||||
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from netaddr.strategy import ipv4
|
|
||||||
from netaddr.strategy import ipv6
|
|
||||||
|
|
||||||
import tobiko
|
|
||||||
from tobiko.openstack.neutron import _client
|
|
||||||
|
|
||||||
|
|
||||||
def new_ipv4_cidr(seed=None):
|
|
||||||
return tobiko.setup_fixture(IPv4CIDRGeneratorFixture).new_cidr(seed=seed)
|
|
||||||
|
|
||||||
|
|
||||||
def new_ipv6_cidr(seed=None):
|
|
||||||
return tobiko.setup_fixture(IPv6CIDRGeneratorFixture).new_cidr(seed=seed)
|
|
||||||
|
|
||||||
|
|
||||||
class CIDRGeneratorFixture(tobiko.SharedFixture):
|
|
||||||
|
|
||||||
cidr = None
|
|
||||||
prefixlen = None
|
|
||||||
client = None
|
|
||||||
config = None
|
|
||||||
cidr_generator = None
|
|
||||||
|
|
||||||
def __init__(self, cidr=None, prefixlen=None, client=None):
|
|
||||||
super(CIDRGeneratorFixture, self).__init__()
|
|
||||||
if cidr:
|
|
||||||
self.cidr = cidr
|
|
||||||
if prefixlen:
|
|
||||||
self.prefixlen = prefixlen
|
|
||||||
if client:
|
|
||||||
self.client = client
|
|
||||||
|
|
||||||
def setup_fixture(self):
|
|
||||||
self.setup_config()
|
|
||||||
self.setup_client()
|
|
||||||
|
|
||||||
def setup_config(self):
|
|
||||||
from tobiko import config
|
|
||||||
CONF = config.CONF
|
|
||||||
self.config = CONF.tobiko.neutron
|
|
||||||
|
|
||||||
def setup_client(self):
|
|
||||||
self.client = _client.neutron_client(self.client)
|
|
||||||
|
|
||||||
def new_cidr(self, seed):
|
|
||||||
used_cidrs = set(list_subnet_cidrs(client=self.client))
|
|
||||||
for cidr in random_subnets(cidr=self.cidr, prefixlen=self.prefixlen,
|
|
||||||
seed=seed):
|
|
||||||
if cidr not in used_cidrs:
|
|
||||||
return cidr
|
|
||||||
raise NoSuchCIDRLeft(cidr=self.cidr, prefixlen=self.prefixlen)
|
|
||||||
|
|
||||||
|
|
||||||
class IPv4CIDRGeneratorFixture(CIDRGeneratorFixture):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cidr(self):
|
|
||||||
return netaddr.IPNetwork(self.config.ipv4_cidr)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def prefixlen(self):
|
|
||||||
return int(self.config.ipv4_prefixlen)
|
|
||||||
|
|
||||||
|
|
||||||
class IPv6CIDRGeneratorFixture(CIDRGeneratorFixture):
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cidr(self):
|
|
||||||
return netaddr.IPNetwork(self.config.ipv6_cidr)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def prefixlen(self):
|
|
||||||
return int(self.config.ipv6_prefixlen)
|
|
||||||
|
|
||||||
|
|
||||||
class NoSuchCIDRLeft(tobiko.TobikoException):
|
|
||||||
message = ("No such subnet CIDR left "
|
|
||||||
"(CIDR={cidr!s}, prefixlen={prefixlen!s})")
|
|
||||||
|
|
||||||
|
|
||||||
def random_subnets(cidr, prefixlen, seed=None):
|
|
||||||
"""
|
|
||||||
A generator that divides up this IPNetwork's subnet into smaller
|
|
||||||
subnets based on a specified CIDR prefix.
|
|
||||||
|
|
||||||
:param prefixlen: a CIDR prefix indicating size of subnets to be
|
|
||||||
returned.
|
|
||||||
|
|
||||||
:return: an iterator containing random IPNetwork subnet objects.
|
|
||||||
"""
|
|
||||||
|
|
||||||
version = cidr.version
|
|
||||||
module = {4: ipv4, 6: ipv6}[version]
|
|
||||||
width = module.width
|
|
||||||
if not 0 <= cidr.prefixlen <= width:
|
|
||||||
message = "CIDR prefix /{!r} invalid for IPv{!s}!".format(
|
|
||||||
prefixlen, cidr.version)
|
|
||||||
raise ValueError(message)
|
|
||||||
|
|
||||||
if not cidr.prefixlen <= prefixlen:
|
|
||||||
# Don't return anything.
|
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
# Calculate number of subnets to be returned.
|
|
||||||
max_subnets = 2 ** (width - cidr.prefixlen) // 2 ** (width - prefixlen)
|
|
||||||
|
|
||||||
base_subnet = module.int_to_str(cidr.first)
|
|
||||||
i = 0
|
|
||||||
rand = random.Random(hash(seed) ^ os.getpid())
|
|
||||||
while True:
|
|
||||||
subnet = netaddr.IPNetwork('%s/%d' % (base_subnet, prefixlen), version)
|
|
||||||
subnet.value += (subnet.size * rand.randrange(0, max_subnets))
|
|
||||||
subnet.prefixlen = prefixlen
|
|
||||||
i += 1
|
|
||||||
yield subnet
|
|
||||||
|
|
||||||
|
|
||||||
def list_subnet_cidrs(client: _client.NeutronClientType = None,
|
|
||||||
**params) -> tobiko.Selection[netaddr.IPNetwork]:
|
|
||||||
from tobiko.openstack.neutron import _subnet
|
|
||||||
subnets = _subnet.list_subnets(client=client, **params)
|
|
||||||
return tobiko.select(netaddr.IPNetwork(subnet['cidr'])
|
|
||||||
for subnet in subnets)
|
|
@ -21,7 +21,6 @@ import netaddr
|
|||||||
import tobiko
|
import tobiko
|
||||||
from tobiko.openstack.neutron import _client
|
from tobiko.openstack.neutron import _client
|
||||||
from tobiko.openstack.neutron import _network
|
from tobiko.openstack.neutron import _network
|
||||||
from tobiko.openstack.neutron import _cidr
|
|
||||||
|
|
||||||
|
|
||||||
SubnetType = typing.Dict[str, typing.Any]
|
SubnetType = typing.Dict[str, typing.Any]
|
||||||
@ -50,7 +49,6 @@ def create_subnet(client: _client.NeutronClientType = None,
|
|||||||
network: _network.NetworkIdType = None,
|
network: _network.NetworkIdType = None,
|
||||||
add_cleanup=True,
|
add_cleanup=True,
|
||||||
ip_version=4,
|
ip_version=4,
|
||||||
cidr: str = None,
|
|
||||||
**params) -> SubnetType:
|
**params) -> SubnetType:
|
||||||
if 'network_id' not in params:
|
if 'network_id' not in params:
|
||||||
if network is None:
|
if network is None:
|
||||||
@ -61,14 +59,6 @@ def create_subnet(client: _client.NeutronClientType = None,
|
|||||||
network_id = _network.get_network_id(network)
|
network_id = _network.get_network_id(network)
|
||||||
params['network_id'] = network_id
|
params['network_id'] = network_id
|
||||||
params['ip_version'] = ip_version
|
params['ip_version'] = ip_version
|
||||||
if cidr is None:
|
|
||||||
if ip_version == 4:
|
|
||||||
cidr = _cidr.new_ipv4_cidr()
|
|
||||||
elif ip_version == 6:
|
|
||||||
cidr = _cidr.new_ipv6_cidr()
|
|
||||||
else:
|
|
||||||
raise ValueError(f'Invalid ip version: {ip_version}')
|
|
||||||
params['cidr'] = cidr
|
|
||||||
subnet = _client.neutron_client(client).create_subnet(
|
subnet = _client.neutron_client(client).create_subnet(
|
||||||
body={'subnet': params})['subnet']
|
body={'subnet': params})['subnet']
|
||||||
if add_cleanup:
|
if add_cleanup:
|
||||||
|
96
tobiko/openstack/neutron/_subnet_pool.py
Normal file
96
tobiko/openstack/neutron/_subnet_pool.py
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
# Copyright 2023 Red Hat
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
from __future__ import absolute_import
|
||||||
|
|
||||||
|
from collections import abc
|
||||||
|
import typing
|
||||||
|
|
||||||
|
import tobiko
|
||||||
|
from tobiko.openstack.neutron import _client
|
||||||
|
|
||||||
|
|
||||||
|
SubnetPoolType = typing.Dict[str, typing.Any]
|
||||||
|
SubnetPoolIdType = typing.Union[str, SubnetPoolType]
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnet_pool_id(subnet_pool: SubnetPoolIdType) -> str:
|
||||||
|
if isinstance(subnet_pool, str):
|
||||||
|
return subnet_pool
|
||||||
|
else:
|
||||||
|
return subnet_pool['id']
|
||||||
|
|
||||||
|
|
||||||
|
def get_subnet_pool(subnet_pool: SubnetPoolIdType,
|
||||||
|
client: _client.NeutronClientType = None,
|
||||||
|
**params) -> SubnetPoolType:
|
||||||
|
subnet_pool_id = get_subnet_pool_id(subnet_pool)
|
||||||
|
try:
|
||||||
|
return _client.neutron_client(client).show_subnetpool(
|
||||||
|
subnet_pool_id, **params)['subnetpool']
|
||||||
|
except _client.NotFound as ex:
|
||||||
|
raise NoSuchSubnetPool from ex
|
||||||
|
|
||||||
|
|
||||||
|
def create_subnet_pool(client: _client.NeutronClientType = None,
|
||||||
|
add_cleanup: bool = True,
|
||||||
|
**params) -> SubnetPoolType:
|
||||||
|
subnet_pool = _client.neutron_client(client).create_subnetpool(
|
||||||
|
body={'subnetpool': params})['subnetpool']
|
||||||
|
if add_cleanup:
|
||||||
|
tobiko.add_cleanup(
|
||||||
|
cleanup_subnet_pool, subnet_pool=subnet_pool, client=client)
|
||||||
|
return subnet_pool
|
||||||
|
|
||||||
|
|
||||||
|
def cleanup_subnet_pool(subnet_pool: SubnetPoolIdType,
|
||||||
|
client: _client.NeutronClientType = None):
|
||||||
|
try:
|
||||||
|
delete_subnet_pool(subnet_pool=subnet_pool, client=client)
|
||||||
|
except NoSuchSubnetPool:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def delete_subnet_pool(subnet_pool: SubnetPoolIdType,
|
||||||
|
client: _client.NeutronClientType = None):
|
||||||
|
subnet_pool_id = get_subnet_pool_id(subnet_pool)
|
||||||
|
try:
|
||||||
|
_client.neutron_client(client).delete_subnetpool(subnet_pool_id)
|
||||||
|
except _client.NotFound as ex:
|
||||||
|
raise NoSuchSubnetPool from ex
|
||||||
|
|
||||||
|
|
||||||
|
def list_subnet_pools(client: _client.NeutronClientType = None,
|
||||||
|
**params) -> tobiko.Selection[SubnetPoolType]:
|
||||||
|
subnet_pools = _client.neutron_client(client).list_subnetpools(**params)
|
||||||
|
if isinstance(subnet_pools, abc.Mapping):
|
||||||
|
subnet_pools = subnet_pools['subnetpools']
|
||||||
|
return tobiko.select(subnet_pools)
|
||||||
|
|
||||||
|
|
||||||
|
def find_subnet_pool(client: _client.NeutronClientType = None,
|
||||||
|
unique=False,
|
||||||
|
**params) -> SubnetPoolType:
|
||||||
|
"""Look for a subnet pool matching some values"""
|
||||||
|
subnet_pools = list_subnet_pools(client=client, **params)
|
||||||
|
if subnet_pools:
|
||||||
|
if unique:
|
||||||
|
return subnet_pools.unique
|
||||||
|
else:
|
||||||
|
return subnet_pools.first
|
||||||
|
else:
|
||||||
|
raise NoSuchSubnetPool
|
||||||
|
|
||||||
|
|
||||||
|
class NoSuchSubnetPool(tobiko.ObjectNotFound):
|
||||||
|
message = "No such subnet pool found"
|
@ -33,7 +33,7 @@ OPTIONS = [
|
|||||||
default=None,
|
default=None,
|
||||||
help="List of nameservers IPv4 addresses"),
|
help="List of nameservers IPv4 addresses"),
|
||||||
cfg.StrOpt('ipv6_cidr',
|
cfg.StrOpt('ipv6_cidr',
|
||||||
default='2001:db8::/48',
|
default='fc00::/48',
|
||||||
help="The CIDR block to allocate IPv6 subnets from"),
|
help="The CIDR block to allocate IPv6 subnets from"),
|
||||||
cfg.IntOpt('ipv6_prefixlen',
|
cfg.IntOpt('ipv6_prefixlen',
|
||||||
default=64,
|
default=64,
|
||||||
|
@ -16,9 +16,11 @@
|
|||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import typing
|
import typing
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
from oslo_concurrency import lockutils
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
|
|
||||||
import tobiko
|
import tobiko
|
||||||
@ -33,6 +35,7 @@ from tobiko.shell import ssh
|
|||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
LOG = log.getLogger(__name__)
|
LOG = log.getLogger(__name__)
|
||||||
|
LOCK_DIR = os.path.expanduser(CONF.tobiko.common.lock_dir)
|
||||||
|
|
||||||
|
|
||||||
class ExternalNetworkStackFixture(heat.HeatStackFixture):
|
class ExternalNetworkStackFixture(heat.HeatStackFixture):
|
||||||
@ -272,9 +275,112 @@ def ensure_router_interface(
|
|||||||
add_cleanup=add_cleanup)
|
add_cleanup=add_cleanup)
|
||||||
|
|
||||||
|
|
||||||
|
@neutron.skip_if_missing_networking_extensions('subnet_allocation')
|
||||||
|
class SubnetPoolFixture(tobiko.SharedFixture):
|
||||||
|
"""Neutron Subnet Pool Fixture.
|
||||||
|
|
||||||
|
A subnet pool is a dependency of network fixtures with either IPv4 or
|
||||||
|
IPv6 subnets (or both). The CIDRs for those subnets are obtained from this
|
||||||
|
resource.
|
||||||
|
NOTE: this fixture does not represent a heat stack, but it is under the
|
||||||
|
stacks module until a decision is taken on where this kind of fixtures are
|
||||||
|
located
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: typing.Optional[str] = None
|
||||||
|
prefixes: list = [CONF.tobiko.neutron.ipv4_cidr]
|
||||||
|
default_prefixlen: int = CONF.tobiko.neutron.ipv4_prefixlen
|
||||||
|
_subnet_pool: typing.Optional[neutron.SubnetPoolType] = None
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
name: typing.Optional[str] = None,
|
||||||
|
prefixes: typing.Optional[list] = None,
|
||||||
|
default_prefixlen: typing.Optional[int] = None):
|
||||||
|
self.name = name or self.fixture_name
|
||||||
|
if prefixes:
|
||||||
|
self.prefixes = prefixes
|
||||||
|
if default_prefixlen:
|
||||||
|
self.default_prefixlen = default_prefixlen
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def ip_version(self):
|
||||||
|
valid_versions = (4, 6)
|
||||||
|
for valid_version in valid_versions:
|
||||||
|
if len(self.prefixes) > 0 and all(
|
||||||
|
netaddr.IPNetwork(prefix).version == valid_version
|
||||||
|
for prefix in self.prefixes):
|
||||||
|
return valid_version
|
||||||
|
# return None when neither IPv4 nor IPv6 (or when both)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def setup_fixture(self):
|
||||||
|
if config.get_bool_env('TOBIKO_PREVENT_CREATE'):
|
||||||
|
LOG.debug("SubnetPoolFixture should have been already created: %r",
|
||||||
|
self.subnet_pool)
|
||||||
|
else:
|
||||||
|
self.try_create_subnet_pool()
|
||||||
|
|
||||||
|
if self.subnet_pool:
|
||||||
|
tobiko.addme_to_shared_resource(__name__, self.name)
|
||||||
|
|
||||||
|
@lockutils.synchronized(
|
||||||
|
'create_subnet_pool', external=True, lock_path=LOCK_DIR)
|
||||||
|
def try_create_subnet_pool(self):
|
||||||
|
if not self.subnet_pool:
|
||||||
|
self._subnet_pool = neutron.create_subnet_pool(
|
||||||
|
name=self.name, prefixes=self.prefixes,
|
||||||
|
default_prefixlen=self.default_prefixlen, add_cleanup=False)
|
||||||
|
|
||||||
|
def cleanup_fixture(self):
|
||||||
|
n_tests_using_resource = len(tobiko.removeme_from_shared_resource(
|
||||||
|
__name__, self.name))
|
||||||
|
if n_tests_using_resource == 0:
|
||||||
|
self._cleanup_subnet_pool()
|
||||||
|
else:
|
||||||
|
LOG.info('Subnet Pool %r not deleted because %d tests '
|
||||||
|
'are using it still.',
|
||||||
|
self.name, n_tests_using_resource)
|
||||||
|
|
||||||
|
def _cleanup_subnet_pool(self):
|
||||||
|
sp_id = self.subnet_pool_id
|
||||||
|
if sp_id:
|
||||||
|
self._subnet_pool = None
|
||||||
|
LOG.debug('Deleting Subnet Pool %r (%r)...',
|
||||||
|
self.name, sp_id)
|
||||||
|
neutron.delete_subnet_pool(sp_id)
|
||||||
|
LOG.debug('Subnet Pool %r (%r) deleted.', self.name, sp_id)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subnet_pool_id(self):
|
||||||
|
if self.subnet_pool:
|
||||||
|
return self._subnet_pool['id']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def subnet_pool(self):
|
||||||
|
if not self._subnet_pool:
|
||||||
|
try:
|
||||||
|
self._subnet_pool = neutron.find_subnet_pool(name=self.name)
|
||||||
|
except neutron.NoSuchSubnetPool:
|
||||||
|
LOG.debug("Subnet Pool %r not found.", self.name)
|
||||||
|
self._subnet_pool = None
|
||||||
|
return self._subnet_pool
|
||||||
|
|
||||||
|
|
||||||
|
class SubnetPoolIPv6Fixture(SubnetPoolFixture):
|
||||||
|
prefixes: list = [CONF.tobiko.neutron.ipv6_cidr]
|
||||||
|
default_prefixlen: int = CONF.tobiko.neutron.ipv6_prefixlen
|
||||||
|
|
||||||
|
|
||||||
@neutron.skip_if_missing_networking_extensions('port-security')
|
@neutron.skip_if_missing_networking_extensions('port-security')
|
||||||
class NetworkStackFixture(heat.HeatStackFixture):
|
class NetworkStackFixture(heat.HeatStackFixture):
|
||||||
"""Heat stack for creating internal network with a router to external"""
|
"""Heat stack for creating internal network with a router to external"""
|
||||||
|
subnet_pools_ipv4_stack = (tobiko.required_fixture(SubnetPoolFixture)
|
||||||
|
if bool(CONF.tobiko.neutron.ipv4_cidr)
|
||||||
|
else None)
|
||||||
|
subnet_pools_ipv6_stack = (tobiko.required_fixture(SubnetPoolIPv6Fixture)
|
||||||
|
if bool(CONF.tobiko.neutron.ipv6_cidr)
|
||||||
|
else None)
|
||||||
|
|
||||||
#: Heat template file
|
#: Heat template file
|
||||||
template = _hot.heat_template_file('neutron/network.yaml')
|
template = _hot.heat_template_file('neutron/network.yaml')
|
||||||
@ -287,24 +393,18 @@ class NetworkStackFixture(heat.HeatStackFixture):
|
|||||||
"""Whenever to setup IPv4 subnet"""
|
"""Whenever to setup IPv4 subnet"""
|
||||||
return bool(CONF.tobiko.neutron.ipv4_cidr)
|
return bool(CONF.tobiko.neutron.ipv4_cidr)
|
||||||
|
|
||||||
@property
|
|
||||||
def ipv4_cidr(self):
|
|
||||||
if self.has_ipv4:
|
|
||||||
return neutron.new_ipv4_cidr(seed=self.fixture_name)
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def has_ipv6(self):
|
def has_ipv6(self):
|
||||||
"""Whenever to setup IPv6 subnet"""
|
"""Whenever to setup IPv6 subnet"""
|
||||||
return bool(CONF.tobiko.neutron.ipv6_cidr)
|
return bool(CONF.tobiko.neutron.ipv6_cidr)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ipv6_cidr(self):
|
def subnet_pool_ipv4_id(self):
|
||||||
if self.has_ipv6:
|
return self.subnet_pools_ipv4_stack.subnet_pool_id
|
||||||
return neutron.new_ipv6_cidr(seed=self.fixture_name)
|
|
||||||
else:
|
@property
|
||||||
return None
|
def subnet_pool_ipv6_id(self):
|
||||||
|
return self.subnet_pools_ipv6_stack.subnet_pool_id
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def network_value_specs(self):
|
def network_value_specs(self):
|
||||||
|
@ -27,17 +27,13 @@ parameters:
|
|||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
|
||||||
ipv4_cidr:
|
subnet_pool_ipv4_id:
|
||||||
description: IPv4 subnet CIDR to be assigned to new network
|
description: IPv4 Subnet Pool ID
|
||||||
type: string
|
type: string
|
||||||
constraints:
|
|
||||||
- custom_constraint: net_cidr
|
|
||||||
|
|
||||||
ipv6_cidr:
|
subnet_pool_ipv6_id:
|
||||||
description: IPv6 subnet CIDR to be assigned to new network
|
description: IPv6 Subnet Pool ID
|
||||||
type: string
|
type: string
|
||||||
constraints:
|
|
||||||
- custom_constraint: net_cidr
|
|
||||||
|
|
||||||
ipv4_dns_nameservers:
|
ipv4_dns_nameservers:
|
||||||
description: IPv4 nameservers IP addresses
|
description: IPv4 nameservers IP addresses
|
||||||
@ -125,7 +121,7 @@ resources:
|
|||||||
properties:
|
properties:
|
||||||
network: {get_resource: _network}
|
network: {get_resource: _network}
|
||||||
ip_version: 4
|
ip_version: 4
|
||||||
cidr: {get_param: ipv4_cidr}
|
subnetpool: {get_param: subnet_pool_ipv4_id}
|
||||||
dns_nameservers: {get_param: ipv4_dns_nameservers}
|
dns_nameservers: {get_param: ipv4_dns_nameservers}
|
||||||
|
|
||||||
_ipv6_subnet:
|
_ipv6_subnet:
|
||||||
@ -134,7 +130,7 @@ resources:
|
|||||||
properties:
|
properties:
|
||||||
network: {get_resource: _network}
|
network: {get_resource: _network}
|
||||||
ip_version: 6
|
ip_version: 6
|
||||||
cidr: {get_param: ipv6_cidr}
|
subnetpool: {get_param: subnet_pool_ipv6_id}
|
||||||
dns_nameservers: {get_param: ipv6_dns_nameservers}
|
dns_nameservers: {get_param: ipv6_dns_nameservers}
|
||||||
ipv6_address_mode: {get_param: ipv6_address_mode}
|
ipv6_address_mode: {get_param: ipv6_address_mode}
|
||||||
ipv6_ra_mode: {get_param: ipv6_ra_mode}
|
ipv6_ra_mode: {get_param: ipv6_ra_mode}
|
||||||
|
@ -529,7 +529,7 @@ class L3AgentTest(BaseAgentTest):
|
|||||||
'ipv6_ra_mode')
|
'ipv6_ra_mode')
|
||||||
ipv6_address_mode = self.stack.network_stack.ipv6_subnet_details.get(
|
ipv6_address_mode = self.stack.network_stack.ipv6_subnet_details.get(
|
||||||
'ipv6_address_mode')
|
'ipv6_address_mode')
|
||||||
if not self.stack.network_stack.ipv6_cidr:
|
if not self.stack.network_stack.ipv6_subnet_details.get('cidr'):
|
||||||
return False
|
return False
|
||||||
if (ipv6_ra_mode not in stateless_modes or
|
if (ipv6_ra_mode not in stateless_modes or
|
||||||
ipv6_address_mode not in stateless_modes):
|
ipv6_address_mode not in stateless_modes):
|
||||||
|
@ -95,9 +95,15 @@ class RouterTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_add_router_interface_with_subnet(self):
|
def test_add_router_interface_with_subnet(self):
|
||||||
network = neutron.create_network(name=self.id())
|
network = neutron.create_network(name=self.id())
|
||||||
|
subnet_pool_range = "172.168.111.0/24"
|
||||||
|
subnet_pool_default_prefixlen = 26
|
||||||
|
subnet_pool = neutron.create_subnet_pool(
|
||||||
|
name=self.id(),
|
||||||
|
prefixes=[subnet_pool_range],
|
||||||
|
default_prefixlen=subnet_pool_default_prefixlen)
|
||||||
subnet = neutron.create_subnet(name=self.id(),
|
subnet = neutron.create_subnet(name=self.id(),
|
||||||
network=network,
|
network=network,
|
||||||
cidr=neutron.new_ipv4_cidr(),
|
subnetpool_id=subnet_pool['id'],
|
||||||
ip_version=4)
|
ip_version=4)
|
||||||
router = self.test_create_router()
|
router = self.test_create_router()
|
||||||
interface = neutron.add_router_interface(router=router, subnet=subnet)
|
interface = neutron.add_router_interface(router=router, subnet=subnet)
|
||||||
|
@ -40,13 +40,26 @@ class SubnetTest(testtools.TestCase):
|
|||||||
self.assertIn(self.stack.ipv6_subnet_id, subnets_ids)
|
self.assertIn(self.stack.ipv6_subnet_id, subnets_ids)
|
||||||
|
|
||||||
def test_list_subnet_cidrs(self):
|
def test_list_subnet_cidrs(self):
|
||||||
subnets_cidrs = neutron.list_subnet_cidrs()
|
def _find_prefix(cidr, prefixes):
|
||||||
|
for prefix in prefixes:
|
||||||
|
if cidr in prefix:
|
||||||
|
return prefix
|
||||||
|
return None
|
||||||
|
|
||||||
if self.stack.has_ipv4:
|
if self.stack.has_ipv4:
|
||||||
cidr = netaddr.IPNetwork(self.stack.ipv4_subnet_details['cidr'])
|
cidr = netaddr.IPNetwork(self.stack.ipv4_subnet_details['cidr'])
|
||||||
self.assertIn(cidr, subnets_cidrs)
|
prefixes = [
|
||||||
|
netaddr.IPNetwork(prefix)
|
||||||
|
for prefix in
|
||||||
|
self.stack.subnet_pools_ipv4_stack.subnet_pool['prefixes']]
|
||||||
|
self.assertIsNotNone(_find_prefix(cidr, prefixes))
|
||||||
if self.stack.has_ipv6:
|
if self.stack.has_ipv6:
|
||||||
cidr = netaddr.IPNetwork(self.stack.ipv6_subnet_details['cidr'])
|
cidr = netaddr.IPNetwork(self.stack.ipv6_subnet_details['cidr'])
|
||||||
self.assertIn(cidr, subnets_cidrs)
|
prefixes = [
|
||||||
|
netaddr.IPNetwork(prefix)
|
||||||
|
for prefix in
|
||||||
|
self.stack.subnet_pools_ipv6_stack.subnet_pool['prefixes']]
|
||||||
|
self.assertIsNotNone(_find_prefix(cidr, prefixes))
|
||||||
|
|
||||||
def test_get_ipv4_subnet(self):
|
def test_get_ipv4_subnet(self):
|
||||||
if not self.stack.has_ipv4:
|
if not self.stack.has_ipv4:
|
||||||
@ -66,12 +79,18 @@ class SubnetTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_create_subnet(self):
|
def test_create_subnet(self):
|
||||||
network = neutron.create_network(name=self.id())
|
network = neutron.create_network(name=self.id())
|
||||||
cidr = neutron.new_ipv4_cidr()
|
subnet_pool_range = "192.168.0.0/16"
|
||||||
|
subnet_pool_default_prefixlen = 24
|
||||||
|
subnet_pool = neutron.create_subnet_pool(
|
||||||
|
name=self.id(),
|
||||||
|
prefixes=[subnet_pool_range],
|
||||||
|
default_prefixlen=subnet_pool_default_prefixlen)
|
||||||
subnet = neutron.create_subnet(network=network,
|
subnet = neutron.create_subnet(network=network,
|
||||||
ip_version=4,
|
ip_version=4,
|
||||||
cidr=cidr)
|
subnetpool_id=subnet_pool['id'])
|
||||||
self.assertEqual(network['id'], subnet['network_id'])
|
self.assertEqual(network['id'], subnet['network_id'])
|
||||||
self.assertEqual(str(cidr), subnet['cidr'])
|
self.assertIn(netaddr.IPNetwork(subnet['cidr']),
|
||||||
|
netaddr.IPNetwork(subnet_pool_range))
|
||||||
self.assertEqual(subnet['id'], neutron.get_subnet(subnet=subnet)['id'])
|
self.assertEqual(subnet['id'], neutron.get_subnet(subnet=subnet)['id'])
|
||||||
return subnet
|
return subnet
|
||||||
|
|
||||||
|
@ -163,6 +163,8 @@ class RouterInterfaceTest(testtools.TestCase):
|
|||||||
|
|
||||||
required_fixtures = [router_stack, network_stack]
|
required_fixtures = [router_stack, network_stack]
|
||||||
|
|
||||||
|
subnet_index = 201
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def tearDownClass(cls) -> None:
|
def tearDownClass(cls) -> None:
|
||||||
for fixture in cls.required_fixtures:
|
for fixture in cls.required_fixtures:
|
||||||
@ -171,10 +173,30 @@ class RouterInterfaceTest(testtools.TestCase):
|
|||||||
except Exception:
|
except Exception:
|
||||||
LOG.exception(f'Error cleaning up fixture: {fixture.fixture}')
|
LOG.exception(f'Error cleaning up fixture: {fixture.fixture}')
|
||||||
|
|
||||||
|
def _get_subnet_pool(self, ip_version=4):
|
||||||
|
if ip_version == 4:
|
||||||
|
subnet_pool_range = f"172.168.{self.subnet_index}.0/24"
|
||||||
|
subnet_pool_default_prefixlen = 26
|
||||||
|
elif ip_version == 6:
|
||||||
|
subnet_pool_range = f"2003:{self.subnet_index}::/64"
|
||||||
|
subnet_pool_default_prefixlen = 68
|
||||||
|
else:
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
|
subnet_pool = neutron.create_subnet_pool(
|
||||||
|
name=self.id(),
|
||||||
|
prefixes=[subnet_pool_range],
|
||||||
|
default_prefixlen=subnet_pool_default_prefixlen)
|
||||||
|
|
||||||
|
self.subnet_index += 1
|
||||||
|
return subnet_pool
|
||||||
|
|
||||||
def test_ensure_router_interface_with_subnet(self,
|
def test_ensure_router_interface_with_subnet(self,
|
||||||
ip_version=4):
|
ip_version=4):
|
||||||
network = neutron.create_network()
|
network = neutron.create_network()
|
||||||
|
subnet_pool = self._get_subnet_pool(ip_version)
|
||||||
subnet = neutron.create_subnet(network=network,
|
subnet = neutron.create_subnet(network=network,
|
||||||
|
subnetpool_id=subnet_pool['id'],
|
||||||
ip_version=ip_version)
|
ip_version=ip_version)
|
||||||
self._test_ensure_router(subnet=subnet)
|
self._test_ensure_router(subnet=subnet)
|
||||||
|
|
||||||
@ -189,7 +211,8 @@ class RouterInterfaceTest(testtools.TestCase):
|
|||||||
|
|
||||||
def test_ensure_router_interface_with_network(self):
|
def test_ensure_router_interface_with_network(self):
|
||||||
network = neutron.create_network()
|
network = neutron.create_network()
|
||||||
neutron.create_subnet(network=network)
|
subnet_pool = self._get_subnet_pool()
|
||||||
|
neutron.create_subnet(network=network, subnetpool_id=subnet_pool['id'])
|
||||||
self._test_ensure_router(network=network)
|
self._test_ensure_router(network=network)
|
||||||
|
|
||||||
def test_ensure_router_interface_with_routed_network(self):
|
def test_ensure_router_interface_with_routed_network(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user