diff --git a/lower-constraints.txt b/lower-constraints.txt index 01b42b6d3..69368ac35 100644 --- a/lower-constraints.txt +++ b/lower-constraints.txt @@ -10,6 +10,7 @@ mock==3.0.5 netaddr==0.8.0 neutron-lib==2.7.0 openstacksdk==0.31.2 +oslo.concurrency==3.26.0 oslo.config==8.4.0 oslo.log==4.4.0 packaging==20.4 diff --git a/requirements.txt b/requirements.txt index b78d7abaa..3615d6ff9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,6 +10,7 @@ metalsmith>=1.6.2 # Apache-2.0 netaddr>=0.8.0 # BSD neutron-lib>=2.7.0 # 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.log>=4.4.0 # Apache-2.0 packaging>=20.4 # Apache-2.0 diff --git a/roles/tobiko-cleanup/tasks/main.yaml b/roles/tobiko-cleanup/tasks/main.yaml index 32b0efaf8..e3a4c7265 100644 --- a/roles/tobiko-cleanup/tasks/main.yaml +++ b/roles/tobiko-cleanup/tasks/main.yaml @@ -19,6 +19,14 @@ until: result.rc == 0 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" shell: | source {{ stackrc_file }} diff --git a/tobiko/config.py b/tobiko/config.py index 42d9630f4..3e3dbc9e9 100644 --- a/tobiko/config.py +++ b/tobiko/config.py @@ -86,7 +86,10 @@ COMMON_GROUP_NAME = 'common' COMMON_OPTIONS = [ cfg.StrOpt('shelves_dir', 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"), ] diff --git a/tobiko/openstack/neutron/__init__.py b/tobiko/openstack/neutron/__init__.py index 137b5038c..6f42ba90b 100644 --- a/tobiko/openstack/neutron/__init__.py +++ b/tobiko/openstack/neutron/__init__.py @@ -15,7 +15,6 @@ from __future__ import absolute_import from tobiko.openstack.neutron import _agent from tobiko.openstack.neutron import _client -from tobiko.openstack.neutron import _cidr from tobiko.openstack.neutron import _extension from tobiko.openstack.neutron import _floating_ip 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 _security_group from tobiko.openstack.neutron import _subnet +from tobiko.openstack.neutron import _subnet_pool SERVER = 'neutron-server' @@ -65,10 +65,6 @@ NeutronClientType = _client.NeutronClientType neutron_client = _client.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 missing_networking_extensions = _extension.missing_networking_extensions has_networking_extensions = _extension.has_networking_extensions @@ -140,6 +136,14 @@ SubnetType = _subnet.SubnetType SubnetIdType = _subnet.SubnetIdType 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 get_security_group = _security_group.get_security_group get_default_security_group = _security_group.get_default_security_group diff --git a/tobiko/openstack/neutron/_cidr.py b/tobiko/openstack/neutron/_cidr.py deleted file mode 100644 index 577f2f9d9..000000000 --- a/tobiko/openstack/neutron/_cidr.py +++ /dev/null @@ -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) diff --git a/tobiko/openstack/neutron/_subnet.py b/tobiko/openstack/neutron/_subnet.py index cb39c9e4d..ca1cc86ea 100644 --- a/tobiko/openstack/neutron/_subnet.py +++ b/tobiko/openstack/neutron/_subnet.py @@ -21,7 +21,6 @@ import netaddr import tobiko from tobiko.openstack.neutron import _client from tobiko.openstack.neutron import _network -from tobiko.openstack.neutron import _cidr SubnetType = typing.Dict[str, typing.Any] @@ -50,7 +49,6 @@ def create_subnet(client: _client.NeutronClientType = None, network: _network.NetworkIdType = None, add_cleanup=True, ip_version=4, - cidr: str = None, **params) -> SubnetType: if 'network_id' not in params: if network is None: @@ -61,14 +59,6 @@ def create_subnet(client: _client.NeutronClientType = None, network_id = _network.get_network_id(network) params['network_id'] = network_id 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( body={'subnet': params})['subnet'] if add_cleanup: diff --git a/tobiko/openstack/neutron/_subnet_pool.py b/tobiko/openstack/neutron/_subnet_pool.py new file mode 100644 index 000000000..505ff39e8 --- /dev/null +++ b/tobiko/openstack/neutron/_subnet_pool.py @@ -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" diff --git a/tobiko/openstack/neutron/config.py b/tobiko/openstack/neutron/config.py index 41d9ce71e..1447111e6 100644 --- a/tobiko/openstack/neutron/config.py +++ b/tobiko/openstack/neutron/config.py @@ -33,7 +33,7 @@ OPTIONS = [ default=None, help="List of nameservers IPv4 addresses"), cfg.StrOpt('ipv6_cidr', - default='2001:db8::/48', + default='fc00::/48', help="The CIDR block to allocate IPv6 subnets from"), cfg.IntOpt('ipv6_prefixlen', default=64, diff --git a/tobiko/openstack/stacks/_neutron.py b/tobiko/openstack/stacks/_neutron.py index 23ef3efaa..9512d2bd7 100644 --- a/tobiko/openstack/stacks/_neutron.py +++ b/tobiko/openstack/stacks/_neutron.py @@ -16,9 +16,11 @@ from __future__ import absolute_import import json +import os import typing import netaddr +from oslo_concurrency import lockutils from oslo_log import log import tobiko @@ -33,6 +35,7 @@ from tobiko.shell import ssh CONF = config.CONF LOG = log.getLogger(__name__) +LOCK_DIR = os.path.expanduser(CONF.tobiko.common.lock_dir) class ExternalNetworkStackFixture(heat.HeatStackFixture): @@ -272,9 +275,112 @@ def ensure_router_interface( 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') class NetworkStackFixture(heat.HeatStackFixture): """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 template = _hot.heat_template_file('neutron/network.yaml') @@ -287,24 +393,18 @@ class NetworkStackFixture(heat.HeatStackFixture): """Whenever to setup IPv4 subnet""" 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 def has_ipv6(self): """Whenever to setup IPv6 subnet""" return bool(CONF.tobiko.neutron.ipv6_cidr) @property - def ipv6_cidr(self): - if self.has_ipv6: - return neutron.new_ipv6_cidr(seed=self.fixture_name) - else: - return None + def subnet_pool_ipv4_id(self): + return self.subnet_pools_ipv4_stack.subnet_pool_id + + @property + def subnet_pool_ipv6_id(self): + return self.subnet_pools_ipv6_stack.subnet_pool_id @property def network_value_specs(self): diff --git a/tobiko/openstack/stacks/neutron/network.yaml b/tobiko/openstack/stacks/neutron/network.yaml index 3f0de0324..5f08be9a6 100644 --- a/tobiko/openstack/stacks/neutron/network.yaml +++ b/tobiko/openstack/stacks/neutron/network.yaml @@ -27,17 +27,13 @@ parameters: type: boolean default: false - ipv4_cidr: - description: IPv4 subnet CIDR to be assigned to new network + subnet_pool_ipv4_id: + description: IPv4 Subnet Pool ID type: string - constraints: - - custom_constraint: net_cidr - ipv6_cidr: - description: IPv6 subnet CIDR to be assigned to new network + subnet_pool_ipv6_id: + description: IPv6 Subnet Pool ID type: string - constraints: - - custom_constraint: net_cidr ipv4_dns_nameservers: description: IPv4 nameservers IP addresses @@ -125,7 +121,7 @@ resources: properties: network: {get_resource: _network} ip_version: 4 - cidr: {get_param: ipv4_cidr} + subnetpool: {get_param: subnet_pool_ipv4_id} dns_nameservers: {get_param: ipv4_dns_nameservers} _ipv6_subnet: @@ -134,7 +130,7 @@ resources: properties: network: {get_resource: _network} ip_version: 6 - cidr: {get_param: ipv6_cidr} + subnetpool: {get_param: subnet_pool_ipv6_id} dns_nameservers: {get_param: ipv6_dns_nameservers} ipv6_address_mode: {get_param: ipv6_address_mode} ipv6_ra_mode: {get_param: ipv6_ra_mode} diff --git a/tobiko/tests/faults/neutron/test_agents.py b/tobiko/tests/faults/neutron/test_agents.py index 2b8193aa8..f52b7ea87 100644 --- a/tobiko/tests/faults/neutron/test_agents.py +++ b/tobiko/tests/faults/neutron/test_agents.py @@ -529,7 +529,7 @@ class L3AgentTest(BaseAgentTest): 'ipv6_ra_mode') ipv6_address_mode = self.stack.network_stack.ipv6_subnet_details.get( 'ipv6_address_mode') - if not self.stack.network_stack.ipv6_cidr: + if not self.stack.network_stack.ipv6_subnet_details.get('cidr'): return False if (ipv6_ra_mode not in stateless_modes or ipv6_address_mode not in stateless_modes): diff --git a/tobiko/tests/functional/openstack/neutron/test_router.py b/tobiko/tests/functional/openstack/neutron/test_router.py index 8833b1897..35537820e 100644 --- a/tobiko/tests/functional/openstack/neutron/test_router.py +++ b/tobiko/tests/functional/openstack/neutron/test_router.py @@ -95,9 +95,15 @@ class RouterTest(testtools.TestCase): def test_add_router_interface_with_subnet(self): 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(), network=network, - cidr=neutron.new_ipv4_cidr(), + subnetpool_id=subnet_pool['id'], ip_version=4) router = self.test_create_router() interface = neutron.add_router_interface(router=router, subnet=subnet) diff --git a/tobiko/tests/functional/openstack/neutron/test_subnet.py b/tobiko/tests/functional/openstack/neutron/test_subnet.py index 3905f9dce..e0a200d6c 100644 --- a/tobiko/tests/functional/openstack/neutron/test_subnet.py +++ b/tobiko/tests/functional/openstack/neutron/test_subnet.py @@ -40,13 +40,26 @@ class SubnetTest(testtools.TestCase): self.assertIn(self.stack.ipv6_subnet_id, subnets_ids) 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: 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: 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): if not self.stack.has_ipv4: @@ -66,12 +79,18 @@ class SubnetTest(testtools.TestCase): def test_create_subnet(self): 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, ip_version=4, - cidr=cidr) + subnetpool_id=subnet_pool['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']) return subnet diff --git a/tobiko/tests/functional/openstack/stacks/test_neutron.py b/tobiko/tests/functional/openstack/stacks/test_neutron.py index c09fa0cfd..499ff74e7 100644 --- a/tobiko/tests/functional/openstack/stacks/test_neutron.py +++ b/tobiko/tests/functional/openstack/stacks/test_neutron.py @@ -163,6 +163,8 @@ class RouterInterfaceTest(testtools.TestCase): required_fixtures = [router_stack, network_stack] + subnet_index = 201 + @classmethod def tearDownClass(cls) -> None: for fixture in cls.required_fixtures: @@ -171,10 +173,30 @@ class RouterInterfaceTest(testtools.TestCase): except Exception: 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, ip_version=4): network = neutron.create_network() + subnet_pool = self._get_subnet_pool(ip_version) subnet = neutron.create_subnet(network=network, + subnetpool_id=subnet_pool['id'], ip_version=ip_version) self._test_ensure_router(subnet=subnet) @@ -189,7 +211,8 @@ class RouterInterfaceTest(testtools.TestCase): def test_ensure_router_interface_with_network(self): 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) def test_ensure_router_interface_with_routed_network(self):