From e5699cf86b2660394199fb8ed38ec629d15a4a78 Mon Sep 17 00:00:00 2001 From: Flavio Fernandes Date: Mon, 27 Jul 2020 06:38:38 -0400 Subject: [PATCH] Add integration tests with port forwarding Floating IP port forwarding by ml2/ovn uses OVN load balancers. Integration tests with ovn_octavia_provider here are done to ensure that these two functionalities can coexist. Depends-On: https://review.opendev.org/#/c/741303/ Change-Id: I4b5ded003cbb3c9c4c0bc026f0e9b396f2665531 --- ovn_octavia_provider/common/constants.py | 2 + ovn_octavia_provider/helper.py | 4 + ovn_octavia_provider/tests/functional/base.py | 4 + .../tests/functional/test_integration.py | 153 ++++++++++++++++++ .../tests/unit/test_helper.py | 24 +++ 5 files changed, 187 insertions(+) create mode 100644 ovn_octavia_provider/tests/functional/test_integration.py diff --git a/ovn_octavia_provider/common/constants.py b/ovn_octavia_provider/common/constants.py index 5ae33e09..85a7917d 100644 --- a/ovn_octavia_provider/common/constants.py +++ b/ovn_octavia_provider/common/constants.py @@ -38,6 +38,8 @@ LB_EXT_IDS_VIP_KEY = 'neutron:vip' LB_EXT_IDS_VIP_FIP_KEY = 'neutron:vip_fip' LB_EXT_IDS_VIP_PORT_ID_KEY = 'neutron:vip_port_id' +PORT_FORWARDING_PLUGIN = 'port_forwarding_plugin' + # Auth sections SERVICE_AUTH = 'service_auth' diff --git a/ovn_octavia_provider/helper.py b/ovn_octavia_provider/helper.py index 82548739..c1edf907 100644 --- a/ovn_octavia_provider/helper.py +++ b/ovn_octavia_provider/helper.py @@ -428,6 +428,10 @@ class OvnProviderHelper(object): lbs = self.ovn_nbdb_api.db_list_rows( 'Load_Balancer').execute(check_error=True) for lb in lbs: + # Skip load balancers used by port forwarding plugin + if lb.external_ids.get(ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY) == ( + ovn_const.PORT_FORWARDING_PLUGIN): + continue if pool_key in lb.external_ids: return lb diff --git a/ovn_octavia_provider/tests/functional/base.py b/ovn_octavia_provider/tests/functional/base.py index 208c152c..550ba01b 100644 --- a/ovn_octavia_provider/tests/functional/base.py +++ b/ovn_octavia_provider/tests/functional/base.py @@ -167,6 +167,10 @@ class TestOvnOctaviaBase(base.TestOVNFunctionalBase, lbs = [] for lb in self.nb_api.tables['Load_Balancer'].rows.values(): external_ids = dict(lb.external_ids) + # Skip load balancers used by port forwarding plugin + if external_ids.get(ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY) == ( + ovn_const.PORT_FORWARDING_PLUGIN): + continue ls_refs = external_ids.get(ovn_const.LB_EXT_IDS_LS_REFS_KEY) if ls_refs: external_ids[ diff --git a/ovn_octavia_provider/tests/functional/test_integration.py b/ovn_octavia_provider/tests/functional/test_integration.py new file mode 100644 index 00000000..65a1ac07 --- /dev/null +++ b/ovn_octavia_provider/tests/functional/test_integration.py @@ -0,0 +1,153 @@ +# Copyright 2020 Red Hat, Inc. +# 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 ovn_octavia_provider.common import constants as ovn_const +from ovn_octavia_provider.common import utils +from ovn_octavia_provider.tests.functional import base as ovn_base + +from neutron import manager +from neutron_lib.api.definitions import floating_ip_port_forwarding as pf_def +from oslo_config import cfg + + +class TestOvnOctaviaProviderIntegration(ovn_base.TestOvnOctaviaBase): + + def setUp(self): + super(TestOvnOctaviaProviderIntegration, self).setUp() + # Add port_forwarding as a configured service plugin (if needed) + svc_plugins = set(cfg.CONF.service_plugins) + svc_plugins.add("port_forwarding") + cfg.CONF.set_override("service_plugins", list(svc_plugins)) + if not self.pf_plugin: + # OVN does not use RPC: disable it for port-forwarding tests + self.pf_plugin = manager.NeutronManager.load_class_for_provider( + 'neutron.service_plugins', 'port_forwarding')() + self.pf_plugin._rpc_notifications_required = False + self.assertIsNotNone(self.pf_plugin, + "TestOVNFunctionalBase is expected to have " + "port forwarding plugin configured") + + def _find_pf_lb(self, router_id, fip_id=None): + result = [] + for ovn_lb in self.nb_api.get_router_floatingip_lbs( + utils.ovn_name(router_id)): + ext_ids = ovn_lb.external_ids + if not fip_id or fip_id == ext_ids[ovn_const.OVN_FIP_EXT_ID_KEY]: + result.append(ovn_lb) + return result or None + + def _loadbalancer_operation(self, lb_data=None, update=False, + delete=False): + if not lb_data: + lb_data = self._create_load_balancer_and_validate( + {'vip_network': 'vip_network', 'cidr': '10.0.0.0/24'}) + if update: + self._update_load_balancer_and_validate(lb_data, + admin_state_up=False) + self._update_load_balancer_and_validate(lb_data, + admin_state_up=True) + if delete: + self._delete_load_balancer_and_validate(lb_data) + return None if delete else lb_data + + def _validate_from_lb_data(self, lb_data): + expected_lbs = self._make_expected_lbs(lb_data) + self._validate_loadbalancers(expected_lbs) + + def test_port_forwarding(self): + + def _verify_pf_lb(test, protocol, vip_ext_port, vip_int_port): + ovn_lbs = test._find_pf_lb(router_id, fip_id) + test.assertEqual(len(ovn_lbs), 1) + test.assertEqual(ovn_lbs[0].name, + 'pf-floatingip-{}-{}'.format(fip_id, protocol)) + self.assertEqual(ovn_lbs[0].vips, { + '{}:{}'.format(fip_ip, vip_ext_port): + '{}:{}'.format(p1_ip, vip_int_port)}) + + n1, s1 = self._create_provider_network() + ext_net = n1['network'] + ext_subnet = s1['subnet'] + + gw_info = { + 'enable_snat': True, + 'network_id': ext_net['id'], + 'external_fixed_ips': [ + {'ip_address': '100.0.0.2', 'subnet_id': ext_subnet['id']}]} + router_id = self._create_router('routertest', gw_info=gw_info) + + # Create Network N2, connect it to router + n2_id, sub2_id, p1_ip, p1_id = self._create_net( + "N2", "10.0.1.0/24", router_id) + + fip_info = {'floatingip': { + 'tenant_id': self._tenant_id, + 'floating_network_id': ext_net['id'], + 'port_id': None, + 'fixed_ip_address': None}} + fip = self.l3_plugin.create_floatingip(self.context, fip_info) + fip_id = fip['id'] + fip_ip = fip['floating_ip_address'] + + # Create floating ip port forwarding. This will create an + # OVN load balancer + fip_pf_args = { + pf_def.EXTERNAL_PORT: 2222, + pf_def.INTERNAL_PORT: 22, + pf_def.INTERNAL_PORT_ID: p1_id, + pf_def.PROTOCOL: 'tcp', + pf_def.INTERNAL_IP_ADDRESS: p1_ip} + fip_attrs = {pf_def.RESOURCE_NAME: {pf_def.RESOURCE_NAME: fip_pf_args}} + pf_obj = self.pf_plugin.create_floatingip_port_forwarding( + self.context, fip_id, **fip_attrs) + + # Check pf_lb with no octavia_provider_lb + _verify_pf_lb(self, 'tcp', 2222, 22) + + # Create octavia_provider_lb + lb_data = self._loadbalancer_operation() + expected_lbs = self._make_expected_lbs(lb_data) + _verify_pf_lb(self, 'tcp', 2222, 22) + + fip_pf_args2 = {pf_def.EXTERNAL_PORT: 5353, pf_def.INTERNAL_PORT: 53, + pf_def.PROTOCOL: 'udp'} + fip_attrs2 = {pf_def.RESOURCE_NAME: { + pf_def.RESOURCE_NAME: fip_pf_args2}} + self.pf_plugin.update_floatingip_port_forwarding( + self.context, pf_obj['id'], fip_id, **fip_attrs2) + + # Make sure octavia_provider_lb is not disturbed + self._validate_loadbalancers(expected_lbs) + + # Update octavia_provider_lb + self._loadbalancer_operation(lb_data, update=True) + _verify_pf_lb(self, 'udp', 5353, 53) + + # Delete octavia_provider_lb + self._loadbalancer_operation(lb_data, delete=True) + _verify_pf_lb(self, 'udp', 5353, 53) + + # Delete pf_lb after creating octavia_provider_lb + lb_data = self._loadbalancer_operation() + expected_lbs = self._make_expected_lbs(lb_data) + + self.pf_plugin.delete_floatingip_port_forwarding( + self.context, pf_obj['id'], fip_id) + self._loadbalancer_operation(lb_data, update=True) + self.assertIsNone(self._find_pf_lb(router_id, fip_id)) + + # Make sure octavia_provider_lb is not disturbed + self._validate_loadbalancers(expected_lbs) + self._loadbalancer_operation(lb_data, delete=True) diff --git a/ovn_octavia_provider/tests/unit/test_helper.py b/ovn_octavia_provider/tests/unit/test_helper.py index d4d936a5..b94a1776 100644 --- a/ovn_octavia_provider/tests/unit/test_helper.py +++ b/ovn_octavia_provider/tests/unit/test_helper.py @@ -33,6 +33,8 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase): def setUp(self): super(TestOvnProviderHelper, self).setUp() self.helper = ovn_helper.OvnProviderHelper() + self.real_helper_find_ovn_lb_with_pool_key = ( + self.helper._find_ovn_lb_with_pool_key) mock.patch.object(self.helper, '_update_status_to_octavia').start() self.listener = {'id': self.listener_id, 'loadbalancer_id': self.loadbalancer_id, @@ -185,6 +187,28 @@ class TestOvnProviderHelper(ovn_base.TestOvnOctaviaBase): status = {} self.assertFalse(f(status)) + def test__find_ovn_lb_with_pool_key(self): + pool_key = self.helper._get_pool_key(uuidutils.generate_uuid()) + test_lb = mock.MagicMock() + test_lb.external_ids = { + ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY: + ovn_const.PORT_FORWARDING_PLUGIN, + pool_key: 'it_is_a_pool_party', + } + self.helper.ovn_nbdb_api.db_list_rows.return_value.\ + execute.return_value = [test_lb] + f = self.real_helper_find_ovn_lb_with_pool_key + + # Ensure lb is not found, due to its device owner + found = f(pool_key) + self.assertIsNone(found) + + # Remove device owner from test_lb.external_ids and make sure test_lb + # is found as expected + test_lb.external_ids.pop(ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY) + found = f(pool_key) + self.assertEqual(found, test_lb) + def test__find_ovn_lbs(self): self.mock_find_ovn_lbs.stop() f = self.helper._find_ovn_lbs