From 2201953759bed9f336e127eb0f79202024b5c3dd Mon Sep 17 00:00:00 2001 From: Ryan Tidwell Date: Fri, 12 Apr 2019 10:23:47 -0500 Subject: [PATCH] Move neutron-dynamic-routing BGP tests from stadium This is step 1 of the movement of the tempest plugin for neutron-dynamic-routing. Co-Authored-By: Slawek Kaplonski Change-Id: I35984bb3ad3673b7a54982657c1ac6fdc3ed6de0 --- .zuul.yaml | 31 ++ .../neutron_dynamic_routing/__init__.py | 0 .../neutron_dynamic_routing/api/__init__.py | 0 .../api/test_bgp_speaker_extensions.py | 325 +++++++++++++++ .../test_bgp_speaker_extensions_negative.py | 126 ++++++ .../neutron_dynamic_routing/scenario/README | 36 ++ .../scenario/__init__.py | 0 .../neutron_dynamic_routing/scenario/base.py | 373 ++++++++++++++++++ .../scenario/base_test_proto.py | 146 +++++++ .../scenario/basic/__init__.py | 0 .../scenario/basic/base.py | 105 +++++ .../scenario/basic/test_4byte_asn.py | 132 +++++++ .../scenario/basic/test_basic.py | 115 ++++++ .../scenario/ipv4/__init__.py | 0 .../scenario/ipv4/test_ipv4.py | 132 +++++++ .../scenario/ipv6/__init__.py | 0 .../scenario/ipv6/test_ipv6.py | 133 +++++++ .../scenario/test_simple_bgp.py} | 7 +- .../services/bgp/__init__.py | 0 .../services/bgp/bgp_client.py | 143 +++++++ playbooks/dynamic-routing-pre-run.yaml | 3 + requirements.txt | 1 + .../docker-setup/files/52_docker_for_tempest | 1 + roles/docker-setup/files/docker_apparmor | 34 ++ roles/docker-setup/tasks/main.yml | 36 ++ 25 files changed, 1873 insertions(+), 6 deletions(-) create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/README create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/__init__.py create mode 100644 neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py rename neutron_tempest_plugin/{scenario/test_bgp.py => neutron_dynamic_routing/scenario/test_simple_bgp.py} (98%) create mode 100644 neutron_tempest_plugin/services/bgp/__init__.py create mode 100644 neutron_tempest_plugin/services/bgp/bgp_client.py create mode 100644 playbooks/dynamic-routing-pre-run.yaml create mode 100644 roles/docker-setup/files/52_docker_for_tempest create mode 100644 roles/docker-setup/files/docker_apparmor create mode 100644 roles/docker-setup/tasks/main.yml diff --git a/.zuul.yaml b/.zuul.yaml index 011bfe5d..70809b3c 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -894,6 +894,35 @@ files: - ^neutron_tempest_plugin/fwaas/.*$ +- job: + name: neutron-tempest-plugin-dynamic-routing + parent: neutron-tempest-plugin + description: | + Perform setup common to all Neutron dynamic routing tempest tests + required-projects: + - openstack/neutron + - openstack/neutron-dynamic-routing + - openstack/os-ken + - openstack/tempest + pre-run: playbooks/dynamic-routing-pre-run.yaml + vars: + devstack_plugins: + neutron-dynamic-routing: https://opendev.org/openstack/neutron-dynamic-routing + neutron-tempest-plugin: https://opendev.org/openstack/neutron-tempest-plugin + network_api_extensions_common: *api_extensions_master + network_api_extensions_bgp: + - bgp + - bgp_dragent_scheduler + - bgp_4byte_asn + devstack_localrc: + NETWORK_API_EXTENSIONS: "{{ (network_api_extensions_common + network_api_extensions_bgp) | join(',') }}" + devstack_services: + neutron-dr: true + neutron-dr-agent: true + q-l3: true + tempest_concurrency: 1 + tempest_test_regex: ^neutron_tempest_plugin\.neutron_dynamic_routing + - project-template: name: neutron-tempest-plugin-jobs check: @@ -967,7 +996,9 @@ - neutron-tempest-plugin-sfc - neutron-tempest-plugin-bgpvpn-bagpipe - neutron-tempest-plugin-fwaas + - neutron-tempest-plugin-dynamic-routing gate: jobs: - neutron-tempest-plugin-bgpvpn-bagpipe - neutron-tempest-plugin-fwaas + - neutron-tempest-plugin-dynamic-routing diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py new file mode 100644 index 00000000..f0e576bc --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions.py @@ -0,0 +1,325 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netaddr +from tempest.common import utils +from tempest import config +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from neutron_tempest_plugin.api import base +from neutron_tempest_plugin.common import tempest_fixtures as fixtures +from neutron_tempest_plugin.services.bgp import bgp_client + + +CONF = config.CONF + + +def _setup_client_args(auth_provider): + """Set up ServiceClient arguments using config settings. """ + service = CONF.network.catalog_type or 'network' + region = CONF.network.region or 'regionOne' + endpoint_type = CONF.network.endpoint_type + build_interval = CONF.network.build_interval + build_timeout = CONF.network.build_timeout + + # The disable_ssl appears in identity + disable_ssl_certificate_validation = ( + CONF.identity.disable_ssl_certificate_validation) + ca_certs = None + + # Trace in debug section + trace_requests = CONF.debug.trace_requests + + return [auth_provider, service, region, endpoint_type, + build_interval, build_timeout, + disable_ssl_certificate_validation, ca_certs, + trace_requests] + + +class BgpSpeakerTestJSONBase(base.BaseAdminNetworkTest): + + default_bgp_speaker_args = {'local_as': '1234', + 'ip_version': 4, + 'name': 'my-bgp-speaker', + 'advertise_floating_ip_host_routes': True, + 'advertise_tenant_networks': True} + default_bgp_peer_args = {'remote_as': '4321', + 'name': 'my-bgp-peer', + 'peer_ip': '192.168.1.1', + 'auth_type': 'md5', 'password': 'my-secret'} + + def setUp(self): + self.addCleanup(self.resource_cleanup) + super(BgpSpeakerTestJSONBase, self).setUp() + + @classmethod + def _setup_bgp_admin_client(cls): + mgr = cls.get_client_manager(credential_type='admin') + auth_provider = mgr.auth_provider + client_args = _setup_client_args(auth_provider) + cls.bgp_adm_client = bgp_client.BgpSpeakerClientJSON(*client_args) + + @classmethod + def _setup_bgp_non_admin_client(cls): + mgr = cls.get_client_manager() + auth_provider = mgr.auth_provider + client_args = _setup_client_args(auth_provider) + cls.bgp_client = bgp_client.BgpSpeakerClientJSON(*client_args) + + @classmethod + def resource_setup(cls): + super(BgpSpeakerTestJSONBase, cls).resource_setup() + if not utils.is_extension_enabled('bgp', 'network'): + msg = "BGP Speaker extension is not enabled." + raise cls.skipException(msg) + + cls.admin_routerports = [] + cls.admin_floatingips = [] + cls.admin_routers = [] + cls.ext_net_id = CONF.network.public_network_id + cls._setup_bgp_admin_client() + cls._setup_bgp_non_admin_client() + + @classmethod + def resource_cleanup(cls): + for floatingip in cls.admin_floatingips: + cls._try_delete_resource(cls.admin_client.delete_floatingip, + floatingip['id']) + for routerport in cls.admin_routerports: + cls._try_delete_resource( + cls.admin_client.remove_router_interface_with_subnet_id, + routerport['router_id'], routerport['subnet_id']) + for router in cls.admin_routers: + cls._try_delete_resource(cls.admin_client.delete_router, + router['id']) + super(BgpSpeakerTestJSONBase, cls).resource_cleanup() + + def create_bgp_speaker(self, auto_delete=True, **args): + data = {'bgp_speaker': args} + bgp_speaker = self.bgp_adm_client.create_bgp_speaker(data) + bgp_speaker_id = bgp_speaker['bgp_speaker']['id'] + if auto_delete: + self.addCleanup(self.delete_bgp_speaker, bgp_speaker_id) + return bgp_speaker['bgp_speaker'] + + def create_bgp_peer(self, **args): + bgp_peer = self.bgp_adm_client.create_bgp_peer({'bgp_peer': args}) + bgp_peer_id = bgp_peer['bgp_peer']['id'] + self.addCleanup(self.delete_bgp_peer, bgp_peer_id) + return bgp_peer['bgp_peer'] + + def update_bgp_speaker(self, id, **args): + data = {'bgp_speaker': args} + return self.bgp_adm_client.update_bgp_speaker(id, data) + + def delete_bgp_speaker(self, id): + return self.bgp_adm_client.delete_bgp_speaker(id) + + def get_bgp_speaker(self, id): + return self.bgp_adm_client.get_bgp_speaker(id)['bgp_speaker'] + + def create_bgp_speaker_and_peer(self): + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args) + return (bgp_speaker, bgp_peer) + + def delete_bgp_peer(self, id): + return self.bgp_adm_client.delete_bgp_peer(id) + + def add_bgp_peer(self, bgp_speaker_id, bgp_peer_id): + return self.bgp_adm_client.add_bgp_peer_with_id(bgp_speaker_id, + bgp_peer_id) + + def remove_bgp_peer(self, bgp_speaker_id, bgp_peer_id): + return self.bgp_adm_client.remove_bgp_peer_with_id(bgp_speaker_id, + bgp_peer_id) + + def delete_address_scope(self, id): + return self.admin_client.delete_address_scope(id) + + +class BgpSpeakerTestJSON(BgpSpeakerTestJSONBase): + + """Tests the following operations in the Neutron API using the REST client: + + Create bgp-speaker + Delete bgp-speaker + Create bgp-peer + Update bgp-peer + Delete bgp-peer + """ + + @decorators.idempotent_id('df259771-7104-4ffa-b77f-bd183600d7f9') + def test_delete_bgp_speaker(self): + bgp_speaker = self.create_bgp_speaker(auto_delete=False, + **self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.delete_bgp_speaker(bgp_speaker_id) + self.assertRaises(lib_exc.NotFound, + self.get_bgp_speaker, + bgp_speaker_id) + + @decorators.idempotent_id('81d9dc45-19f8-4c6e-88b8-401d965cd1b0') + def test_create_bgp_peer(self): + self.create_bgp_peer(**self.default_bgp_peer_args) + + @decorators.idempotent_id('6ade0319-1ee2-493c-ac4b-5eb230ff3a77') + def test_add_bgp_peer(self): + bgp_speaker, bgp_peer = self.create_bgp_speaker_and_peer() + bgp_speaker_id = bgp_speaker['id'] + bgp_peer_id = bgp_peer['id'] + + self.add_bgp_peer(bgp_speaker_id, bgp_peer_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + bgp_peers_list = bgp_speaker['peers'] + self.assertEqual(1, len(bgp_peers_list)) + self.assertTrue(bgp_peer_id in bgp_peers_list) + + @decorators.idempotent_id('f9737708-1d79-440b-8350-779f97d882ee') + def test_remove_bgp_peer(self): + bgp_peer = self.create_bgp_peer(**self.default_bgp_peer_args) + bgp_peer_id = bgp_peer['id'] + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.add_bgp_peer(bgp_speaker_id, bgp_peer_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + bgp_peers_list = bgp_speaker['peers'] + self.assertTrue(bgp_peer_id in bgp_peers_list) + + bgp_speaker = self.remove_bgp_peer(bgp_speaker_id, bgp_peer_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + bgp_peers_list = bgp_speaker['peers'] + self.assertTrue(not bgp_peers_list) + + @decorators.idempotent_id('23c8eb37-d10d-4f43-b2e7-6542cb6a4405') + def test_add_gateway_network(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + network_list = bgp_speaker['networks'] + self.assertEqual(1, len(network_list)) + self.assertTrue(self.ext_net_id in network_list) + + @decorators.idempotent_id('6cfc7137-0d99-4a3d-826c-9d1a3a1767b0') + def test_remove_gateway_network(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + networks = bgp_speaker['networks'] + + self.assertTrue(self.ext_net_id in networks) + self.bgp_adm_client.remove_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + bgp_speaker = self.get_bgp_speaker(bgp_speaker_id) + network_list = bgp_speaker['networks'] + self.assertTrue(not network_list) + + @decorators.idempotent_id('5bef22ad-5e70-4f7b-937a-dc1944642996') + def test_get_advertised_routes_null_address_scope(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(0, len(routes['advertised_routes'])) + + @decorators.idempotent_id('cae9cdb1-ad65-423c-9604-d4cd0073616e') + def test_get_advertised_routes_floating_ips(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + self.ext_net_id) + tenant_net = self.create_network() + tenant_subnet = self.create_subnet(tenant_net) + ext_gw_info = {'network_id': self.ext_net_id} + router = self.admin_client.create_router( + 'my-router', + external_gateway_info=ext_gw_info, + admin_state_up=True, + distributed=False) + self.admin_routers.append(router['router']) + self.admin_client.add_router_interface_with_subnet_id( + router['router']['id'], + tenant_subnet['id']) + self.admin_routerports.append({'router_id': router['router']['id'], + 'subnet_id': tenant_subnet['id']}) + tenant_port = self.create_port(tenant_net) + floatingip = self.create_floatingip(self.ext_net_id) + self.admin_floatingips.append(floatingip) + self.client.update_floatingip(floatingip['id'], + port_id=tenant_port['id']) + routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(floatingip['floating_ip_address'] + '/32', + routes['advertised_routes'][0]['destination']) + + @decorators.idempotent_id('c9ad566e-fe8f-4559-8303-bbad9062a30c') + def test_get_advertised_routes_tenant_networks(self): + self.useFixture(fixtures.LockFixture('gateway_network_binding')) + addr_scope = self.create_address_scope('my-scope', ip_version=4) + ext_net = self.create_shared_network(**{'router:external': True}) + tenant_net = self.create_network() + ext_subnetpool = self.create_subnetpool( + 'test-pool-ext', + is_admin=True, + default_prefixlen=24, + address_scope_id=addr_scope['id'], + prefixes=['8.0.0.0/8']) + tenant_subnetpool = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope['id'], + prefixes=['10.10.0.0/16']) + self.create_subnet({'id': ext_net['id']}, + cidr=netaddr.IPNetwork('8.0.0.0/24'), + ip_version=4, + client=self.admin_client, + subnetpool_id=ext_subnetpool['id']) + tenant_subnet = self.create_subnet( + {'id': tenant_net['id']}, + cidr=netaddr.IPNetwork('10.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool['id']) + ext_gw_info = {'network_id': ext_net['id']} + router = self.admin_client.create_router( + 'my-router', + external_gateway_info=ext_gw_info, + distributed=False)['router'] + self.admin_routers.append(router) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet['id']}) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + ext_net['id']) + routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(tenant_subnet['cidr'], + routes['advertised_routes'][0]['destination']) + fixed_ip = router['external_gateway_info']['external_fixed_ips'][0] + self.assertEqual(fixed_ip['ip_address'], + routes['advertised_routes'][0]['next_hop']) diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py new file mode 100644 index 00000000..9d145367 --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/api/test_bgp_speaker_extensions_negative.py @@ -0,0 +1,126 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company LP +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import netaddr + +from tempest.common import utils +from tempest.lib import decorators +from tempest.lib import exceptions as lib_exc + +from neutron_tempest_plugin.neutron_dynamic_routing.api import\ + test_bgp_speaker_extensions as test_base + + +class BgpSpeakerTestJSONNegative(test_base.BgpSpeakerTestJSONBase): + + """Negative test cases asserting proper behavior of BGP API extension""" + + @decorators.attr(type=['negative', 'smoke']) + @decorators.idempotent_id('75e9ee2f-6efd-4320-bff7-ae24741c8b06') + def test_create_bgp_speaker_illegal_local_asn(self): + wrong_asn = 65537 + if utils.is_extension_enabled('bgp_4byte_asn', 'network'): + wrong_asn = 4294967296 + self.assertRaises(lib_exc.BadRequest, + self.create_bgp_speaker, + local_as=wrong_asn) + + @decorators.attr(type=['negative', 'smoke']) + @decorators.idempotent_id('6742ec2e-382a-4453-8791-13a19b47cd13') + def test_create_bgp_speaker_non_admin(self): + self.assertRaises(lib_exc.Forbidden, + self.bgp_client.create_bgp_speaker, + {'bgp_speaker': self.default_bgp_speaker_args}) + + @decorators.attr(type=['negative', 'smoke']) + @decorators.idempotent_id('33f7aaf0-9786-478b-b2d1-a51086a50eb4') + def test_create_bgp_peer_non_admin(self): + self.assertRaises(lib_exc.Forbidden, + self.bgp_client.create_bgp_peer, + {'bgp_peer': self.default_bgp_peer_args}) + + @decorators.attr(type=['negative', 'smoke']) + @decorators.idempotent_id('39435932-0266-4358-899b-0e9b1e53c3e9') + def test_update_bgp_speaker_local_asn(self): + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + + self.assertRaises(lib_exc.BadRequest, self.update_bgp_speaker, + bgp_speaker_id, local_as='4321') + + @decorators.idempotent_id('9cc33701-51e5-421f-a5d5-fd7b330e550f') + def test_get_advertised_routes_tenant_networks(self): + addr_scope1 = self.create_address_scope('my-scope1', ip_version=4) + addr_scope2 = self.create_address_scope('my-scope2', ip_version=4) + ext_net = self.create_shared_network(**{'router:external': True}) + tenant_net1 = self.create_network() + tenant_net2 = self.create_network() + ext_subnetpool = self.create_subnetpool( + 'test-pool-ext', + is_admin=True, + default_prefixlen=24, + address_scope_id=addr_scope1['id'], + prefixes=['8.0.0.0/8']) + tenant_subnetpool1 = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope1['id'], + prefixes=['10.10.0.0/16']) + tenant_subnetpool2 = self.create_subnetpool( + 'tenant-test-pool', + default_prefixlen=25, + address_scope_id=addr_scope2['id'], + prefixes=['11.10.0.0/16']) + self.create_subnet({'id': ext_net['id']}, + cidr=netaddr.IPNetwork('8.0.0.0/24'), + ip_version=4, + client=self.admin_client, + subnetpool_id=ext_subnetpool['id']) + tenant_subnet1 = self.create_subnet( + {'id': tenant_net1['id']}, + cidr=netaddr.IPNetwork('10.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool1['id']) + tenant_subnet2 = self.create_subnet( + {'id': tenant_net2['id']}, + cidr=netaddr.IPNetwork('11.10.0.0/24'), + ip_version=4, + subnetpool_id=tenant_subnetpool2['id']) + ext_gw_info = {'network_id': ext_net['id']} + router = self.admin_client.create_router( + 'my-router', + distributed=False, + external_gateway_info=ext_gw_info)['router'] + self.admin_routers.append(router) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet1['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet1['id']}) + self.admin_client.add_router_interface_with_subnet_id( + router['id'], + tenant_subnet2['id']) + self.admin_routerports.append({'router_id': router['id'], + 'subnet_id': tenant_subnet2['id']}) + bgp_speaker = self.create_bgp_speaker(**self.default_bgp_speaker_args) + bgp_speaker_id = bgp_speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(bgp_speaker_id, + ext_net['id']) + routes = self.bgp_adm_client.get_bgp_advertised_routes(bgp_speaker_id) + self.assertEqual(1, len(routes['advertised_routes'])) + self.assertEqual(tenant_subnet1['cidr'], + routes['advertised_routes'][0]['destination']) + fixed_ip = router['external_gateway_info']['external_fixed_ips'][0] + self.assertEqual(fixed_ip['ip_address'], + routes['advertised_routes'][0]['next_hop']) diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README new file mode 100644 index 00000000..44990bdc --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/README @@ -0,0 +1,36 @@ +scenario tests use the following environment. + +diagram: + + ----------------------+--------------- tenant + | network + +--------+ + | router | + +--------+ + | + -----+----------------+--------------- provider + | | network + +---------+ | + | dragent | | + +---------+ | + | | + | +--------------+ + | | + +--------+ + | docker | + | bridge | + +--------+ + | + +-----------+------------+------- + | | + +---------+ +---------+ + docker | quagga1 | | quagga2 | ... + container +---------+ +---------+ + + +docker container environment is provided by test tool of os-ken. +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. diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py new file mode 100644 index 00000000..de8677fb --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base.py @@ -0,0 +1,373 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import collections +import threading +import time + +import netaddr +import six + +from os_ken.tests.integrated.common import docker_base as ctn_base +from tempest.common import utils +from tempest import config + +from neutron_tempest_plugin.api import base +from neutron_tempest_plugin.services.bgp import bgp_client + + +CONF = config.CONF + +Scope = collections.namedtuple('Scope', 'name') +Pool = collections.namedtuple('Pool', 'name, prefixlen, prefixes') +Net = collections.namedtuple('Net', 'name, net, mask, cidr, router') +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): + """Set up ServiceClient arguments using config settings. """ + service = CONF.network.catalog_type or 'network' + region = CONF.network.region or 'regionOne' + endpoint_type = CONF.network.endpoint_type + build_interval = CONF.network.build_interval + build_timeout = CONF.network.build_timeout + + # The disable_ssl appears in identity + disable_ssl_certificate_validation = ( + CONF.identity.disable_ssl_certificate_validation) + ca_certs = None + + # Trace in debug section + trace_requests = CONF.debug.trace_requests + + return [auth_provider, service, region, endpoint_type, + build_interval, build_timeout, + disable_ssl_certificate_validation, ca_certs, + trace_requests] + + +class BgpSpeakerScenarioTestJSONBase(base.BaseAdminNetworkTest): + + def setUp(self): + self.addCleanup(self.net_resource_cleanup) + super(BgpSpeakerScenarioTestJSONBase, self).setUp() + + @classmethod + def _setup_bgp_non_admin_client(cls): + mgr = cls.get_client_manager() + auth_provider = mgr.auth_provider + client_args = _setup_client_args(auth_provider) + cls.bgp_client = bgp_client.BgpSpeakerClientJSON(*client_args) + + @classmethod + def _setup_bgp_admin_client(cls): + mgr = cls.get_client_manager(credential_type='admin') + auth_provider = mgr.auth_provider + client_args = _setup_client_args(auth_provider) + cls.bgp_adm_client = bgp_client.BgpSpeakerClientJSON(*client_args) + + @classmethod + def resource_setup(cls): + super(BgpSpeakerScenarioTestJSONBase, cls).resource_setup() + if not utils.is_extension_enabled('bgp', 'network'): + msg = "BGP Speaker extension is not enabled." + raise cls.skipException(msg) + + cls.images = [] + cls.containers = [] + cls.r_ass = [] + cls.r_as_ip = [] + cls.bridges = [] + cls.admin_routerports = [] + cls.admin_floatingips = [] + cls.admin_routers = [] + cls.admin_router_ip = [] + cls.resource_setup_container() + cls._setup_bgp_admin_client() + cls._setup_bgp_non_admin_client() + cls.lock = threading.Lock() + + @classmethod + def resource_cleanup(cls): + for ctn in cls.containers: + try: + ctn.stop() + except ctn_base.CommandError: + pass + ctn.remove() + for br in cls.bridges: + br.delete() + super(BgpSpeakerScenarioTestJSONBase, cls).resource_cleanup() + + @classmethod + def get_subnet(self, start='10.10.1.0', end='10.10.255.0', step=256): + subnet_gen = netaddr.iter_iprange(start, end, step=step) + i = 1 + while True: + with self.lock: + try: + yield (i, str(six.next(subnet_gen))) + except StopIteration: + subnet_gen = netaddr.iter_iprange(start, end, step=step) + yield (i, str(six.next(subnet_gen))) + i += 1 + + def net_resource_cleanup(self): + for floatingip in self.admin_floatingips: + self._try_delete_resource(self.admin_client.delete_floatingip, + floatingip['id']) + for routerport in self.admin_routerports: + self._try_delete_resource( + self.admin_client.remove_router_interface_with_subnet_id, + routerport['router_id'], routerport['subnet_id']) + for router in self.admin_routers: + self._try_delete_resource(self.admin_client.delete_router, + router['id']) + + def create_bgp_speaker(self, auto_delete=True, **args): + data = {'bgp_speaker': args} + bgp_speaker = self.bgp_adm_client.create_bgp_speaker(data) + bgp_speaker_id = bgp_speaker['bgp_speaker']['id'] + if auto_delete: + self.addCleanup(self.bgp_adm_client.delete_bgp_speaker, + bgp_speaker_id) + return bgp_speaker['bgp_speaker'] + + def delete_bgp_speaker(self, id): + return self.bgp_adm_client.delete_bgp_speaker(id) + + def create_bgp_peer(self, auto_delete=True, **args): + bgp_peer = self.bgp_adm_client.create_bgp_peer({'bgp_peer': args}) + bgp_peer_id = bgp_peer['bgp_peer']['id'] + if auto_delete: + self.addCleanup(self.bgp_adm_client.delete_bgp_peer, + bgp_peer_id) + return bgp_peer['bgp_peer'] + + def delete_bgp_peer(self, id): + return self.bgp_adm_client.delete_bgp_peer(id) + + def get_dragent_id(self): + agents = self.admin_client.list_agents( + agent_type="BGP dynamic routing agent") + self.assertTrue(agents['agents'][0]['alive']) + return agents['agents'][0]['id'] + + def add_bgp_speaker_to_dragent(self, agent_id, speaker_id): + self.bgp_adm_client.add_bgp_speaker_to_dragent(agent_id, speaker_id) + + # tnets[[net1, subnet1, router1], [net2, subnet2, router2], ...] + def create_bgp_network(self, ip_version, scope, + exnet, expool, exsubnet, + tpool, tnets): + addr_scope = self.create_address_scope(scope.name, + ip_version=ip_version) + # external network + ext_net = self.create_shared_network(**{'router:external': True}) + ext_net_id = ext_net['id'] + ext_subnetpool = self.create_subnetpool( + expool.name, + is_admin=True, + default_prefixlen=expool.prefixlen, + address_scope_id=addr_scope['id'], + prefixes=expool.prefixes) + self.create_subnet( + {'id': ext_net_id}, + cidr=netaddr.IPNetwork(exsubnet.cidr), + mask_bits=exsubnet.mask, + ip_version=ip_version, + client=self.admin_client, + subnetpool_id=ext_subnetpool['id'], + reserve_cidr=False) + # tenant network + tenant_subnetpool = self.create_subnetpool( + tpool.name, + default_prefixlen=tpool.prefixlen, + address_scope_id=addr_scope['id'], + prefixes=tpool.prefixes) + for tnet, tsubnet, router in tnets: + tenant_net = self.create_network() + tenant_subnet = self.create_subnet( + {'id': tenant_net['id']}, + cidr=netaddr.IPNetwork(tsubnet.cidr), + mask_bits=tsubnet.mask, + ip_version=ip_version, + subnetpool_id=tenant_subnetpool['id'], + reserve_cidr=False) + # router + ext_gw_info = {'network_id': ext_net_id} + router_cr = self.admin_client.create_router( + router.name, + external_gateway_info=ext_gw_info)['router'] + self.admin_routers.append(router_cr) + self.admin_client.add_router_interface_with_subnet_id( + router_cr['id'], + tenant_subnet['id']) + self.admin_routerports.append({'router_id': router_cr['id'], + 'subnet_id': tenant_subnet['id']}) + router = self.admin_client.show_router(router_cr['id'])['router'] + fixed_ips = router['external_gateway_info']['external_fixed_ips'] + self.admin_router_ip.append(fixed_ips[0]['ip_address']) + return ext_net_id + + def create_and_add_peers_to_speaker(self, ext_net_id, + speaker_info, peer_infos, + auto_delete=True): + speaker = self.create_bgp_speaker(auto_delete=auto_delete, + **speaker_info) + speaker_id = speaker['id'] + self.bgp_adm_client.add_bgp_gateway_network(speaker_id, + ext_net_id) + peer_ids = [] + for peer_args in peer_infos: + peer = self.create_bgp_peer(auto_delete=auto_delete, + **peer_args) + peer_id = peer['id'] + peer_ids.append(peer_id) + self.bgp_adm_client.add_bgp_peer_with_id(speaker_id, + 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 = '' + for route in routes['advertised_routes']: + if route['destination'] == dest_addr: + next_hop = route['next_hop'] + break + return 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 new file mode 100644 index 00000000..1bcf5b17 --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/base_test_proto.py @@ -0,0 +1,146 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import six +from tempest import config + +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 + + +class BgpSpeakerProtoTestBase(base.BgpSpeakerScenarioTestJSONBase): + + def _test_check_neighbor_established(self, ip_version): + self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0] + num, subnet = six.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( + ip_version, 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) + + def _test_check_advertised_tenant_network(self, ip_version): + self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0] + num, subnet = six.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( + ip_version, 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) + 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)) + + def _test_check_advertised_multiple_tenant_network(self, ip_version): + self.bgp_peer_args[0]['peer_ip'] = self.r_as_ip[0].split('/')[0] + tnets = [] + tnets_cidr = [] + for i in range(0, 3): + num, subnet = six.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='') + tnets.append([TNet, TSubNet, MyRouter]) + tnets_cidr.append(TNet.cidr) + ext_net_id = self.create_bgp_network( + ip_version, self.MyScope, + self.PNet, self.PPool, self.PSubNet, + self.TPool, tnets) + 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) + 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)) + + def _test_check_neighbor_established_with_multiple_peers( + self, ip_version): + for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args, + self.r_as_ip): + bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0] + num, subnet = six.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( + ip_version, 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) + self.check_multi_remote_as_state(self.dr, self.r_ass, + ctn_base.BGP_FSM_ESTABLISHED) + + def _test_check_advertised_tenant_network_with_multiple_peers( + self, ip_version): + for (bgp_peer_args, r_as_ip) in zip(self.bgp_peer_args, + self.r_as_ip): + bgp_peer_args['peer_ip'] = r_as_ip.split('/')[0] + num, subnet = six.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( + ip_version, 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) + self.check_multi_remote_as_state(self.dr, self.r_ass, + ctn_base.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) diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py new file mode 100644 index 00000000..dd170e7f --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/base.py @@ -0,0 +1,105 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# 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() + 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 new file mode 100644 index 00000000..f492ede9 --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_4byte_asn.py @@ -0,0 +1,132 @@ +# 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() + 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 new file mode 100644 index 00000000..1c680f9b --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/basic/test_basic.py @@ -0,0 +1,115 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# 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 +import six +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 = six.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 = six.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 = six.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/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/__init__.py new file mode 100644 index 00000000..e69de29b 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 new file mode 100644 index 00000000..158b7ad3 --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv4/test_ipv4.py @@ -0,0 +1,132 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# 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 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 BgpSpeakerIpv4Test(test_base.BgpSpeakerProtoTestBase): + + RAS_MAX = 3 + 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) + 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': 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'} + ] + + def setUp(self): + super(BgpSpeakerIpv4Test, self).setUp() + + @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() + 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' + 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('7f2acbc2-ff88-4a63-aa02-a2f9feb3f5b0') + def test_check_neighbor_established(self): + self._test_check_neighbor_established(self.ip_version) + + @decorators.idempotent_id('f32245fc-aeab-4244-acfa-3af9dd662e8d') + def test_check_advertised_tenant_network(self): + self._test_check_advertised_tenant_network(self.ip_version) + + @decorators.idempotent_id('a5c238de-b750-499c-aaa2-b44a057e9ed3') + def test_check_advertised_multiple_tenant_network(self): + self._test_check_advertised_multiple_tenant_network(self.ip_version) + + @decorators.idempotent_id('e4961cc1-0c47-4081-a896-caaa9342ca75') + def test_check_neighbor_established_with_multiple_peers(self): + self._test_check_neighbor_established_with_multiple_peers( + self.ip_version) + + @decorators.idempotent_id('91971dfb-c129-4744-9fbb-ac4f9e8d56c0') + 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/__init__.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/__init__.py new file mode 100644 index 00000000..e69de29b 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 new file mode 100644 index 00000000..937b38d8 --- /dev/null +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/ipv6/test_ipv6.py @@ -0,0 +1,133 @@ +# Copyright (C) 2016 VA Linux Systems Japan K.K. +# Copyright (C) 2016 Fumihiko Kakuma +# 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 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 BgpSpeakerIpv6Test(test_base.BgpSpeakerProtoTestBase): + + RAS_MAX = 3 + 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, + prefixes=[PNet.net + '/8']) + PSubNet = base.SubNet(name='', cidr=PNet.cidr, mask=PNet.mask) + TPool = base.Pool(name='tenant-test-pool', prefixlen=64, + prefixes=['2001:db8:8000::/48']) + 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='2001:db8:9002::/48'), + base.AS(asn='64523', router_id='192.168.0.13', + adv_net='2001:db8:9003::/48'), + base.AS(asn='64524', router_id='192.168.0.14', + adv_net='2001:db8:9004::/48') + ] + + 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'} + ] + + def setUp(self): + super(BgpSpeakerIpv6Test, self).setUp() + + @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() + 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' + 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'}) + 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.tnet_gen = cls.get_subnet(start='2001:db8:8000:1::', + end='2001:db8:8000:ffff::', + step=65536 * 65536 * 65536 * 65536) + + @decorators.idempotent_id('5194a8e2-95bd-49f0-872d-1e3e875ede32') + def test_check_neighbor_established(self): + self._test_check_neighbor_established(self.ip_version) + + @decorators.idempotent_id('6a3483fc-8c8a-4387-bda6-c7061410e04b') + def test_check_advertised_tenant_network(self): + self._test_check_advertised_tenant_network(self.ip_version) + + @decorators.idempotent_id('aca5d678-c249-4de5-921b-6b6ba621e4f7') + def test_check_advertised_multiple_tenant_network(self): + self._test_check_advertised_multiple_tenant_network(self.ip_version) + + @decorators.idempotent_id('f81012f3-2f7e-4b3c-8c1d-b1778146d712') + def test_check_neighbor_established_with_multiple_peers(self): + self._test_check_neighbor_established_with_multiple_peers( + self.ip_version) + + @decorators.idempotent_id('be710ec1-a338-44c9-8b89-31c3532aae65') + 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/scenario/test_bgp.py b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py similarity index 98% rename from neutron_tempest_plugin/scenario/test_bgp.py rename to neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py index 62f433b2..85cc8103 100644 --- a/neutron_tempest_plugin/scenario/test_bgp.py +++ b/neutron_tempest_plugin/neutron_dynamic_routing/scenario/test_simple_bgp.py @@ -24,12 +24,7 @@ from tempest.lib import decorators from neutron_tempest_plugin import config from neutron_tempest_plugin.scenario import base from neutron_tempest_plugin.scenario import constants - -try: - # TODO(yamamoto): Remove this hack after bgp tests are rehomed - from neutron_dynamic_routing.tests.tempest import bgp_client -except ImportError: - bgp_client = None +from neutron_tempest_plugin.services.bgp import bgp_client CONF = config.CONF diff --git a/neutron_tempest_plugin/services/bgp/__init__.py b/neutron_tempest_plugin/services/bgp/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/neutron_tempest_plugin/services/bgp/bgp_client.py b/neutron_tempest_plugin/services/bgp/bgp_client.py new file mode 100644 index 00000000..ae514275 --- /dev/null +++ b/neutron_tempest_plugin/services/bgp/bgp_client.py @@ -0,0 +1,143 @@ +# Copyright 2016 Hewlett Packard Enterprise Development Company LP +# +# 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 oslo_serialization import jsonutils +from tempest.lib.common import rest_client + + +class BgpSpeakerClientJSON(rest_client.RestClient): + + def create_bgp_speaker(self, post_data): + post_body = jsonutils.dumps(post_data) + resp, body = self.post('v2.0/bgp-speakers', post_body) + body = jsonutils.loads(body) + self.expected_success(201, resp.status) + return rest_client.ResponseBody(resp, body) + + def get_bgp_speaker(self, id): + uri = 'v2.0/bgp-speakers/{0}'.format(id) + resp, body = self.get(uri) + body = jsonutils.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def get_bgp_speakers(self): + uri = self.get_uri("bgp-speakers") + resp, body = self.get(uri) + body = jsonutils.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBodyList(resp, body) + + def update_bgp_speaker(self, id, put_data): + uri = 'v2.0/bgp-speakers/{0}'.format(id) + update_body = {'bgp_speaker': put_data} + update_body = jsonutils.dumps(update_body) + resp, body = self.put(uri, update_body) + body = jsonutils.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def delete_bgp_speaker(self, id): + uri = 'v2.0/bgp-speakers/{0}'.format(id) + resp, body = self.delete(uri) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) + + def create_bgp_peer(self, post_data): + post_body = jsonutils.dumps(post_data) + resp, body = self.post('v2.0/bgp-peers', post_body) + body = jsonutils.loads(body) + self.expected_success(201, resp.status) + return rest_client.ResponseBody(resp, body) + + def get_bgp_peer(self, id): + uri = 'v2.0/bgp-peers/{0}'.format(id) + resp, body = self.get(uri) + body = jsonutils.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def delete_bgp_peer(self, id): + uri = 'v2.0/bgp-peers/{0}'.format(id) + resp, body = self.delete(uri) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) + + def add_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id): + uri = 'v2.0/bgp-speakers/%s/add_bgp_peer' % bgp_speaker_id + update_body = {"bgp_peer_id": bgp_peer_id} + update_body = jsonutils.dumps(update_body) + resp, body = self.put(uri, update_body) + self.expected_success(200, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def remove_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id): + uri = 'v2.0/bgp-speakers/%s/remove_bgp_peer' % bgp_speaker_id + update_body = {"bgp_peer_id": bgp_peer_id} + update_body = jsonutils.dumps(update_body) + resp, body = self.put(uri, update_body) + self.expected_success(200, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def add_bgp_gateway_network(self, bgp_speaker_id, network_id): + uri = 'v2.0/bgp-speakers/%s/add_gateway_network' % bgp_speaker_id + update_body = {"network_id": network_id} + update_body = jsonutils.dumps(update_body) + resp, body = self.put(uri, update_body) + self.expected_success(200, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def remove_bgp_gateway_network(self, bgp_speaker_id, network_id): + uri = 'v2.0/bgp-speakers/%s/remove_gateway_network' % bgp_speaker_id + update_body = {"network_id": network_id} + update_body = jsonutils.dumps(update_body) + resp, body = self.put(uri, update_body) + self.expected_success(200, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def get_bgp_advertised_routes(self, bgp_speaker_id): + base_uri = 'v2.0/bgp-speakers/%s/get_advertised_routes' + uri = base_uri % bgp_speaker_id + resp, body = self.get(uri) + body = jsonutils.loads(body) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, body) + + def list_dragents_for_bgp_speaker(self, bgp_speaker_id): + uri = 'v2.0/bgp-speakers/%s/bgp-dragents' % bgp_speaker_id + resp, body = self.get(uri) + self.expected_success(200, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def add_bgp_speaker_to_dragent(self, agent_id, bgp_speaker_id, + ignore_errors=False): + uri = 'v2.0/agents/%s/bgp-drinstances' % agent_id + update_body = {"bgp_speaker_id": bgp_speaker_id} + update_body = jsonutils.dumps(update_body) + resp, body = self.post(uri, update_body) + if not ignore_errors: + self.expected_success(201, resp.status) + body = jsonutils.loads(body) + return rest_client.ResponseBody(resp, body) + + def remove_bgp_speaker_from_dragent(self, agent_id, bgp_speaker_id): + uri = 'v2.0/agents/%s/bgp-drinstances/%s' % (agent_id, bgp_speaker_id) + resp, body = self.delete(uri) + self.expected_success(204, resp.status) + return rest_client.ResponseBody(resp, body) diff --git a/playbooks/dynamic-routing-pre-run.yaml b/playbooks/dynamic-routing-pre-run.yaml new file mode 100644 index 00000000..925223eb --- /dev/null +++ b/playbooks/dynamic-routing-pre-run.yaml @@ -0,0 +1,3 @@ +- hosts: all + roles: + - docker-setup diff --git a/requirements.txt b/requirements.txt index 2febb7e5..9a5e99f5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,6 +7,7 @@ neutron-lib>=1.25.0 # Apache-2.0 oslo.config>=5.2.0 # Apache-2.0 ipaddress>=1.0.17;python_version<'3.3' # PSF netaddr>=0.7.18 # BSD +os-ken>=0.3.0 # Apache-2.0 oslo.log>=3.36.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 diff --git a/roles/docker-setup/files/52_docker_for_tempest b/roles/docker-setup/files/52_docker_for_tempest new file mode 100644 index 00000000..a61c897e --- /dev/null +++ b/roles/docker-setup/files/52_docker_for_tempest @@ -0,0 +1 @@ +tempest ALL=(ALL) NOPASSWD: ALL diff --git a/roles/docker-setup/files/docker_apparmor b/roles/docker-setup/files/docker_apparmor new file mode 100644 index 00000000..198eeb71 --- /dev/null +++ b/roles/docker-setup/files/docker_apparmor @@ -0,0 +1,34 @@ +#include + +profile docker-default flags=(attach_disconnected,mediate_deleted) { + + #include + + network, + capability, + file, + umount, + + deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir) + # deny write to files not in /proc//** or /proc/sys/** + deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w, + deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel) + deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/ + deny @{PROC}/sysrq-trigger rwklx, + deny @{PROC}/mem rwklx, + deny @{PROC}/kmem rwklx, + deny @{PROC}/kcore rwklx, + + deny mount, + + deny /sys/[^f]*/** wklx, + deny /sys/f[^s]*/** wklx, + deny /sys/fs/[^c]*/** wklx, + deny /sys/fs/c[^g]*/** wklx, + deny /sys/fs/cg[^r]*/** wklx, + deny /sys/firmware/efi/efivars/** rwklx, + deny /sys/kernel/security/** rwklx, + + # suppress ptrace denials when using 'docker ps' or using 'ps' inside a container + ptrace (trace,read) peer=docker-default, +} diff --git a/roles/docker-setup/tasks/main.yml b/roles/docker-setup/tasks/main.yml new file mode 100644 index 00000000..19d8bfb3 --- /dev/null +++ b/roles/docker-setup/tasks/main.yml @@ -0,0 +1,36 @@ +- name: Install and configure docker + become: yes + package: + name: docker.io + state: present + +- name: Copy 52_docker_for_tempest to /etc/sudoers.d + copy: + src: 52_docker_for_tempest + dest: /etc/sudoers.d + owner: root + group: root + mode: 0440 + become: yes + +- name: Copy docker_apparmor to /etc/apparmor.d + copy: + src: docker_apparmor + dest: /etc/apparmor.d + owner: root + group: root + mode: 0640 + become: yes + +- name: Ensure apparmor is restarted + become: yes + service: + name: apparmor + state: restarted + ignore_errors: yes + +- name: Ensure docker engine is restarted + become: yes + service: + name: docker + state: restarted