From 5214b27c080208ff4fc6b47c997f8aa6a28a6d44 Mon Sep 17 00:00:00 2001 From: YAMAMOTO Takashi Date: Tue, 16 Jan 2018 13:58:41 +0900 Subject: [PATCH] Move test cases from networking-midonet repository Rehome tests for the following extensions: bgp-speaker-router-insertion fip64 router-interface-fip Closes-Bug: #1743497 Change-Id: I04fe57630d902f9aae3bb3405619d89c837a8564 --- .../api/test_router_interface_fip.py | 120 +++++++++ neutron_tempest_plugin/scenario/test_bgp.py | 232 ++++++++++++++++++ neutron_tempest_plugin/scenario/test_fip64.py | 92 +++++++ 3 files changed, 444 insertions(+) create mode 100644 neutron_tempest_plugin/api/test_router_interface_fip.py create mode 100644 neutron_tempest_plugin/scenario/test_bgp.py create mode 100644 neutron_tempest_plugin/scenario/test_fip64.py diff --git a/neutron_tempest_plugin/api/test_router_interface_fip.py b/neutron_tempest_plugin/api/test_router_interface_fip.py new file mode 100644 index 00000000..43698389 --- /dev/null +++ b/neutron_tempest_plugin/api/test_router_interface_fip.py @@ -0,0 +1,120 @@ +# Copyright (c) 2016 Midokura SARL +# 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 netaddr +import testtools + +from tempest.common import utils +from tempest.lib.common.utils import data_utils +from tempest.lib import decorators +import tempest.lib.exceptions as lib_exc + +from neutron_tempest_plugin.api import base + + +class ExpectedException(testtools.ExpectedException): + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, tb): + if super(ExpectedException, self).__exit__(exc_type, exc_value, tb): + self.exception = exc_value + return True + return False + + +class RouterInterfaceFip(base.BaseAdminNetworkTest): + @classmethod + @utils.requires_ext(extension="router-interface-fip", service="network") + def resource_setup(cls): + super(RouterInterfaceFip, cls).resource_setup() + + @decorators.idempotent_id('943ab44d-0ea7-4c6a-bdfd-8ba759622992') + def test_router_interface_fip(self): + # +-------------+ + # | router1 | + # +-+--------+--+ + # | | + # +-+--+ +-+--------+ + # |net1| |net2 | + # | | |(external)| + # +-+--+ +--+-------+ + # | | + # port1 fip2 + cidr1 = netaddr.IPNetwork('192.2.1.0/24') + cidr2 = netaddr.IPNetwork('192.2.2.0/24') + router1_name = data_utils.rand_name('router1') + router1 = self.create_router(router1_name) + net1 = self.create_network() + subnet1 = self.create_subnet(net1, cidr=cidr1) + self.create_router_interface(router1['id'], subnet1['id']) + net2 = self.admin_client.create_network( + project_id=self.client.tenant_id, + **{'router:external': True})['network'] + self.networks.append(net2) + subnet2 = self.create_subnet(net2, cidr=cidr2) + self.create_router_interface(router1['id'], subnet2['id']) + port1 = self.create_port(net1) + fip2 = self.create_floatingip(net2['id']) + fip2_updated = self.client.update_floatingip( + fip2['id'], port_id=port1['id'])['floatingip'] + expected = { + 'floating_network_id': net2['id'], + 'port_id': port1['id'], + 'router_id': router1['id'], + } + for k, v in expected.items(): + self.assertIn(k, fip2_updated) + self.assertEqual(v, fip2_updated[k]) + if 'revision_number' in fip2: + self.assertGreater(fip2_updated['revision_number'], + fip2['revision_number']) + # NOTE(yamamoto): The status can be updated asynchronously. + fip2_shown = self.client.show_floatingip(fip2['id'])['floatingip'] + if 'revision_number' in fip2: + self.assertGreaterEqual(fip2_shown['revision_number'], + fip2_updated['revision_number']) + fip2_shown.pop('status') + fip2_shown.pop('updated_at') + fip2_shown.pop('revision_number') + fip2_updated.pop('status') + fip2_updated.pop('updated_at') + fip2_updated.pop('revision_number') + self.assertEqual(fip2_updated, fip2_shown) + with ExpectedException(lib_exc.Conflict) as ctx: + self.client.remove_router_interface_with_subnet_id( + router1['id'], subnet2['id']) + self.assertEqual('RouterInterfaceInUseAsGatewayByFloatingIP', + ctx.exception.resp_body['type']) + with ExpectedException(lib_exc.Conflict) as ctx: + self.client.remove_router_interface_with_subnet_id( + router1['id'], subnet1['id']) + self.assertEqual('RouterInterfaceInUseByFloatingIP', + ctx.exception.resp_body['type']) + fip2_updated2 = self.client.update_floatingip( + fip2['id'], port_id=None)['floatingip'] + expected = { + 'floating_network_id': net2['id'], + 'floating_ip_address': fip2_shown['floating_ip_address'], + 'port_id': None, + 'router_id': None, + } + for k, v in expected.items(): + self.assertIn(k, fip2_updated2) + self.assertEqual(v, fip2_updated2[k]) + self.client.remove_router_interface_with_subnet_id( + router1['id'], subnet2['id']) + self.client.remove_router_interface_with_subnet_id( + router1['id'], subnet1['id']) diff --git a/neutron_tempest_plugin/scenario/test_bgp.py b/neutron_tempest_plugin/scenario/test_bgp.py new file mode 100644 index 00000000..62f433b2 --- /dev/null +++ b/neutron_tempest_plugin/scenario/test_bgp.py @@ -0,0 +1,232 @@ +# Copyright (c) 2017 Midokura SARL +# 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 netaddr + +from tempest.common import utils +from tempest.common import waiters +from tempest.lib.common import ssh +from tempest.lib.common.utils import data_utils +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 + + +CONF = config.CONF + + +class BgpClientMixin(object): + @classmethod + def resource_setup(cls): + super(BgpClientMixin, cls).resource_setup() + if bgp_client is None: + msg = "No BGP service client is available" + raise cls.skipException(msg) + manager = cls.os_admin + cls.bgp_client = bgp_client.BgpSpeakerClientJSON( + manager.auth_provider, + CONF.network.catalog_type, + CONF.network.region or CONF.identity.region, + endpoint_type=CONF.network.endpoint_type, + build_interval=CONF.network.build_interval, + build_timeout=CONF.network.build_timeout, + **manager.default_params) + + def create_bgp_speaker(self, **kwargs): + bgp_speaker = self.bgp_client.create_bgp_speaker(post_data={ + 'bgp_speaker': kwargs, + })['bgp_speaker'] + self.addCleanup(self.bgp_client.delete_bgp_speaker, bgp_speaker['id']) + return bgp_speaker + + def create_bgp_peer(self, **kwargs): + bgp_peer = self.bgp_client.create_bgp_peer(post_data={ + 'bgp_peer': kwargs, + })['bgp_peer'] + self.addCleanup(self.bgp_client.delete_bgp_peer, bgp_peer['id']) + return bgp_peer + + def add_bgp_peer_with_id(self, bgp_speaker_id, bgp_peer_id): + self.bgp_client.add_bgp_peer_with_id(bgp_speaker_id, bgp_peer_id) + + +class Bgp(BgpClientMixin, base.BaseTempestTestCase): + """Test the following topology + + +-------------------+ + | public | + | network | + | | + +-+---------------+-+ + | | + | | + +-------+-+ +-+-------+ + | LEFT | | RIGHT | + | router | <--BGP--> | router | + | | | | + +----+----+ +----+----+ + | | + +----+----+ +----+----+ + | LEFT | | RIGHT | + | network | | network | + | | | | + +---------+ +---------+ + """ + + credentials = ['primary', 'admin'] + + @classmethod + @utils.requires_ext(extension="bgp-speaker-router-insertion", + service="network") + def resource_setup(cls): + super(Bgp, cls).resource_setup() + + # common + cls.keypair = cls.create_keypair() + cls.secgroup = cls.os_primary.network_client.create_security_group( + name=data_utils.rand_name('secgroup-'))['security_group'] + cls.security_groups.append(cls.secgroup) + cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id']) + cls.create_pingable_secgroup_rule(secgroup_id=cls.secgroup['id']) + + # LEFT + cls.router = cls.create_router( + data_utils.rand_name('left-router'), + admin_state_up=True, + external_network_id=CONF.network.public_network_id) + cls.network = cls.create_network(network_name='left-network') + cls.subnet = cls.create_subnet(cls.network, + name='left-subnet') + cls.create_router_interface(cls.router['id'], cls.subnet['id']) + + # RIGHT + cls._right_network, cls._right_subnet, cls._right_router = \ + cls._create_right_network() + + @classmethod + def _create_right_network(cls): + # NOTE(yamamoto): Disable SNAT to workaround a bug + # https://midonet.atlassian.net/browse/MNA-1114 + router = cls.create_admin_router( + data_utils.rand_name('right-router'), + admin_state_up=True, + external_network_id=CONF.network.public_network_id, + enable_snat=False, + project_id=cls.os_primary.network_client.tenant_id) + network = cls.create_network(network_name='right-network') + subnet = cls.create_subnet( + network, + cidr=netaddr.IPNetwork('10.10.0.0/24'), + name='right-subnet') + cls.create_router_interface(router['id'], subnet['id']) + return network, subnet, router + + def _create_server(self, create_floating_ip=True, network=None): + if network is None: + network = self.network + port = self.create_port(network, security_groups=[self.secgroup['id']]) + if create_floating_ip: + fip = self.create_and_associate_floatingip(port['id']) + else: + fip = None + server = self.create_server( + flavor_ref=CONF.compute.flavor_ref, + image_ref=CONF.compute.image_ref, + key_name=self.keypair['name'], + networks=[{'port': port['id']}])['server'] + waiters.wait_for_server_status(self.os_primary.servers_client, + server['id'], + constants.SERVER_STATUS_ACTIVE) + return {'port': port, 'fip': fip, 'server': server} + + def _find_ipv4_subnet(self, network_id): + subnets = self.os_admin.network_client.list_subnets( + network_id=network_id)['subnets'] + for subnet in subnets: + if subnet['ip_version'] == 4: + return subnet['id'] + msg = "No suitable subnets on public network" + raise self.skipException(msg) + + def _get_external_ip(self, router, subnet_id): + for ip in router['external_gateway_info']['external_fixed_ips']: + if ip['subnet_id'] == subnet_id: + return ip['ip_address'] + return None + + def _setup_bgp(self): + network_id = CONF.network.public_network_id + subnet_id = self._find_ipv4_subnet(network_id) + sites = [ + dict(name="left", network=self.network, subnet=self.subnet, + router=self.router, local_as=64512), + dict(name="right", network=self._right_network, + subnet=self._right_subnet, router=self._right_router, + local_as=64513), + ] + psk = data_utils.rand_name('mysecret') + for i in range(0, 2): + site = sites[i] + router = site['router'] + site['bgp_speaker'] = self.create_bgp_speaker( + name=data_utils.rand_name('%s-bgp-speaker' % site['name']), + local_as=site['local_as'], + ip_version=4, + logical_router=router['id']) + site['external_v4_ip'] = self._get_external_ip(router, subnet_id) + for i in range(0, 2): + site = sites[i] + bgp_speaker_id = site['bgp_speaker']['id'] + peer = sites[1 - i] + peer_ip = peer['external_v4_ip'] + peer_as = peer['local_as'] + bgp_peer = self.create_bgp_peer( + name=data_utils.rand_name('%s-bgp-peer' % site['name']), + peer_ip=peer_ip, + remote_as=peer_as, + auth_type='md5', + password=psk) + self.add_bgp_peer_with_id(bgp_speaker_id, bgp_peer['id']) + + @decorators.idempotent_id('c1208ce2-c55f-4424-9035-25de83161d6f') + def test_bgp(self): + # RIGHT + right_server = self._create_server( + network=self._right_network, + create_floating_ip=False) + + # LEFT + left_server = self._create_server() + ssh_client = ssh.Client(left_server['fip']['floating_ip_address'], + CONF.validation.image_ssh_user, + pkey=self.keypair['private_key']) + + # check LEFT -> RIGHT connectivity via BGP advertised routes + self.check_remote_connectivity( + ssh_client, + right_server['port']['fixed_ips'][0]['ip_address'], + should_succeed=False) + self._setup_bgp() + self.check_remote_connectivity( + ssh_client, + right_server['port']['fixed_ips'][0]['ip_address']) diff --git a/neutron_tempest_plugin/scenario/test_fip64.py b/neutron_tempest_plugin/scenario/test_fip64.py new file mode 100644 index 00000000..3e218156 --- /dev/null +++ b/neutron_tempest_plugin/scenario/test_fip64.py @@ -0,0 +1,92 @@ +# Copyright (c) 2016 Midokura SARL +# 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 netaddr + +from tempest.common import utils +from tempest.common import waiters +from tempest.lib.common.utils import data_utils +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 + + +CONF = config.CONF + + +class Fip64(base.BaseTempestTestCase): + credentials = ['primary', 'admin'] + + _fip_ip_version = 6 + + @classmethod + @utils.requires_ext(extension="fip64", service="network") + def resource_setup(cls): + super(Fip64, cls).resource_setup() + cls.network = cls.create_network() + cls.subnet = cls.create_subnet(cls.network) + router = cls.create_router_by_client() + cls.create_router_interface(router['id'], cls.subnet['id']) + cls.keypair = cls.create_keypair() + + cls.secgroup = cls.os_primary.network_client.create_security_group( + name=data_utils.rand_name('secgroup-'))['security_group'] + cls.security_groups.append(cls.secgroup) + cls.create_loginable_secgroup_rule(secgroup_id=cls.secgroup['id']) + + def _find_ipv6_subnet(self, network_id): + subnets = self.os_admin.network_client.list_subnets( + network_id=network_id)['subnets'] + for subnet in subnets: + if subnet['ip_version'] == self._fip_ip_version: + return subnet['id'] + msg = "No suitable subnets on public network" + raise self.skipException(msg) + + def _create_and_associate_floatingip64(self, port_id): + network_id = CONF.network.public_network_id + subnet_id = self._find_ipv6_subnet(network_id) + fip = self.os_primary.network_client.create_floatingip( + floating_network_id=network_id, + subnet_id=subnet_id, + port_id=port_id)['floatingip'] + self.floating_ips.append(fip) + self.assertEqual( + self._fip_ip_version, + netaddr.IPAddress(fip['floating_ip_address']).version) + return fip + + def _create_server_with_fip64(self): + port = self.create_port(self.network, security_groups=[ + self.secgroup['id']]) + fip = self._create_and_associate_floatingip64(port['id']) + server = self.create_server( + flavor_ref=CONF.compute.flavor_ref, + image_ref=CONF.compute.image_ref, + key_name=self.keypair['name'], + networks=[{'port': port['id']}])['server'] + waiters.wait_for_server_status(self.os_primary.servers_client, + server['id'], + constants.SERVER_STATUS_ACTIVE) + return {'port': port, 'fip': fip, 'server': server} + + @decorators.idempotent_id('63f7da91-c7dd-449b-b50b-1c56853ce0ef') + def test_fip64(self): + server = self._create_server_with_fip64() + self.check_connectivity(server['fip']['floating_ip_address'], + CONF.validation.image_ssh_user, + self.keypair['private_key'])