Merge "Check connection between VMs with BGPVPN after live migration" into mcp/epoxy
diff --git a/neutron_tempest_plugin/api/test_subnetpools.py b/neutron_tempest_plugin/api/test_subnetpools.py
index eaaee33..a9e2303 100644
--- a/neutron_tempest_plugin/api/test_subnetpools.py
+++ b/neutron_tempest_plugin/api/test_subnetpools.py
@@ -28,6 +28,13 @@
class SubnetPoolsTestBase(base.BaseAdminNetworkTest):
@classmethod
+ def skip_checks(cls):
+ super(SubnetPoolsTestBase, cls).skip_checks()
+ if not utils.is_extension_enabled('default-subnetpools', 'network'):
+ msg = "default-subnetpools extension not enabled."
+ raise cls.skipException(msg)
+
+ @classmethod
def resource_setup(cls):
super(SubnetPoolsTestBase, cls).resource_setup()
min_prefixlen = '29'
@@ -328,7 +335,6 @@
self.assertIsNone(body['subnetpool']['address_scope_id'])
@decorators.idempotent_id('4c6963c2-f54c-4347-b288-75d18421c4c4')
- @utils.requires_ext(extension='default-subnetpools', service='network')
def test_tenant_create_non_default_subnetpool(self):
"""Test creates a subnetpool, the "is_default" attribute is False."""
created_subnetpool = self._create_subnetpool()
diff --git a/neutron_tempest_plugin/api/test_subnetpools_negative.py b/neutron_tempest_plugin/api/test_subnetpools_negative.py
index 934d3cd..a85014c 100644
--- a/neutron_tempest_plugin/api/test_subnetpools_negative.py
+++ b/neutron_tempest_plugin/api/test_subnetpools_negative.py
@@ -62,7 +62,6 @@
@decorators.attr(type='negative')
@decorators.idempotent_id('6ae09d8f-95be-40ed-b1cf-8b850d45bab5')
- @utils.requires_ext(extension='default-subnetpools', service='network')
def test_tenant_create_default_subnetpool(self):
# 'default' subnetpool can only be created by admin.
self.assertRaises(lib_exc.Forbidden, self._create_subnetpool,
diff --git a/neutron_tempest_plugin/api/test_trunk.py b/neutron_tempest_plugin/api/test_trunk.py
index 1006617..6b69e43 100644
--- a/neutron_tempest_plugin/api/test_trunk.py
+++ b/neutron_tempest_plugin/api/test_trunk.py
@@ -250,6 +250,8 @@
if not all(cls.is_type_driver_enabled(t) for t in ['vlan', 'vxlan']):
msg = "Either vxlan or vlan type driver not enabled."
raise cls.skipException(msg)
+ if not CONF.neutron_plugin_options.provider_vlans:
+ raise cls.skipException("No provider VLAN networks available")
def setUp(self):
super(TrunkTestMtusJSONBase, self).setUp()
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/manager.py b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
index 398c764..b65407d 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/manager.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/manager.py
@@ -14,19 +14,70 @@
# License for the specific language governing permissions and limitations
# under the License.
+import os
+
+import netaddr
from oslo_log import log
+from tempest.common import compute
from tempest.common import utils
+from tempest.common import waiters
from tempest import config
from tempest.lib.common.utils import data_utils
from tempest.lib.common.utils import test_utils
from tempest.lib import exceptions as lib_exc
from tempest.scenario import manager
+
CONF = config.CONF
LOG = log.getLogger(__name__)
+NET_A = 'A'
+NET_A_BIS = 'A-Bis'
+NET_B = 'B'
+NET_C = 'C'
+
+if "SUBNETPOOL_PREFIX_V4" in os.environ:
+ subnet_base = netaddr.IPNetwork(os.environ['SUBNETPOOL_PREFIX_V4'])
+ if subnet_base.prefixlen > 21:
+ raise Exception("if SUBNETPOOL_PREFIX_V4 is set, it needs to offer "
+ "space for at least 8 /24 subnets")
+else:
+ subnet_base = netaddr.IPNetwork("10.100.0.0/16")
+
+
+def assign_24(idx):
+ # how many addresses in a /24:
+ range_size = 2 ** (32 - 24)
+ return netaddr.cidr_merge(
+ subnet_base[range_size * idx:range_size * (idx + 1)])[0]
+
+
+S1A = assign_24(1)
+S2A = assign_24(2)
+S1B = assign_24(4)
+S2B = assign_24(6)
+S1C = assign_24(6)
+NET_A_S1 = str(S1A)
+NET_A_S2 = str(S2A)
+NET_B_S1 = str(S1B)
+NET_B_S2 = str(S2B)
+NET_C_S1 = str(S1C)
+IP_A_S1_1 = str(S1A[10])
+IP_B_S1_1 = str(S1B[20])
+IP_C_S1_1 = str(S1C[30])
+IP_A_S1_2 = str(S1A[30])
+IP_B_S1_2 = str(S1B[40])
+IP_A_S1_3 = str(S1A[50])
+IP_B_S1_3 = str(S1B[60])
+IP_A_S2_1 = str(S2A[50])
+IP_B_S2_1 = str(S2B[60])
+IP_A_BIS_S1_1 = IP_A_S1_1
+IP_A_BIS_S1_2 = IP_A_S1_2
+IP_A_BIS_S1_3 = IP_A_S1_3
+IP_A_BIS_S2_1 = IP_A_S2_1
+
class ScenarioTest(manager.NetworkScenarioTest):
"""Base class for scenario tests. Uses tempest own clients. """
@@ -147,9 +198,9 @@
def _create_router(self, client=None, tenant_id=None,
namestart='router-smoke'):
if not client:
- client = self.admin_routers_client
+ client = self.routers_client
if not tenant_id:
- tenant_id = client.project_id
+ tenant_id = client.tenant_id
name = data_utils.rand_name(namestart)
result = client.create_router(name=name,
admin_state_up=True,
@@ -160,3 +211,310 @@
client.delete_router,
router['id'])
return router
+
+ def _create_security_group_for_test(self):
+ self.security_group = self.create_security_group(
+ project_id=self.bgpvpn_client.project_id)
+
+ def _create_networks_and_subnets(self, names=None, subnet_cidrs=None,
+ port_security=True):
+ if not names:
+ names = [NET_A, NET_B, NET_C]
+ if not subnet_cidrs:
+ subnet_cidrs = [[NET_A_S1], [NET_B_S1], [NET_C_S1]]
+ for (name, subnet_cidrs) in zip(names, subnet_cidrs):
+ network = super(NetworkScenarioTest, self).create_network(
+ namestart=name,
+ port_security_enabled=port_security)
+ self.networks[name] = network
+ self.subnets[name] = []
+ for (j, cidr) in enumerate(subnet_cidrs):
+ sub_name = "subnet-%s-%d" % (name, j + 1)
+ subnet = self._create_subnet_with_cidr(network,
+ namestart=sub_name,
+ cidr=cidr,
+ ip_version=4)
+ self.subnets[name].append(subnet)
+
+ def _create_subnet_with_cidr(self, network, subnets_client=None,
+ namestart='subnet-smoke', **kwargs):
+ if not subnets_client:
+ subnets_client = self.subnets_client
+ tenant_cidr = kwargs.get('cidr')
+ # reserving pool for Neutron service ports
+ net = netaddr.IPNetwork(tenant_cidr)
+ allocation_pools = [{'start': str(net[2]), 'end': str(net[9])}]
+ subnet = dict(
+ name=data_utils.rand_name(namestart),
+ network_id=network['id'],
+ tenant_id=network['tenant_id'],
+ allocation_pools=allocation_pools,
+ **kwargs)
+ result = subnets_client.create_subnet(**subnet)
+ self.assertIsNotNone(result, 'Unable to allocate tenant network')
+ subnet = result['subnet']
+ self.assertEqual(subnet['cidr'], tenant_cidr)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ subnets_client.delete_subnet, subnet['id'])
+ return subnet
+
+ def _create_fip_router(self, client=None, public_network_id=None,
+ subnet_id=None):
+ router = self._create_router(client, namestart='router-')
+ router_id = router['id']
+ if public_network_id is None:
+ public_network_id = CONF.network.public_network_id
+ if client is None:
+ client = self.routers_client
+ kwargs = {'external_gateway_info': {'network_id': public_network_id}}
+ router = client.update_router(router_id, **kwargs)['router']
+ if subnet_id is not None:
+ client.add_router_interface(router_id, subnet_id=subnet_id)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ client.remove_router_interface, router_id,
+ subnet_id=subnet_id)
+ return router
+
+ def _associate_fip(self, server_index):
+ server = self.servers[server_index]
+ fip = self.create_floating_ip(
+ server, external_network_id=CONF.network.public_network_id,
+ port_id=self.ports[server['id']]['id'])
+ self.server_fips[server['id']] = fip
+ return fip
+
+ def _create_router_and_associate_fip(self, server_index, subnet):
+ router = self._create_fip_router(subnet_id=subnet['id'])
+ self._associate_fip(server_index)
+ return router
+
+ def _create_server(self, name, keypair, network, ip_address,
+ security_group_ids, clients, port_security):
+ security_groups = []
+ if port_security:
+ security_groups = security_group_ids
+ create_port_body = {'fixed_ips': [{'ip_address': ip_address}],
+ 'namestart': 'port-smoke',
+ 'security_groups': security_groups}
+
+ port = super(NetworkScenarioTest, self).create_port(
+ network_id=network['id'],
+ client=clients.ports_client,
+ **create_port_body)
+
+ create_server_kwargs = {
+ 'key_name': keypair['name'],
+ 'networks': [{'uuid': network['id'], 'port': port['id']}]
+ }
+ body, servers = compute.create_test_server(
+ clients, wait_until='ACTIVE', name=name, **create_server_kwargs)
+ self.addCleanup(waiters.wait_for_server_termination,
+ clients.servers_client, body['id'])
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ clients.servers_client.delete_server, body['id'])
+ server = clients.servers_client.show_server(body['id'])['server']
+ LOG.debug('Created server: %s with status: %s', server['id'],
+ server['status'])
+ self.ports[server['id']] = port
+ return server
+
+ def _create_servers(self, ports_config=None, port_security=True):
+ keypair = self.create_keypair()
+ security_group_ids = [self.security_group['id']]
+ if not ports_config:
+ ports_config = [[self.networks[NET_A], IP_A_S1_1],
+ [self.networks[NET_B], IP_B_S1_1]]
+
+ for (i, port_config) in enumerate(ports_config):
+ network = port_config[0]
+ server = self._create_server(
+ 'server-' + str(i + 1), keypair, network, port_config[1],
+ security_group_ids, self.os_primary, port_security)
+ self.servers.append(server)
+ self.servers_keypairs[server['id']] = keypair
+ self.server_fixed_ips[server['id']] = (
+ server['addresses'][network['name']][0]['addr'])
+ self.assertTrue(self.servers_keypairs)
+
+ def _create_l3_bgpvpn(self, name='test-l3-bgpvpn', rts=None,
+ import_rts=None, export_rts=None):
+ if rts is None and import_rts is None and export_rts is None:
+ rts = [self.RT1]
+ import_rts = import_rts or []
+ export_rts = export_rts or []
+ self.bgpvpn = self.create_bgpvpn(
+ self.bgpvpn_admin_client, tenant_id=self.bgpvpn_client.tenant_id,
+ name=name, route_targets=rts, export_targets=export_rts,
+ import_targets=import_rts)
+ self.addCleanup(test_utils.call_and_ignore_notfound_exc,
+ self.bgpvpn_admin_client.delete_bgpvpn,
+ self.bgpvpn['id'])
+ return self.bgpvpn
+
+ def _update_l3_bgpvpn(self, rts=None, import_rts=None, export_rts=None,
+ bgpvpn=None):
+ bgpvpn = bgpvpn or self.bgpvpn
+ if rts is None:
+ rts = [self.RT1]
+ import_rts = import_rts or []
+ export_rts = export_rts or []
+ LOG.debug('Updating targets in BGPVPN %s', bgpvpn['id'])
+ self.bgpvpn_admin_client.update_bgpvpn(bgpvpn['id'],
+ route_targets=rts,
+ export_targets=export_rts,
+ import_targets=import_rts)
+
+ def _associate_all_nets_to_bgpvpn(self, bgpvpn=None):
+ bgpvpn = bgpvpn or self.bgpvpn
+ for network in self.networks.values():
+ self.bgpvpn_client.create_network_association(
+ bgpvpn['id'], network['id'])
+ LOG.debug('BGPVPN network associations completed')
+
+ def _setup_ssh_client(self, server):
+ server_fip = self.server_fips[server['id']][
+ 'floating_ip_address']
+ private_key = self.servers_keypairs[server['id']][
+ 'private_key']
+ ssh_client = self.get_remote_client(server_fip,
+ private_key=private_key,
+ server=server)
+ return ssh_client
+
+ def _setup_http_server(self, server_index):
+ server = self.servers[server_index]
+ ssh_client = self._setup_ssh_client(server)
+ ssh_client.exec_command("sudo nc -kl -p 80 -e echo '%s:%s' &"
+ % (server['name'], server['id']))
+
+ def _setup_ip_forwarding(self, server_index):
+ server = self.servers[server_index]
+ ssh_client = self._setup_ssh_client(server)
+ ssh_client.exec_command("sudo sysctl -w net.ipv4.ip_forward=1")
+
+ def _setup_ip_address(self, server_index, cidr, device=None):
+ self._setup_range_ip_address(server_index, [cidr], device=None)
+
+ def _setup_range_ip_address(self, server_index, cidrs, device=None):
+ MAX_CIDRS = 50
+ if device is None:
+ device = 'lo'
+ server = self.servers[server_index]
+ ssh_client = self._setup_ssh_client(server)
+ for i in range(0, len(cidrs), MAX_CIDRS):
+ ips = ' '.join(cidrs[i:i + MAX_CIDRS])
+ ssh_client.exec_command(
+ ("for ip in {ips}; do sudo ip addr add $ip "
+ "dev {dev}; done").format(ips=ips, dev=device))
+
+ def _check_l3_bgpvpn(self, from_server=None, to_server=None,
+ should_succeed=True, validate_server=False):
+ to_server = to_server or self.servers[1]
+ destination_srv = None
+ if validate_server:
+ destination_srv = '%s:%s' % (to_server['name'], to_server['id'])
+ destination_ip = self.server_fixed_ips[to_server['id']]
+ self._check_l3_bgpvpn_by_specific_ip(from_server=from_server,
+ to_server_ip=destination_ip,
+ should_succeed=should_succeed,
+ validate_server=destination_srv)
+
+ def _check_l3_bgpvpn_by_specific_ip(self, from_server=None,
+ to_server_ip=None,
+ should_succeed=True,
+ validate_server=None,
+ repeat_validate_server=10):
+ from_server = from_server or self.servers[0]
+ from_server_ip = self.server_fips[from_server['id']][
+ 'floating_ip_address']
+ if to_server_ip is None:
+ to_server_ip = self.server_fixed_ips[self.servers[1]['id']]
+ ssh_client = self._setup_ssh_client(from_server)
+ check_reachable = should_succeed or validate_server
+ msg = ""
+ if check_reachable:
+ msg = "Timed out waiting for {ip} to become reachable".format(
+ ip=to_server_ip)
+ else:
+ msg = ("Unexpected ping response from VM with IP address "
+ "{dest} originated from VM with IP address "
+ "{src}").format(dest=to_server_ip, src=from_server_ip)
+ try:
+ result = self._check_remote_connectivity(ssh_client,
+ to_server_ip,
+ check_reachable)
+ # if a negative connectivity check was unsuccessful (unexpected
+ # ping reply) then try to know more:
+ if not check_reachable and not result:
+ try:
+ content = ssh_client.exec_command(
+ "nc %s 80" % to_server_ip).strip()
+ LOG.warning("Can connect to %s: %s", to_server_ip, content)
+ except Exception:
+ LOG.warning("Could ping %s, but no http", to_server_ip)
+
+ self.assertTrue(result, msg)
+
+ if validate_server and result:
+ # repeating multiple times gives increased odds of avoiding
+ # false positives in the case where the dataplane does
+ # equal-cost multipath
+ for i in range(0, repeat_validate_server):
+ real_dest = ssh_client.exec_command(
+ "nc %s 80" % to_server_ip).strip()
+ result = real_dest == validate_server
+ self.assertTrue(
+ should_succeed == result,
+ ("Destination server name is '%s', expected is '%s'" %
+ (real_dest, validate_server)))
+ LOG.info("nc server name check %d successful", i)
+ except Exception:
+ LOG.exception("Error validating connectivity to %s "
+ "from VM with IP address %s: %s",
+ to_server_ip, from_server_ip, msg)
+ raise
+
+ def _associate_fip_and_check_l3_bgpvpn(self, subnet=None,
+ should_succeed=True):
+ if not subnet:
+ subnet = self.subnets[NET_A][0]
+ else:
+ subnet = self.subnets[subnet][0]
+
+ self.router = self._create_router_and_associate_fip(0, subnet)
+ self._check_l3_bgpvpn(should_succeed=should_succeed)
+
+ def _live_migrate(self, server_id, target_host, state,
+ volume_backed=False):
+ # If target_host is None,
+ # check whether source host is different with
+ # the new host after migration.
+ if target_host is None:
+ source_host = self.get_host_for_server(server_id)
+ self._migrate_server_to(server_id, target_host, volume_backed)
+ waiters.wait_for_server_status(self.servers_client, server_id, state)
+ migration_list = (self.admin_migration_client.list_migrations()
+ ['migrations'])
+ msg = ("Live Migration failed. Migrations list for Instance "
+ "%s: [" % server_id)
+ for live_migration in migration_list:
+ if (live_migration['instance_uuid'] == server_id):
+ msg += "\n%s" % live_migration
+ msg += "]"
+ if target_host is None:
+ self.assertNotEqual(source_host,
+ self.get_host_for_server(server_id), msg)
+ else:
+ self.assertEqual(target_host, self.get_host_for_server(server_id),
+ msg)
+
+ def _migrate_server_to(self, server_id, dest_host, volume_backed=False):
+ kwargs = dict()
+ block_migration = getattr(self, 'block_migration', None)
+ if self.block_migration is None:
+ block_migration = (CONF.compute_feature_enabled.
+ block_migration_for_live_migration and
+ not volume_backed)
+ self.admin_servers_client.live_migrate_server(
+ server_id, host=dest_host, block_migration=block_migration,
+ **kwargs)
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_advanced.py b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_advanced.py
index c7b2f8f..d73ba36 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_advanced.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_advanced.py
@@ -33,9 +33,7 @@
class TestBGPVPNAdvanced(base.BaseBgpvpnTest, manager.NetworkScenarioTest):
def setUp(self):
super(TestBGPVPNAdvanced, self).setUp()
- self.security_group = self._create_security_group(
- tenant_id=self.bgpvpn_client.tenant_id
- )
+ self._create_security_group_for_test()
@decorators.idempotent_id("734213fb-8213-487d-9fe3-c8ff31758e18")
@utils.services("compute", "network")
@@ -98,7 +96,7 @@
def _create_networks_and_subnets(
self, name="bgp", subnet_cidr=None, port_security=True
):
- self.network = self._create_network(
+ self.network = super(manager.NetworkScenarioTest, self).create_network(
namestart=name, port_security_enabled=port_security
)
self.subnet = self._create_subnet_with_cidr(
@@ -160,7 +158,7 @@
"fixed_ips": [{"ip_address": ip_address}],
"namestart": "port-endpoint",
}
- self._create_port(
+ super(manager.NetworkScenarioTest, self).create_port(
network_id=self.network["id"],
client=clients.ports_client,
**create_port_kwargs
diff --git a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
index 55a9b33..1b0d6cc 100644
--- a/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
+++ b/neutron_tempest_plugin/bgpvpn/scenario/test_bgpvpn_basic.py
@@ -23,7 +23,6 @@
from tempest.common import utils
from tempest import config
from tempest.lib import decorators
-import testtools
from neutron_tempest_plugin.bgpvpn import base
from neutron_tempest_plugin.bgpvpn.scenario import manager
@@ -792,7 +791,6 @@
@decorators.idempotent_id('8de130c1-778a-4d86-913b-ff41be3c2f0b')
@utils.services('compute', 'network')
@utils.requires_ext(extension='bgpvpn-routes-control', service='network')
- @testtools.skipUnless(False, "Skip unless PRODX-25126 is fixed")
def test_bgpvpn_port_association_create_and_delete_bgpvpn(self):
"""This test checks port association in BGPVPN.
diff --git a/neutron_tempest_plugin/config.py b/neutron_tempest_plugin/config.py
index 74f7c03..8979055 100644
--- a/neutron_tempest_plugin/config.py
+++ b/neutron_tempest_plugin/config.py
@@ -217,6 +217,52 @@
CONF.register_group(bgpvpn_group)
CONF.register_opts(BgpvpnGroup, group="bgpvpn")
+
+DynamicRoutingGroup = [
+ cfg.StrOpt('frr_docker_image',
+ default='quay.io/frrouting/frr:8.5.0',
+ help=("Docker image with frr.")),
+ cfg.ListOpt('frr_provider_ipv4_ips',
+ default=["10.11.12.71/24", "10.11.12.72/24", "10.11.12.73/24",
+ "10.11.12.74/24", "10.11.12.75/24", "10.11.12.76/24"],
+ help=('List of ip addresses to bind frr containers. Require '
+ 'at least 3 items per class to run tests simulteniously. '
+ 'The addresses should be assigned on interface with '
+ 'tempest node.')),
+ cfg.ListOpt('frr_provider_ipv6_ips',
+ default=["2001:db8:a000::7001/64", "2001:db8:a000::7002/64",
+ "2001:db8:a000::7003/64", "2001:db8:a000::7004/64",
+ "2001:db8:a000::7005/64", "2001:db8:a000::7006/64"],
+ help=('List of ip addresses to bind frr containers. Require '
+ 'at least 3 items per class to run tests simulteniously. '
+ 'The addresses should be assigned on interface with '
+ 'tempest node.')),
+ cfg.BoolOpt('frr_bgp_ipv6_enabled',
+ default=False,
+ help=("Run dynamic routing ipv6 tests.")),
+ cfg.IntOpt('frr_bgp_timeout',
+ default=600,
+ help=('Timeout for bgp operation like setup neighborship '
+ 'or route update.')),
+ cfg.StrOpt('frr_bgp_ipv4_control_cidr',
+ default='10.0.0.0/8',
+ help=("CIDR for bgp control network on gateway nodes. Is "
+ "used as allowed range for dynamic neighbors.")),
+ cfg.StrOpt('frr_bgp_ipv6_control_cidr',
+ default='2001:db8::/32',
+ help=("CIDR for bgp control network on gateway nodes. Is "
+ "used as allowed range for dynamic neighbors.")),
+]
+dynamic_routing_group = cfg.OptGroup(name="dynamic_routing",
+ title=("Networking-DNR Service Options"))
+CONF.register_group(dynamic_routing_group)
+CONF.register_opts(DynamicRoutingGroup, group="dynamic_routing")
+
+# TODO(slaweq): This config option is added to avoid running fwaas tests twice
+# on stable branches till stable/stein. We need to remove this config option
+# once stable/stein is EOL. Fwaas tempest plugin has been merged into
+# neutron-tempest-plugin from Train. Train onwards fwaas tests will run from
+# neutron-tempest-plugins.
FwaasGroup = [
# TODO(tkajinam): This has been required since the plugin tests was merged
# into neutron-tempest-plugin in Train. Remove this after 2025.1 release.
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py
new file mode 100644
index 0000000..8505995
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/__init__.py
@@ -0,0 +1,185 @@
+# Copyright 2023 Mirantis Inc.
+#
+# 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 abc
+import json
+import os
+
+import docker
+import jinja2
+from tempest import config
+import tenacity
+
+
+CONF = config.CONF
+
+
+class GenericConnector:
+ @abc.abstractmethod
+ def exec(self, cmd):
+ pass
+
+
+class ContainerConnector(GenericConnector):
+ def __init__(self, container):
+ self.ctn = container
+
+ def exec(self, cmd):
+ res_cmd = f"vtysh -d bgpd -c '{cmd}'"
+ return self.ctn.exec_run(res_cmd)
+
+
+class BGPClient:
+ def __init__(self, connector):
+ self.connector = connector
+
+ def exec(self, cmd):
+ res = self.connector.exec(cmd)
+ if res.exit_code == 0:
+ return res.output
+ raise Exception(f"Failed to run command {cmd}")
+
+ def show_bgp_neighbors(self, *args):
+ cmd = ["show", "bgp", "neighbors", "json"]
+ cmd.extend(args)
+ return json.loads(self.exec(" ".join(cmd)))
+
+ def show_bgp_ipv4(self):
+ cmd = ["show", "bgp", "ipv4", "json"]
+ return json.loads(self.exec(" ".join(cmd)))
+
+ def show_bgp_ipv6(self):
+ cmd = ["show", "bgp", "ipv6", "json"]
+ return json.loads(self.exec(" ".join(cmd)))
+
+ def show_bgp(self):
+ cmd = ["show", "bgp", "json"]
+ return json.loads(self.exec(" ".join(cmd)))
+
+
+class FrrBGPContainer:
+ conf_dir = "/etc/frr"
+
+ def __init__(self, name, image, bgpd, daemons=None):
+ self.image = image
+ self.name = name
+ self.config_dir = os.path.join(CONF.state_path, f"ctn_base/{name}/")
+ self.volumes = [f"{self.config_dir}:{self.conf_dir}"]
+ self.daemons = daemons or {
+ "bgpd": {"enabled": "yes"},
+ "vtysh": {"enabled": "yes"},
+ }
+ self.bgpd = bgpd
+ self.docker_client = docker.from_env()
+ self._create_config_debian()
+ self.ctn = None
+ self._bgp_client = None
+
+ @property
+ def bgp_client(self):
+ if self._bgp_client is None:
+ self._bgp_client = BGPClient(ContainerConnector(self.ctn))
+ return self._bgp_client
+
+ def _create_config_debian(self):
+ environment = jinja2.Environment(
+ loader=jinja2.FileSystemLoader(
+ os.path.join(os.path.dirname(__file__), "templates")
+ )
+ )
+ template = environment.get_template("bgpd.conf")
+ if not os.path.exists(self.config_dir):
+ os.makedirs(self.config_dir)
+ for cfg_file in ["daemons", "vtysh.conf", "bgpd.conf"]:
+ with open(
+ os.path.join(self.config_dir, cfg_file),
+ mode="w",
+ encoding="utf-8",
+ ) as conf:
+ template = environment.get_template(cfg_file)
+ data = template.render(bgpd=self.bgpd, daemons=self.daemons)
+ conf.write(data)
+
+ def run(self, wait=True):
+ self.docker_client.images.pull(self.image)
+ self.ctn = self.docker_client.containers.create(
+ image=self.image,
+ name=self.name,
+ volumes=self.volumes,
+ privileged=True,
+ network_mode="host",
+ )
+ self.ctn.start()
+ if wait:
+ self._wait_running()
+
+ @tenacity.retry(
+ retry=tenacity.retry_if_result(lambda val: val is not True),
+ wait=tenacity.wait_random(min=5, max=15),
+ stop=tenacity.stop_after_delay(60),
+ )
+ def _wait_running(self):
+ self.ctn.reload()
+ if self.ctn.status == "running":
+ return True
+
+ def exec_on_ctn(self, cmd, capture=True, detach=False):
+ self.ctn.exec_run(cmd, detach=detach)
+
+ @tenacity.retry(
+ retry=tenacity.retry_if_result(lambda val: val is not True),
+ wait=tenacity.wait_random(min=5, max=15),
+ stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+ )
+ def bgp_check_neighbor_state(self, nei_ident, expected_state):
+ res = self.bgp_client.show_bgp_neighbors()
+ neighbor_states = [
+ nei.get("bgpState") == expected_state
+ for nei in res.values()
+ if nei.get("peerGroup") == nei_ident
+ ]
+ return len(neighbor_states) > 0 and all(neighbor_states)
+
+ @tenacity.retry(
+ retry=tenacity.retry_if_result(lambda val: val is not True),
+ wait=tenacity.wait_random(min=5, max=15),
+ stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+ )
+ def bgp_check_neighbor_absent(self, nei_ident):
+ res = self.bgp_client.show_bgp_neighbors()
+ neighbors = [
+ nei for nei in res.values() if nei.get("peerGroup") == nei_ident
+ ]
+ return len(neighbors) == 0
+
+ @tenacity.retry(
+ retry=tenacity.retry_if_result(lambda val: val is not True),
+ wait=tenacity.wait_random(min=5, max=15),
+ stop=tenacity.stop_after_delay(CONF.dynamic_routing.frr_bgp_timeout),
+ )
+ def bgp_check_rib(self, ip_version, cidr, nexthop=None):
+ res = getattr(self.bgp_client, f"show_bgp_{ip_version}")()
+ should = {"cidr": False}
+ if nexthop:
+ should["nexthop"] = False
+
+ for _cidr, routes in res.get("routes", {}).items():
+ if cidr == _cidr:
+ should["cidr"] = True
+ for route in routes:
+ if nexthop:
+ for hop_data in route.get("nexthops", []):
+ if nexthop == hop_data.get("ip"):
+ should["nexthop"] = True
+ return all(should.values())
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py b/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py
new file mode 100644
index 0000000..f2b96d8
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/constants.py
@@ -0,0 +1,7 @@
+# Various states of bgp state machine.
+BGP_FSM_IDLE = "Idle"
+BGP_FSM_CONNECT = "Connect"
+BGP_FSM_ACTIVE = "Active"
+BGP_FSM_OPEN_SENT = "OpenSent"
+BGP_FSM_OPEN_CONFIRM = "OpenConfirm"
+BGP_FSM_ESTABLISHED = "Established"
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf
new file mode 100644
index 0000000..27d8fab
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/bgpd.conf
@@ -0,0 +1,23 @@
+!
+router bgp {{ bgpd.bgp.as_number }}
+ bgp router-id {{ bgpd.bgp.router_id }}
+ {%- for neighbor in bgpd.bgp.neighbors %}
+ {%- if neighbor.peer_group %}
+ neighbor {{ neighbor.address }} peer-group
+ {%- endif %}
+ neighbor {{ neighbor.address }} remote-as {{ neighbor.as_number }}
+ {%- if neighbor.get('passive', false) %}
+ neighbor {{ neighbor.address }} passive
+ {%- endif %}
+ {%- endfor %}
+ {%- if bgpd.bgp.get('listen', {}).get('limit') %}
+ bgp listen limit {{ bgpd.bgp.listen.limit }}
+ {%- endif %}
+ {%- for listen_range in bgpd.bgp.get('listen', {}).get('ranges', []) %}
+ bgp listen range {{ listen_range.cidr }} peer-group {{ listen_range.peer_group }}
+ {%- endfor %}
+ {%- if bgpd.bgp.ebgp_requires_policy is false %}
+ no bgp ebgp-requires-policy
+ {%- endif %}
+ neighbor dnr timers 10 30
+!
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons
new file mode 100644
index 0000000..314e231
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/daemons
@@ -0,0 +1,33 @@
+zebra=no
+bgpd={{ daemons.bgpd.enabled }}
+ospfd=no
+ospf6d=no
+ripd=no
+ripngd=no
+isisd=no
+babeld=no
+
+#
+# If this option is set the /etc/init.d/frr script automatically loads
+# the config via "vtysh -b" when the servers are started.
+# Check /etc/pam.d/frr if you intend to use "vtysh"!
+#
+vtysh_enable={{ daemons.vtysh.enabled }}
+zebra_options=" -A 127.0.0.1 -s 90000000"
+bgpd_options=" -A 127.0.0.1 --listenon {{ daemons.bgpd.listenon }}"
+ospfd_options=" -A 127.0.0.1"
+ospf6d_options=" -A ::1"
+ripd_options=" -A 127.0.0.1"
+ripngd_options=" -A ::1"
+isisd_options=" -A 127.0.0.1"
+pimd_options=" -A 127.0.0.1"
+ldpd_options=" -A 127.0.0.1"
+nhrpd_options=" -A 127.0.0.1"
+eigrpd_options=" -A 127.0.0.1"
+babeld_options=" -A 127.0.0.1"
+sharpd_options=" -A 127.0.0.1"
+pbrd_options=" -A 127.0.0.1"
+staticd_options="-A 127.0.0.1"
+bfdd_options=" -A 127.0.0.1"
+fabricd_options="-A 127.0.0.1"
+vrrpd_options=" -A 127.0.0.1"
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf
new file mode 100644
index 0000000..e0ab9cb
--- /dev/null
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/frr/templates/vtysh.conf
@@ -0,0 +1 @@
+service integrated-vtysh-config
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
index 44990bd..14957d3 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README
@@ -14,23 +14,19 @@
| dragent | |
+---------+ |
| |
- | +--------------+
- | |
- +--------+
- | docker |
- | bridge |
- +--------+
- |
- +-----------+------------+-------
+ | |
+ | |
+ | |
+ +-----------+----+-------+-------
| |
+---------+ +---------+
- docker | quagga1 | | quagga2 | ...
+ docker | frr1 | | frr1 | ...
container +---------+ +---------+
-docker container environment is provided by test tool of os-ken.
+docker container environment is provided by test tools with help of
+python-docker library.
It has the following functions:
-- build and remove a container image.
- run, stop and remove a container.
-- some operations to quagga container.
-- get some information from quagga container.
+- some operations to frr container.
+- get some information from frr container.
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
index 3f799bb..fec6231 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py
@@ -16,11 +16,10 @@
import collections
import threading
-import time
+import docker
import netaddr
-from os_ken.tests.integrated.common import docker_base as ctn_base
from tempest.common import utils
from tempest import config
@@ -36,10 +35,6 @@
SubNet = collections.namedtuple('SubNet', 'name, cidr, mask')
Router = collections.namedtuple('Router', 'name, gw')
AS = collections.namedtuple('AS', 'asn, router_id, adv_net')
-CHECKTIME = 180
-CHECKTIME_INFO = 60
-CHECKTIME_INT = 1
-BRIDGE_TYPE = ctn_base.BRIDGE_TYPE_DOCKER
def _setup_client_args(auth_provider):
@@ -111,7 +106,7 @@
for ctn in cls.containers:
try:
ctn.stop()
- except ctn_base.CommandError:
+ except docker.errors.APIError:
pass
ctn.remove()
for br in cls.bridges:
@@ -250,122 +245,6 @@
peer_id)
return (speaker_id, peer_ids)
- def get_remote_as_state(self, l_as, r_as,
- expected_state,
- init_state=ctn_base.BGP_FSM_IDLE,
- checktime=CHECKTIME,
- checktime_int=CHECKTIME_INT):
- neighbor_state = init_state
- for i in range(0, checktime):
- neighbor_state = r_as.get_neighbor_state(l_as)
- if neighbor_state == expected_state:
- break
- time.sleep(checktime_int)
- return neighbor_state
-
- def check_remote_as_state(self, l_as, r_as,
- expected_state,
- init_state=ctn_base.BGP_FSM_IDLE,
- checktime=CHECKTIME,
- checktime_int=CHECKTIME_INT):
- neighbor_state = self.get_remote_as_state(l_as, r_as,
- expected_state,
- init_state=init_state,
- checktime=checktime,
- checktime_int=checktime_int)
- self.assertEqual(neighbor_state, expected_state)
-
- def get_remote_as_of_state_ok(self, l_as, r_ass,
- expected_state,
- init_state=ctn_base.BGP_FSM_IDLE,
- checktime=CHECKTIME,
- checktime_int=CHECKTIME_INT):
- neighbor_state = init_state
- ras_list = []
- ras_max = len(r_ass)
- for r_as in r_ass:
- ras_list.append({'as': r_as, 'check': False})
- ok_ras = []
- for i in range(0, checktime):
- for ras in ras_list:
- if ras['check']:
- continue
- neighbor_state = ras['as'].get_neighbor_state(l_as)
- if neighbor_state == expected_state:
- ras['check'] = True
- ok_ras.append(ras['as'])
- if len(ok_ras) >= ras_max:
- break
- time.sleep(checktime_int)
- return ok_ras
-
- def check_multi_remote_as_state(self, l_as, r_ass,
- expected_state,
- init_state=ctn_base.BGP_FSM_IDLE,
- checktime=CHECKTIME,
- checktime_int=CHECKTIME_INT):
- ok_ras = self.get_remote_as_of_state_ok(
- l_as, r_ass,
- expected_state,
- init_state=init_state,
- checktime=checktime,
- checktime_int=checktime_int)
- self.assertEqual(len(ok_ras), len(r_ass))
-
- def get_remote_as_rib(self, r_as, prefix, rf, key, expected_item,
- checktime=CHECKTIME_INFO,
- checktime_int=CHECKTIME_INT):
- item = None
- for i in range(0, checktime):
- rib = r_as.get_global_rib(prefix=prefix, rf=rf)
- if rib and key in rib[0]:
- if expected_item == rib[0][key]:
- item = rib[0][key]
- break
- time.sleep(checktime_int)
- return item
-
- def check_remote_as_rib(self, r_as, prefix, rf, key, expected_item,
- checktime=CHECKTIME_INFO,
- checktime_int=CHECKTIME_INT):
- item = self.get_remote_as_rib(r_as=r_as, prefix=prefix, rf=rf,
- key=key, expected_item=expected_item,
- checktime=checktime,
- checktime_int=checktime_int)
- self.assertEqual(expected_item, item)
-
- def get_remote_as_of_rib_ok(self, r_ass, prefix, rf, key, expected_item,
- checktime=CHECKTIME_INFO,
- checktime_int=CHECKTIME_INT):
- ras_list = []
- ras_max = len(r_ass)
- for r_as in r_ass:
- ras_list.append({'as': r_as, 'check': False})
- ok_ras = []
- for i in range(0, checktime):
- for ras in ras_list:
- if ras['check']:
- continue
- rib = r_as.get_global_rib(prefix=prefix, rf=rf)
- if rib and key in rib[0]:
- if expected_item == rib[0][key]:
- ras['check'] = True
- ok_ras.append(ras['as'])
- if len(ok_ras) >= ras_max:
- break
- time.sleep(checktime_int)
- return ok_ras
-
- def check_multi_remote_as_rib(self, r_ass, prefix, rf, key, expected_item,
- checktime=CHECKTIME_INFO,
- checktime_int=CHECKTIME_INT):
- ok_ras = self.get_remote_as_of_rib_ok(
- r_ass=r_ass, prefix=prefix, rf=rf,
- key=key, expected_item=expected_item,
- checktime=checktime,
- checktime_int=checktime_int)
- self.assertEqual(len(ok_ras), len(r_ass))
-
def get_next_hop(self, speaker_id, dest_addr):
routes = self.bgp_adm_client.get_bgp_advertised_routes(speaker_id)
next_hop = ''
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
index 643620f..305ab25 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py
@@ -16,10 +16,9 @@
from tempest import config
+from neutron_tempest_plugin.neutron_dynamic_routing.frr import constants
from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
-from os_ken.tests.integrated.common import docker_base as ctn_base
-
CONF = config.CONF
@@ -41,8 +40,8 @@
ext_net_id,
self.bgp_speaker_args,
[self.bgp_peer_args[0]])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
+ self.r_ass[0].bgp_check_neighbor_state("dnr",
+ constants.BGP_FSM_ESTABLISHED)
def _test_check_advertised_tenant_network(self, ip_version):
self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
@@ -60,12 +59,12 @@
ext_net_id,
self.bgp_speaker_args,
[self.bgp_peer_args[0]])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
+
+ self.r_ass[0].bgp_check_neighbor_state("dnr",
+ constants.BGP_FSM_ESTABLISHED)
rf = 'ipv' + str(ip_version)
- self.check_remote_as_rib(self.r_ass[0], TNet.cidr, rf,
- 'nexthop',
- self.get_next_hop(speaker_id, TNet.cidr))
+ self.r_ass[0].bgp_check_rib(rf, TNet.cidr,
+ self.get_next_hop(speaker_id, TNet.cidr))
def _test_check_advertised_multiple_tenant_network(self, ip_version):
self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
@@ -88,13 +87,14 @@
ext_net_id,
self.bgp_speaker_args,
[self.bgp_peer_args[0]])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
+
+ self.r_ass[0].bgp_check_neighbor_state("dnr",
+ constants.BGP_FSM_ESTABLISHED)
+
rf = 'ipv' + str(ip_version)
for cidr in tnets_cidr:
- self.check_remote_as_rib(self.r_ass[0], cidr, rf,
- 'nexthop',
- self.get_next_hop(speaker_id, cidr))
+ self.r_ass[0].bgp_check_rib(rf, cidr,
+ self.get_next_hop(speaker_id, cidr))
def _test_check_neighbor_established_with_multiple_peers(
self, ip_version):
@@ -115,8 +115,8 @@
ext_net_id,
self.bgp_speaker_args,
self.bgp_peer_args)
- self.check_multi_remote_as_state(self.dr, self.r_ass,
- ctn_base.BGP_FSM_ESTABLISHED)
+ for r_as in self.r_ass:
+ r_as.bgp_check_neighbor_state("dnr", constants.BGP_FSM_ESTABLISHED)
def _test_check_advertised_tenant_network_with_multiple_peers(
self, ip_version):
@@ -137,9 +137,10 @@
ext_net_id,
self.bgp_speaker_args,
self.bgp_peer_args)
- self.check_multi_remote_as_state(self.dr, self.r_ass,
- ctn_base.BGP_FSM_ESTABLISHED)
+ for r_as in self.r_ass:
+ r_as.bgp_check_neighbor_state("dnr", constants.BGP_FSM_ESTABLISHED)
+
rf = 'ipv' + str(ip_version)
- next_hop = self.get_next_hop(speaker_id, TNet.cidr)
- self.check_multi_remote_as_rib(self.r_ass, TNet.cidr, rf,
- 'nexthop', next_hop)
+ for r_as in self.r_ass:
+ r_as.bgp_check_rib(rf, TNet.cidr,
+ self.get_next_hop(speaker_id, TNet.cidr))
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py
+++ /dev/null
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
deleted file mode 100644
index 3719dd6..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (C) 2016 VA Linux Systems Japan K.K.
-# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
-# 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.
-
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
-
-
-class BgpSpeakerBasicTestJSONBase(base.BgpSpeakerScenarioTestJSONBase):
-
- RAS_MAX = 3
- public_gw = '192.168.20.1'
- MyScope = base.Scope(name='my-scope')
- PNet = base.Net(name='', net='172.24.6.0', mask=24,
- cidr='172.24.6.0/24', router=None)
- PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
- prefixes=[PNet.net + '/8'])
- PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
- TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
- prefixes=['10.10.0.0/16'])
- L_AS = base.AS(asn='64512', router_id='192.168.0.2', adv_net='')
- ras_l = [
- base.AS(asn='64522', router_id='192.168.0.12',
- adv_net='192.168.162.0/24'),
- base.AS(asn='64523', router_id='192.168.0.13',
- adv_net='192.168.163.0/24'),
- base.AS(asn='64524', router_id='192.168.0.14',
- adv_net='192.168.164.0/24')
- ]
-
- bgp_speaker_args = {
- 'local_as': L_AS.asn,
- 'ip_version': 4,
- 'name': 'my-bgp-speaker1',
- 'advertise_floating_ip_host_routes': True,
- 'advertise_tenant_networks': True
- }
- bgp_peer_args = [
- {'remote_as': ras_l[0].asn,
- 'name': 'my-bgp-peer1',
- 'peer_ip': None,
- 'auth_type': 'none'},
- {'remote_as': ras_l[1].asn,
- 'name': 'my-bgp-peer2',
- 'peer_ip': None,
- 'auth_type': 'none'},
- {'remote_as': ras_l[2].asn,
- 'name': 'my-bgp-peer3',
- 'peer_ip': None,
- 'auth_type': 'none'}
- ]
-
- def setUp(self):
- super(BgpSpeakerBasicTestJSONBase, self).setUp()
-
- @classmethod
- def resource_setup_container(cls):
- cls.brdc = ctn_base.Bridge(name='br-docker-basic',
- subnet='192.168.20.0/24',
- start_ip='192.168.20.128',
- end_ip='192.168.20.254',
- self_ip=True,
- fixed_ip=cls.public_gw + '/24',
- br_type=base.BRIDGE_TYPE)
- cls.bridges.append(cls.brdc)
- # This is dummy container object for a dr service.
- # This keeps data which passes to a quagga container.
- cls.dr = ctn_base.BGPContainer(name='dummy-dr-basic',
- asn=int(cls.L_AS.asn),
- router_id=cls.L_AS.router_id)
- cls.dr.set_addr_info(bridge='br-docker-basic', ipv4=cls.public_gw)
- # quagga container
- cls.dockerimg = ctn_base.DockerImage(baseimage=cls.baseimage)
- cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
- cls.images.append(cls.q_img)
- for i in range(cls.RAS_MAX):
- qg = quagga.QuaggaBGPContainer(name='q-basic-' + str(i + 1),
- asn=int(cls.ras_l[i].asn),
- router_id=cls.ras_l[i].router_id,
- ctn_image_name=cls.q_img)
- cls.containers.append(qg)
- cls.r_ass.append(qg)
- qg.add_route(cls.ras_l[i].adv_net)
- qg.run(wait=True)
- cls.r_as_ip.append(cls.brdc.addif(qg))
- qg.add_peer(cls.dr, bridge=cls.brdc.name,
- peer_info={'passive': True})
-
- cls.tnet_gen = cls.get_subnet(start='10.10.1.0',
- end='10.10.255.0',
- step=256)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py
deleted file mode 100644
index 1421734..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py
+++ /dev/null
@@ -1,132 +0,0 @@
-# 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 tempest.common import utils
-from tempest import config
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
- import base_test_proto as test_base
-
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
-CONF = config.CONF
-
-
-class BgpSpeaker4byteASNTest(test_base.BgpSpeakerProtoTestBase):
-
- RAS_MAX = 3
- ip_version = 4
- public_gw = '192.168.10.1'
- MyScope = base.Scope(name='my-scope')
- PNet = base.Net(name='', net='172.24.6.0', mask=24,
- cidr='172.24.6.0/24', router=None)
- PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
- prefixes=[PNet.net + '/8'])
- PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
- TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
- prefixes=['10.10.0.0/16'])
- L_AS = base.AS(asn='4200000000', router_id='192.168.0.3', adv_net='')
- ras_l = [
- base.AS(asn='4210000000', router_id='192.168.0.12',
- adv_net='192.168.162.0/24'),
- base.AS(asn='64522', router_id='192.168.0.13',
- adv_net='192.168.163.0/24'),
- base.AS(asn='4230000000', router_id='192.168.0.14',
- adv_net='192.168.164.0/24')
- ]
-
- bgp_speaker_args = {
- 'local_as': L_AS.asn,
- 'ip_version': ip_version,
- 'name': 'my-bgp-speaker1',
- 'advertise_floating_ip_host_routes': True,
- 'advertise_tenant_networks': True
- }
- bgp_peer_args = [
- {'remote_as': ras_l[0].asn,
- 'name': 'my-bgp-peer1',
- 'peer_ip': None,
- 'auth_type': 'none'},
- {'remote_as': ras_l[1].asn,
- 'name': 'my-bgp-peer2',
- 'peer_ip': None,
- 'auth_type': 'none'},
- {'remote_as': ras_l[2].asn,
- 'name': 'my-bgp-peer3',
- 'peer_ip': None,
- 'auth_type': 'none'}
- ]
-
- @classmethod
- @utils.requires_ext(extension="bgp_4byte_asn", service="network")
- def resource_setup(cls):
- super(BgpSpeaker4byteASNTest, cls).resource_setup()
-
- @classmethod
- def resource_setup_container(cls):
- cls.brdc = ctn_base.Bridge(name='br-docker-4byte-asn',
- subnet='192.168.10.0/24',
- start_ip='192.168.10.128',
- end_ip='192.168.10.254',
- self_ip=True,
- fixed_ip=cls.public_gw + '/24',
- br_type=base.BRIDGE_TYPE)
- cls.bridges.append(cls.brdc)
- # This is dummy container object for a dr service.
- # This keeps data which passes to a quagga container.
- cls.dr = ctn_base.BGPContainer(name='dummy-dr-4byte-asn',
- asn=int(cls.L_AS.asn),
- router_id=cls.L_AS.router_id)
- cls.dr.set_addr_info(bridge='br-docker-4byte-asn', ipv4=cls.public_gw)
- # quagga container
- cls.dockerimg = ctn_base.DockerImage(baseimage=cls.baseimage)
- cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
- cls.images.append(cls.q_img)
- for i in range(cls.RAS_MAX):
- qg = quagga.QuaggaBGPContainer(name='q-4byte-asn-' + str(i + 1),
- asn=int(cls.ras_l[i].asn),
- router_id=cls.ras_l[i].router_id,
- ctn_image_name=cls.q_img)
- cls.containers.append(qg)
- cls.r_ass.append(qg)
- qg.add_route(cls.ras_l[i].adv_net)
- qg.run(wait=True)
- cls.r_as_ip.append(cls.brdc.addif(qg))
- qg.add_peer(cls.dr, bridge=cls.brdc.name,
- peer_info={'passive': True})
- cls.tnet_gen = cls.get_subnet(start='10.10.1.0', end='10.10.255.0',
- step=256)
-
- @decorators.idempotent_id('9f18c931-a59e-4a27-939b-21124995ffe2')
- def test_check_neighbor_established(self):
- self._test_check_neighbor_established(self.ip_version)
-
- @decorators.idempotent_id('73466aa5-7d1d-4f9f-8fb4-4100fad2dffe')
- def test_check_advertised_tenant_network(self):
- self._test_check_advertised_tenant_network(self.ip_version)
-
- @decorators.idempotent_id('c3158328-2f69-4aa2-b1b7-5a06ab58afaf')
- def test_check_advertised_multiple_tenant_network(self):
- self._test_check_advertised_multiple_tenant_network(self.ip_version)
-
- @decorators.idempotent_id('212a3d82-ac50-43dc-b657-030b1133643e')
- def test_check_neighbor_established_with_multiple_peers(self):
- self._test_check_neighbor_established_with_multiple_peers(
- self.ip_version)
-
- @decorators.idempotent_id('c72411c8-ea79-495d-bdbd-a10159642676')
- def test_check_advertised_tenant_network_with_multiple_peers(self):
- self._test_check_advertised_tenant_network_with_multiple_peers(
- self.ip_version)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
deleted file mode 100644
index 90a6815..0000000
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py
+++ /dev/null
@@ -1,114 +0,0 @@
-# Copyright (C) 2016 VA Linux Systems Japan K.K.
-# Copyright (C) 2016 Fumihiko Kakuma <kakuma at valinux co jp>
-# 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.
-
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from tempest import config
-from tempest.lib import decorators
-
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
- import base as s_base
-from neutron_tempest_plugin.neutron_dynamic_routing.scenario.basic import base
-
-CONF = config.CONF
-
-
-class BgpSpeakerBasicTest(base.BgpSpeakerBasicTestJSONBase):
-
- @decorators.idempotent_id('cc615252-c6cb-4d75-a70e-608fb2c3736a')
- def test_schedule_added_speaker(self):
- self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
- num, subnet = next(self.tnet_gen)
- mask = '/' + str(self.TPool.prefixlen)
- TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
- cidr=subnet + mask, router=None)
- TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
- MyRouter = s_base.Router(name='my-router' + str(num), gw='')
- ext_net_id = self.create_bgp_network(
- 4, self.MyScope,
- self.PNet, self.PPool, self.PSubNet,
- self.TPool, [[TNet, TSubNet, MyRouter]])
- speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
- ext_net_id,
- self.bgp_speaker_args,
- [self.bgp_peer_args[0]])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
-
- @decorators.idempotent_id('ce98c33c-0ffa-49ae-b365-da836406793b')
- def test_unschedule_deleted_speaker(self):
- self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
- num, subnet = next(self.tnet_gen)
- mask = '/' + str(self.TPool.prefixlen)
- TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
- cidr=subnet + mask, router=None)
- TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
- MyRouter = s_base.Router(name='my-router' + str(num), gw='')
- ext_net_id = self.create_bgp_network(
- 4, self.MyScope,
- self.PNet, self.PPool, self.PSubNet,
- self.TPool, [[TNet, TSubNet, MyRouter]])
- speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
- ext_net_id,
- self.bgp_speaker_args,
- [self.bgp_peer_args[0]],
- auto_delete=False)
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
- self.delete_bgp_speaker(speaker_id)
- self.delete_bgp_peer(peers_ids[0])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ACTIVE,
- init_state=ctn_base.BGP_FSM_ESTABLISHED)
-
- @decorators.idempotent_id('aa6c565c-ded3-413b-8dc9-3928b3b0e38f')
- def test_remove_add_speaker_agent(self):
- self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
- num, subnet = next(self.tnet_gen)
- mask = '/' + str(self.TPool.prefixlen)
- TNet = s_base.Net(name='', net=subnet, mask=self.TPool.prefixlen,
- cidr=subnet + mask, router=None)
- TSubNet = s_base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
- MyRouter = s_base.Router(name='my-router' + str(num), gw='')
- ext_net_id = self.create_bgp_network(
- 4, self.MyScope,
- self.PNet, self.PPool, self.PSubNet,
- self.TPool, [[TNet, TSubNet, MyRouter]])
- speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
- ext_net_id,
- self.bgp_speaker_args,
- [self.bgp_peer_args[0]])
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
- agent_list = self.bgp_client.list_dragents_for_bgp_speaker(
- speaker_id)['agents']
- self.assertEqual(1, len(agent_list))
- agent_id = agent_list[0]['id']
- self.bgp_client.remove_bgp_speaker_from_dragent(agent_id, speaker_id)
- # NOTE(tidwellr) This assertion can fail due to the fact that BGP
- # speakers are auto-scheduled. The BGP process can quickly return to
- # ACTIVE status before this gets asserted. Let's see how it goes with
- # this commented out.
- # self.check_remote_as_state(self.dr, self.r_ass[0],
- # ctn_base.BGP_FSM_ACTIVE,
- # init_state=ctn_base.BGP_FSM_ESTABLISHED)
-
- # Ignore errors re-associating the BGP speaker, auto-scheduling may
- # have already added it to an agent. The next assertion is what
- # matters.
- self.bgp_client.add_bgp_speaker_to_dragent(agent_id, speaker_id,
- ignore_errors=True)
- self.check_remote_as_state(self.dr, self.r_ass[0],
- ctn_base.BGP_FSM_ESTABLISHED)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
index 57c9018..5a9deae 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py
@@ -14,24 +14,24 @@
# License for the specific language governing permissions and limitations
# under the License.
+from tempest.common import utils
from tempest import config
from tempest.lib import decorators
+from neutron_tempest_plugin.neutron_dynamic_routing import frr
+from neutron_tempest_plugin.neutron_dynamic_routing.frr import constants
from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
import base_test_proto as test_base
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
-
CONF = config.CONF
class BgpSpeakerIpv4Test(test_base.BgpSpeakerProtoTestBase):
RAS_MAX = 3
+ IP_ALLOCATION_MULTIPLIER = 0
ip_version = 4
- public_gw = '192.168.11.1'
MyScope = base.Scope(name='my-scope')
PNet = base.Net(name='', net='172.24.6.0', mask=24,
cidr='172.24.6.0/24', router=None)
@@ -73,42 +73,71 @@
]
def setUp(self):
+ for ctn in self.containers:
+ ctn.restart()
super(BgpSpeakerIpv4Test, self).setUp()
@classmethod
+ def skip_checks(cls):
+ super().skip_checks()
+ if CONF.production:
+ raise cls.skipException('Skip on production environment.')
+
+ @classmethod
def resource_setup_container(cls):
- cls.brdc = ctn_base.Bridge(name='br-docker-ipv4',
- subnet='192.168.11.0/24',
- start_ip='192.168.11.128',
- end_ip='192.168.11.254',
- self_ip=True,
- fixed_ip=cls.public_gw + '/24',
- br_type=base.BRIDGE_TYPE)
- cls.bridges.append(cls.brdc)
- # This is dummy container object for a dr service.
- # This keeps data which passes to a quagga container.
- cls.dr = ctn_base.BGPContainer(name='dr', asn=int(cls.L_AS.asn),
- router_id=cls.L_AS.router_id)
- cls.dr.set_addr_info(bridge='br-docker-ipv4', ipv4=cls.public_gw)
- # quagga container
- cls.dockerimg = ctn_base.DockerImage(baseimage=cls.baseimage)
- cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
- cls.images.append(cls.q_img)
+ # frr container
for i in range(cls.RAS_MAX):
- qg = quagga.QuaggaBGPContainer(name='q' + str(i + 1),
- asn=int(cls.ras_l[i].asn),
- router_id=cls.ras_l[i].router_id,
- ctn_image_name=cls.q_img)
- cls.containers.append(qg)
- cls.r_ass.append(qg)
- qg.add_route(cls.ras_l[i].adv_net)
+ container_number = i + 1
+ bgp_listenon = CONF.dynamic_routing.frr_provider_ipv4_ips[
+ cls.IP_ALLOCATION_MULTIPLIER + container_number - 1
+ ].split('/')[0]
+ bgpd = {
+ 'bgp': {
+ 'as_number': int(cls.ras_l[i].asn),
+ 'router_id': bgp_listenon,
+ 'neighbors': [
+ {
+ 'peer_group': True,
+ 'address': 'dnr',
+ 'as_number': cls.L_AS.asn,
+ 'passive': True,
+ }
+ ],
+ 'listen': {
+ 'ranges': [
+ {'cidr': CONF.dynamic_routing.
+ frr_bgp_ipv4_control_cidr,
+ 'peer_group': 'dnr'}
+ ],
+ 'limit': 10,
+ },
+ 'ebgp_requires_policy': False,
+ }
+ }
+ daemons = {
+ "bgpd": {
+ "enabled": "yes",
+ "listenon": bgp_listenon,
+ },
+ "vtysh": {"enabled": "yes"},
+ }
+ qg = frr.FrrBGPContainer(
+ "q" + str(container_number),
+ CONF.dynamic_routing.frr_docker_image,
+ bgpd=bgpd,
+ daemons=daemons,
+ )
qg.run(wait=True)
- cls.r_as_ip.append(cls.brdc.addif(qg))
- qg.add_peer(cls.dr, bridge=cls.brdc.name,
- peer_info={'passive': True})
+ cls.containers.append(qg.ctn)
+ cls.r_ass.append(qg)
+ cls.r_as_ip.append(
+ CONF.dynamic_routing.frr_provider_ipv4_ips[
+ cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+ )
cls.tnet_gen = cls.get_subnet(start='10.10.1.0', end='10.10.255.0',
step=256)
+ @decorators.attr(type='smoke')
@decorators.idempotent_id('7f2acbc2-ff88-4a63-aa02-a2f9feb3f5b0')
def test_check_neighbor_established(self):
self._test_check_neighbor_established(self.ip_version)
@@ -130,3 +159,246 @@
def test_check_advertised_tenant_network_with_multiple_peers(self):
self._test_check_advertised_tenant_network_with_multiple_peers(
self.ip_version)
+
+ @decorators.idempotent_id('cc615252-c6cb-4d75-a70e-608fb2c3736a')
+ def test_schedule_added_speaker(self):
+ self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+ num, subnet = next(self.tnet_gen)
+ mask = '/' + str(self.TPool.prefixlen)
+ TNet = base.Net(
+ name='',
+ net=subnet,
+ mask=self.TPool.prefixlen,
+ cidr=subnet + mask,
+ router=None,
+ )
+ TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+ MyRouter = base.Router(name='my-router' + str(num), gw='')
+ ext_net_id = self.create_bgp_network(
+ 4,
+ self.MyScope,
+ self.PNet,
+ self.PPool,
+ self.PSubNet,
+ self.TPool,
+ [[TNet, TSubNet, MyRouter]],
+ )
+ speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+ ext_net_id, self.bgp_speaker_args, [self.bgp_peer_args[0]]
+ )
+ self.r_ass[0].bgp_check_neighbor_state(
+ 'dnr', constants.BGP_FSM_ESTABLISHED
+ )
+
+ @decorators.idempotent_id('ce98c33c-0ffa-49ae-b365-da836406793b')
+ def test_unschedule_deleted_speaker(self):
+ self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+ num, subnet = next(self.tnet_gen)
+ mask = '/' + str(self.TPool.prefixlen)
+ TNet = base.Net(
+ name='',
+ net=subnet,
+ mask=self.TPool.prefixlen,
+ cidr=subnet + mask,
+ router=None,
+ )
+ TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+ MyRouter = base.Router(name='my-router' + str(num), gw='')
+ ext_net_id = self.create_bgp_network(
+ 4,
+ self.MyScope,
+ self.PNet,
+ self.PPool,
+ self.PSubNet,
+ self.TPool,
+ [[TNet, TSubNet, MyRouter]],
+ )
+ speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+ ext_net_id,
+ self.bgp_speaker_args,
+ [self.bgp_peer_args[0]],
+ auto_delete=False,
+ )
+ self.r_ass[0].bgp_check_neighbor_state(
+ 'dnr', constants.BGP_FSM_ESTABLISHED
+ )
+ self.delete_bgp_speaker(speaker_id)
+ self.delete_bgp_peer(peers_ids[0])
+ self.r_ass[0].bgp_check_neighbor_absent('dnr')
+
+ @decorators.idempotent_id('aa6c565c-ded3-413b-8dc9-3928b3b0e38f')
+ def test_remove_add_speaker_agent(self):
+ self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0]
+ num, subnet = next(self.tnet_gen)
+ mask = '/' + str(self.TPool.prefixlen)
+ TNet = base.Net(
+ name='',
+ net=subnet,
+ mask=self.TPool.prefixlen,
+ cidr=subnet + mask,
+ router=None,
+ )
+ TSubNet = base.SubNet(name='', cidr=TNet.cidr, mask=TNet.mask)
+ MyRouter = base.Router(name='my-router' + str(num), gw='')
+ ext_net_id = self.create_bgp_network(
+ 4,
+ self.MyScope,
+ self.PNet,
+ self.PPool,
+ self.PSubNet,
+ self.TPool,
+ [[TNet, TSubNet, MyRouter]],
+ )
+ speaker_id, peers_ids = self.create_and_add_peers_to_speaker(
+ ext_net_id, self.bgp_speaker_args, [self.bgp_peer_args[0]]
+ )
+ self.r_ass[0].bgp_check_neighbor_state(
+ 'dnr', constants.BGP_FSM_ESTABLISHED
+ )
+ agent_list = self.bgp_client.list_dragents_for_bgp_speaker(speaker_id)[
+ 'agents'
+ ]
+ self.assertEqual(1, len(agent_list))
+ agent_id = agent_list[0]['id']
+ self.bgp_client.remove_bgp_speaker_from_dragent(agent_id, speaker_id)
+ self.r_ass[0].bgp_check_neighbor_absent('dnr')
+ self.bgp_client.add_bgp_speaker_to_dragent(
+ agent_id, speaker_id, ignore_errors=True
+ )
+ self.r_ass[0].bgp_check_neighbor_state(
+ 'dnr', constants.BGP_FSM_ESTABLISHED
+ )
+
+
+class BgpSpeaker4byteASNTest(test_base.BgpSpeakerProtoTestBase):
+
+ RAS_MAX = 3
+ IP_ALLOCATION_MULTIPLIER = 3
+ ip_version = 4
+ MyScope = base.Scope(name='my-scope')
+ PNet = base.Net(name='', net='172.24.6.0', mask=24, cidr='172.24.6.0/24',
+ router=None)
+ PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
+ prefixes=[PNet.net + '/8'])
+ PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask)
+ TPool = base.Pool(name='tenant-test-pool', prefixlen=28,
+ prefixes=['10.10.0.0/16'])
+ L_AS = base.AS(asn='4200000000', router_id='192.168.0.3', adv_net='')
+ ras_l = [
+ base.AS(asn='4210000000', router_id='192.168.0.12',
+ adv_net='192.168.162.0/24'),
+ base.AS(asn='64522', router_id='192.168.0.13',
+ adv_net='192.168.163.0/24'),
+ base.AS(asn='4230000000', router_id='192.168.0.14',
+ adv_net='192.168.164.0/24'),
+ ]
+
+ bgp_speaker_args = {
+ 'local_as': L_AS.asn,
+ 'ip_version': ip_version,
+ 'name': 'my-bgp-speaker1',
+ 'advertise_floating_ip_host_routes': True,
+ 'advertise_tenant_networks': True,
+ }
+ bgp_peer_args = [
+ {'remote_as': ras_l[0].asn,
+ 'name': 'my-bgp-peer1',
+ 'peer_ip': None,
+ 'auth_type': 'none'},
+ {'remote_as': ras_l[1].asn,
+ 'name': 'my-bgp-peer2',
+ 'peer_ip': None,
+ 'auth_type': 'none'},
+ {'remote_as': ras_l[2].asn,
+ 'name': 'my-bgp-peer3',
+ 'peer_ip': None,
+ 'auth_type': 'none'},
+ ]
+
+ @classmethod
+ @utils.requires_ext(extension='bgp_4byte_asn', service='network')
+ def resource_setup(cls):
+ super(BgpSpeaker4byteASNTest, cls).resource_setup()
+
+ @classmethod
+ def skip_checks(cls):
+ super().skip_checks()
+ if CONF.production:
+ raise cls.skipException('Skip on production environment.')
+
+ @classmethod
+ def resource_setup_container(cls):
+ # frr container
+ for i in range(cls.RAS_MAX):
+ container_number = i + 1
+ bgp_listenon = CONF.dynamic_routing.frr_provider_ipv4_ips[
+ cls.IP_ALLOCATION_MULTIPLIER + container_number - 1
+ ].split('/')[0]
+ bgpd = {
+ 'bgp': {
+ 'as_number': int(cls.ras_l[i].asn),
+ 'router_id': bgp_listenon,
+ 'neighbors': [
+ {
+ 'peer_group': True,
+ 'address': 'dnr',
+ 'as_number': cls.L_AS.asn,
+ 'passive': True,
+ }
+ ],
+ 'listen': {
+ 'ranges': [
+ {'cidr': CONF.dynamic_routing.
+ frr_bgp_ipv4_control_cidr,
+ 'peer_group': 'dnr'}
+ ],
+ 'limit': 10,
+ },
+ 'ebgp_requires_policy': False,
+ }
+ }
+ daemons = {
+ "bgpd": {
+ "enabled": "yes",
+ "listenon": bgp_listenon,
+ },
+ "vtysh": {"enabled": "yes"},
+ }
+ qg = frr.FrrBGPContainer(
+ 'q-4byte-asn-' + str(container_number),
+ CONF.dynamic_routing.frr_docker_image,
+ bgpd=bgpd,
+ daemons=daemons,
+ )
+ qg.run(wait=True)
+ cls.containers.append(qg.ctn)
+ cls.r_ass.append(qg)
+ cls.r_as_ip.append(
+ CONF.dynamic_routing.frr_provider_ipv4_ips[
+ cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+ )
+ cls.tnet_gen = cls.get_subnet(
+ start='10.10.1.0', end='10.10.255.0', step=256
+ )
+
+ @decorators.idempotent_id('9f18c931-a59e-4a27-939b-21124995ffe2')
+ def test_check_neighbor_established(self):
+ self._test_check_neighbor_established(self.ip_version)
+
+ @decorators.idempotent_id('73466aa5-7d1d-4f9f-8fb4-4100fad2dffe')
+ def test_check_advertised_tenant_network(self):
+ self._test_check_advertised_tenant_network(self.ip_version)
+
+ @decorators.idempotent_id('c3158328-2f69-4aa2-b1b7-5a06ab58afaf')
+ def test_check_advertised_multiple_tenant_network(self):
+ self._test_check_advertised_multiple_tenant_network(self.ip_version)
+
+ @decorators.idempotent_id('212a3d82-ac50-43dc-b657-030b1133643e')
+ def test_check_neighbor_established_with_multiple_peers(self):
+ self._test_check_neighbor_established_with_multiple_peers(
+ self.ip_version)
+
+ @decorators.idempotent_id('c72411c8-ea79-495d-bdbd-a10159642676')
+ def test_check_advertised_tenant_network_with_multiple_peers(self):
+ self._test_check_advertised_tenant_network_with_multiple_peers(
+ self.ip_version)
diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
index 9bd4c8d..d83fb7c 100644
--- a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
+++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py
@@ -17,12 +17,11 @@
from tempest import config
from tempest.lib import decorators
+from neutron_tempest_plugin.neutron_dynamic_routing import frr
from neutron_tempest_plugin.neutron_dynamic_routing.scenario import base
from neutron_tempest_plugin.neutron_dynamic_routing.scenario\
import base_test_proto as test_base
-from os_ken.tests.integrated.common import docker_base as ctn_base
-from os_ken.tests.integrated.common import quagga
CONF = config.CONF
@@ -30,9 +29,8 @@
class BgpSpeakerIpv6Test(test_base.BgpSpeakerProtoTestBase):
RAS_MAX = 3
+ IP_ALLOCATION_MULTIPLIER = 0
ip_version = 6
- public_gw = '2001:db8:a000::1'
- MyScope = base.Scope(name='my-scope')
PNet = base.Net(name='', net='2001:db8::', mask=64,
cidr='2001:db8::/64', router=None)
PPool = base.Pool(name='test-pool-ext', prefixlen=PNet.mask,
@@ -76,36 +74,53 @@
super(BgpSpeakerIpv6Test, self).setUp()
@classmethod
+ def skip_checks(cls):
+ super().skip_checks()
+ if not CONF.dynamic_routing.frr_bgp_ipv6_enabled:
+ raise cls.skipException("Dynamic routing IPv6 tests not enabled.")
+ if CONF.production:
+ raise cls.skipException("Skip on production environment.")
+
+ @classmethod
def resource_setup_container(cls):
- cls.brdc = ctn_base.Bridge(name='br-docker-ipv6',
- subnet='2001:db8:a000::/64',
- start_ip='2001:db8:a000::8000',
- end_ip='2001:db8:a000::fffe',
- self_ip=True,
- fixed_ip=cls.public_gw + '/64',
- br_type=base.BRIDGE_TYPE)
- cls.bridges.append(cls.brdc)
- # This is dummy container object for a dr service.
- # This keeps data which passes to a quagga container.
- cls.dr = ctn_base.BGPContainer(name='dr', asn=int(cls.L_AS.asn),
- router_id=cls.L_AS.router_id)
- cls.dr.set_addr_info(bridge='br-docker-ipv6', ipv6=cls.public_gw)
- # quagga container
- cls.dockerimg = ctn_base.DockerImage(baseimage=cls.baseimage)
- cls.q_img = cls.dockerimg.create_quagga(check_exist=True)
- cls.images.append(cls.q_img)
+ # frr container
for i in range(cls.RAS_MAX):
- qg = quagga.QuaggaBGPContainer(name='q' + str(i + 1),
- asn=int(cls.ras_l[i].asn),
- router_id=cls.ras_l[i].router_id,
- ctn_image_name=cls.q_img)
- cls.containers.append(qg)
- cls.r_ass.append(qg)
- qg.add_route(cls.ras_l[i].adv_net, route_info={'rf': 'ipv6'})
+ container_number = i + 1
+ bgpd = {
+ 'bgp': {
+ 'as_number': int(cls.ras_l[i].asn),
+ 'router_id': cls.ras_l[i].router_id,
+ 'neighbors': [
+ {
+ 'peer_group': True,
+ 'address': 'dnr',
+ 'as_number': cls.L_AS.asn,
+ 'passive': True,
+ }
+ ],
+ 'listen': {
+ 'ranges': [
+ {'cidr': CONF.dynamic_routing.
+ frr_bgp_ipv6_control_cidr,
+ 'peer_group': 'dnr'}
+ ],
+ 'limit': 10,
+ },
+ 'ebgp_requires_policy': False,
+ }
+ }
+ qg = frr.FrrBGPContainer(
+ "qv6" + str(container_number),
+ CONF.dynamic_routing.frr_docker_image,
+ bgpd=bgpd,
+ )
qg.run(wait=True)
- cls.r_as_ip.append(cls.brdc.addif(qg))
- qg.add_peer(cls.dr, bridge=cls.brdc.name, v6=True,
- peer_info={'passive': True})
+ cls.containers.append(qg.ctn)
+ cls.r_ass.append(qg)
+ cls.r_as_ip.append(
+ CONF.dynamic_routing.frr_provider_ips[
+ cls.IP_ALLOCATION_MULTIPLIER + container_number - 1]
+ )
cls.tnet_gen = cls.get_subnet(start='2001:db8:8000:1::',
end='2001:db8:8000:ffff::',
step=65536 * 65536 * 65536 * 65536)
diff --git a/neutron_tempest_plugin/scenario/test_dns_integration.py b/neutron_tempest_plugin/scenario/test_dns_integration.py
index d6bafef..e349a97 100644
--- a/neutron_tempest_plugin/scenario/test_dns_integration.py
+++ b/neutron_tempest_plugin/scenario/test_dns_integration.py
@@ -17,11 +17,9 @@
import testtools
-from oslo_log import log
from tempest.common import utils
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
-from tempest.lib.common.utils import test_utils
from tempest.lib import decorators
from tempest.lib import exceptions as lib_exc
@@ -32,8 +30,6 @@
CONF = config.CONF
-LOG = log.getLogger(__name__)
-
# Note(jh): Need to do a bit of juggling here in order to avoid failures
# when designate_tempest_plugin is not available
@@ -47,15 +43,36 @@
DNSMixin = object
+def rand_zone_name(name='', prefix='rand', suffix=None):
+ """Generate a random zone name
+
+ :param str name: The name that you want to include
+ :param prefix: the exact text to start the string. Defaults to "rand"
+ :param suffix: the exact text to end the string
+ :return: a random zone name e.g. example.org.
+ :rtype: string
+ """
+
+ if suffix is None:
+ suffix = '.{}.'.format(CONF.dns.tld_suffix)
+ name = data_utils.rand_name(name=name, prefix=prefix)
+ return name + suffix
+
+
class BaseDNSIntegrationTests(base.BaseTempestTestCase, DNSMixin):
credentials = ['primary', 'admin']
@classmethod
def setup_clients(cls):
super(BaseDNSIntegrationTests, cls).setup_clients()
- cls.zone_client = cls.os_tempest.dns_v2.ZonesClient()
- cls.recordset_client = cls.os_tempest.dns_v2.RecordsetClient()
- cls.query_client.build_timeout = 60
+ cls.dns_client = cls.os_tempest.dns_v2.ZonesClient()
+ cls.query_client.build_timeout = CONF.dns.build_timeout
+ if CONF.enforce_scope.designate:
+ cls.admin_tld_client = cls.os_system_admin.dns_v2.TldClient()
+ cls.rec_client = cls.os_system_admin.dns_v2.RecordsetClient()
+ else:
+ cls.admin_tld_client = cls.os_admin.dns_v2.TldClient()
+ cls.rec_client = cls.os_admin.dns_v2.RecordsetClient()
@classmethod
def skip_checks(cls):
@@ -65,18 +82,27 @@
raise cls.skipException("Designate support is required")
if not (dns_base and dns_waiters):
raise cls.skipException("Designate tempest plugin is missing")
+ # Neutron creates zones for reverse lookups and designate requires
+ # related TLD if there is any
+ if CONF.production:
+ if CONF.dns.existing_tlds and not set([CONF.dns.tld_suffix,
+ "arpa", "in-addr.arpa"]).issubset(CONF.dns.existing_tlds):
+ raise cls.skipException("Skip on production environment "
+ "because it doesn't match TLD configuration.")
@classmethod
@utils.requires_ext(extension="dns-integration", service="network")
def resource_setup(cls):
super(BaseDNSIntegrationTests, cls).resource_setup()
- cls.zone_name = dns_data_utils.rand_zone_name(
- name="basednsintegrationtests")
- cls.zone = cls.zone_client.create_zone(
- name=cls.zone_name, wait_until='ACTIVE')[1]
- cls.addClassResourceCleanup(
- cls.zone_client.delete_zone, cls.zone['id'],
- ignore_errors=lib_exc.NotFound)
+
+ zone_name = rand_zone_name(
+ name="dnsinttest", prefix='')
+ _, cls.zone = cls.dns_client.create_zone(name=zone_name)
+ cls.addClassResourceCleanup(cls.dns_client.delete_zone,
+ cls.zone['id'], ignore_errors=lib_exc.NotFound)
+ dns_waiters.wait_for_zone_status(
+ cls.dns_client, cls.zone['id'], 'ACTIVE')
+
cls.network = cls.create_network(dns_domain=cls.zone['name'])
cls.subnet = cls.create_subnet(cls.network)
cls.subnet_v6 = cls.create_subnet(cls.network, ip_version=6)
@@ -84,6 +110,10 @@
cls.create_router_interface(cls.router['id'], cls.subnet['id'])
cls.keypair = cls.create_keypair()
+ @classmethod
+ def resource_cleanup(cls):
+ super(BaseDNSIntegrationTests, cls).resource_cleanup()
+
def _create_floatingip_with_dns(self, dns_name):
return self.create_floatingip(client=self.os_primary.network_client,
dns_name=dns_name,
@@ -102,79 +132,12 @@
fip = self.create_floatingip(port=port)
return {'port': port, 'fip': fip, 'server': server}
- def _check_type_in_recordsets(self, zone_id, rec_type):
- types = [rec['type'] for rec in self.recordset_client.list_recordset(
- zone_id)[1]['recordsets']]
- if rec_type in types:
- return True
- return False
-
- def _wait_for_type_in_recordsets(self, zone_id, type):
- test_utils.call_until_true(
- func=self._check_type_in_recordsets, zone_id=zone_id,
- rec_type=type, duration=self.query_client.build_timeout,
- sleep_for=5)
-
- def _check_recordset_deleted(
- self, recordset_client, zone_id, recordset_id):
- return test_utils.call_and_ignore_notfound_exc(
- recordset_client.show_recordset, zone_id, recordset_id) is None
-
- def _verify_designate_recordset(
- self, address, found=True, record_type='A'):
- if found:
- self._wait_for_type_in_recordsets(self.zone['id'], record_type)
- recordsets = self.recordset_client.list_recordset(
- self.zone['id'])[1]['recordsets']
- relevant_type = [rec for rec in recordsets if
- rec['type'] == record_type]
- self.assertTrue(
- relevant_type,
- 'Failed no {} type recordset has been detected in the '
- 'Designate DNS DB'.format(record_type))
- rec_id = [rec['id'] for rec in relevant_type if address in
- str(rec['records'])][0]
- self.assertTrue(
- rec_id, 'Record of type:{} with IP:{} was not detected in '
- 'the Designate DNS DB'.format(record_type, address))
- dns_waiters.wait_for_recordset_status(
- self.recordset_client, self.zone['id'], rec_id, 'ACTIVE')
- else:
- rec_id = None
- recordsets = self.recordset_client.list_recordset(
- self.zone['id'])[1]['recordsets']
- relevant_type = [rec for rec in recordsets if
- rec['type'] == record_type]
- if relevant_type:
- rec_id = [rec['id'] for rec in relevant_type if
- address in str(rec['records'])][0]
- if rec_id:
- recordset_exists = test_utils.call_until_true(
- func=self._check_recordset_deleted,
- recordset_client=self.recordset_client,
- zone_id=self.zone['id'], recordset_id=rec_id,
- duration=self.query_client.build_timeout, sleep_for=5)
- self.assertTrue(
- recordset_exists,
- 'Failed, recordset type:{} and ID:{} is still exist in '
- 'the Designate DNS DB'.format(record_type, rec_id))
-
def _verify_dns_records(self, address, name, found=True, record_type='A'):
client = self.query_client
forward = name + '.' + self.zone['name']
reverse = ipaddress.ip_address(address).reverse_pointer
- record_types_to_check = [record_type, 'PTR']
- for rec_type in record_types_to_check:
- try:
- if rec_type == 'PTR':
- dns_waiters.wait_for_query(
- client, reverse, rec_type, found)
- else:
- dns_waiters.wait_for_query(
- client, forward, rec_type, found)
- except Exception as e:
- LOG.error(e)
- self._verify_designate_recordset(address, found, rec_type)
+ dns_waiters.wait_for_query(client, forward, record_type, found)
+ dns_waiters.wait_for_query(client, reverse, 'PTR', found)
if not found:
return
fwd_response = client.query(forward, record_type)
@@ -300,15 +263,19 @@
@classmethod
def resource_setup(cls):
super(BaseDNSIntegrationTests, cls).resource_setup()
- cls.name = data_utils.rand_name('test-domain')
- cls.zone_name = "%s.%s.%s.zone." % (cls.client.user_id,
- cls.client.project_id,
- cls.name)
- dns_domain_template = "<user_id>.<project_id>.%s.zone." % cls.name
- cls.zone = cls.zone_client.create_zone(
- name=cls.zone_name, wait_until='ACTIVE')[1]
- cls.addClassResourceCleanup(cls.zone_client.delete_zone,
+
+ name = data_utils.rand_name('test-domain')
+ zone_name = "%s.%s.%s.zone." % (cls.client.user_id,
+ cls.client.tenant_id,
+ name)
+ dns_domain_template = "<user_id>.<project_id>.%s.zone." % name
+
+ _, cls.zone = cls.dns_client.create_zone(name=zone_name)
+ cls.addClassResourceCleanup(cls.dns_client.delete_zone,
cls.zone['id'], ignore_errors=lib_exc.NotFound)
+ dns_waiters.wait_for_zone_status(
+ cls.dns_client, cls.zone['id'], 'ACTIVE')
+
cls.network = cls.create_network(dns_domain=dns_domain_template)
cls.subnet = cls.create_subnet(cls.network,
dns_publish_fixed_ip=True)
diff --git a/neutron_tempest_plugin/scenario/test_security_groups.py b/neutron_tempest_plugin/scenario/test_security_groups.py
index 21453a0..9ece6c8 100644
--- a/neutron_tempest_plugin/scenario/test_security_groups.py
+++ b/neutron_tempest_plugin/scenario/test_security_groups.py
@@ -555,6 +555,9 @@
# configure sec groups to support SSH connectivity
self.create_loginable_secgroup_rule(
secgroup_id=secgroups[-1]['id'])
+ # add the initial metadata access rule if VMs are not booted yet
+ self.create_ingress_metadata_secgroup_rule(
+ secgroup_id=secgroups[-1]['id'])
# Configure security groups, first two servers as remotes
for i, server in enumerate(servers):
diff --git a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
index 78b9323..942e7f0 100644
--- a/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
+++ b/neutron_tempest_plugin/vpnaas/scenario/test_vpnaas.py
@@ -116,10 +116,10 @@
cls.extra_subnet_attributes['ipv6_address_mode'] = 'slaac'
cls.extra_subnet_attributes['ipv6_ra_mode'] = 'slaac'
- left_v4_cidr = netaddr.IPNetwork('10.20.0.0/24')
+ left_v4_cidr = netaddr.IPNetwork('10.220.0.0/24')
left_v6_cidr = netaddr.IPNetwork('2001:db8:0:2::/64')
cls.left_cidr = left_v6_cidr if cls.inner_ipv6 else left_v4_cidr
- right_v4_cidr = netaddr.IPNetwork('10.10.0.0/24')
+ right_v4_cidr = netaddr.IPNetwork('10.210.0.0/24')
right_v6_cidr = netaddr.IPNetwork('2001:db8:0:1::/64')
cls.right_cidr = right_v6_cidr if cls.inner_ipv6 else right_v4_cidr
diff --git a/requirements.txt b/requirements.txt
index 957f186..7e99869 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -2,7 +2,6 @@
neutron-lib>=1.25.0 # Apache-2.0
oslo.config>=5.2.0 # Apache-2.0
netaddr>=0.7.18 # BSD
-os-ken>=0.3.0 # Apache-2.0
oslo.log>=3.36.0 # Apache-2.0
oslo.serialization>=2.20.0 # Apache-2.0
oslo.utils>=3.33.0 # Apache-2.0
@@ -14,3 +13,5 @@
testtools>=2.2.0 # MIT
eventlet>=0.21.0 # MIT
debtcollector>=1.2.0 # Apache-2.0
+docker # Apache-2.0
+Jinja2 # BSD License (3 clause)