# Copyright (c) 2019 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 unittest import mock from neutron_lib import constants from neutron_lib import context from oslo_utils import uuidutils from neutron.agent.l3 import agent as l3_agent from neutron.agent.l3.extensions import conntrack_helper as cth from neutron.agent.l3 import l3_agent_extension_api as l3_ext_api from neutron.agent.l3 import router_info as l3router from neutron.agent.linux import iptables_manager from neutron.api.rpc.callbacks.consumer import registry from neutron.api.rpc.callbacks import resources from neutron.api.rpc.handlers import resources_rpc from neutron.objects import conntrack_helper as cth_obj from neutron.tests import base from neutron.tests.unit.agent.l3 import test_agent BINARY_NAME = iptables_manager.get_binary_name() DEFAULT_RULE = ('PREROUTING', '-j %s-' % BINARY_NAME + cth.DEFAULT_CONNTRACK_HELPER_CHAIN) HOSTNAME = 'testhost' class ConntrackHelperExtensionBaseTestCase( test_agent.BasicRouterOperationsFramework): def setUp(self): super(ConntrackHelperExtensionBaseTestCase, self).setUp() self.cth_ext = cth.ConntrackHelperAgentExtension() self.context = context.get_admin_context() self.connection = mock.Mock() self.router_id = uuidutils.generate_uuid() self.conntrack_helper1 = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='udp', port=69, helper='tftp', router_id=self.router_id) self.agent = l3_agent.L3NATAgent(HOSTNAME, self.conf) self.router = {'id': self.router_id, 'ha': False, 'distributed': False} self.router_info = l3router.RouterInfo(self.agent, self.router_id, self.router, **self.ri_kwargs) self.agent.router_info[self.router['id']] = self.router_info self.get_router_info = mock.patch( 'neutron.agent.l3.l3_agent_extension_api.' 'L3AgentExtensionAPI.get_router_info').start() self.get_router_info.return_value = self.router_info self.agent_api = l3_ext_api.L3AgentExtensionAPI(None, None) self.cth_ext.consume_api(self.agent_api) self.conntrack_helpers = [self.conntrack_helper1] class ConntrackHelperExtensionInitializeTestCase( ConntrackHelperExtensionBaseTestCase): @mock.patch.object(registry, 'register') @mock.patch.object(resources_rpc, 'ResourcesPushRpcCallback') def test_initialize_subscribed_to_rpc(self, rpc_mock, subscribe_mock): call_to_patch = 'neutron_lib.rpc.Connection' with mock.patch(call_to_patch, return_value=self.connection) as create_connection: self.cth_ext.initialize( self.connection, constants.L3_AGENT_MODE) create_connection.assert_has_calls([mock.call()]) self.connection.create_consumer.assert_has_calls( [mock.call( resources_rpc.resource_type_versioned_topic( resources.CONNTRACKHELPER), [rpc_mock()], fanout=True)] ) subscribe_mock.assert_called_with( mock.ANY, resources.CONNTRACKHELPER) class ConntrackHelperExtensionTestCase(ConntrackHelperExtensionBaseTestCase): def setUp(self): super(ConntrackHelperExtensionTestCase, self).setUp() self.cth_ext.initialize( self.connection, constants.L3_AGENT_MODE) self._set_bulk_pull_mock() def _set_bulk_pull_mock(self): def _bulk_pull_mock(context, resource_type, filter_kwargs=None): if 'router_id' in filter_kwargs: result = [] for cthobj in self.conntrack_helpers: if cthobj.router_id in filter_kwargs['router_id']: result.append(cthobj) return result return self.conntrack_helpers self.bulk_pull = mock.patch( 'neutron.api.rpc.handlers.resources_rpc.' 'ResourcesPullRpcApi.bulk_pull').start() self.bulk_pull.side_effect = _bulk_pull_mock @mock.patch.object(iptables_manager.IptablesTable, 'add_rule') @mock.patch.object(iptables_manager.IptablesTable, 'add_chain') def test_create_router(self, mock_add_chain, mock_add_rule): self.cth_ext.add_router(self.context, self.router) chain_name = (cth.CONNTRACK_HELPER_CHAIN_PREFIX + self.conntrack_helper1.id)[ :constants.MAX_IPTABLES_CHAIN_LEN_WRAP] chain_rule = ('-p %(protocol)s --dport %(dport)s -j CT --helper ' '%(helper)s' % {'protocol': self.conntrack_helper1.protocol, 'dport': self.conntrack_helper1.port, 'helper': self.conntrack_helper1.helper}) tag = cth.CONNTRACK_HELPER_PREFIX + self.conntrack_helper1.id self.assertEqual(mock_add_chain.call_count, 6) self.assertEqual(mock_add_rule.call_count, 6) mock_add_chain.assert_has_calls([ mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(chain_name), mock.call(chain_name) ]) mock_add_rule.assert_has_calls([ mock.call(DEFAULT_RULE[0], DEFAULT_RULE[1]), mock.call(DEFAULT_RULE[0], DEFAULT_RULE[1]), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN, '-j %s-' % BINARY_NAME + chain_name, tag=tag), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN, '-j %s-' % BINARY_NAME + chain_name, tag=tag), mock.call(chain_name, chain_rule, tag=tag), mock.call(chain_name, chain_rule, tag=tag) ]) @mock.patch.object(iptables_manager.IptablesTable, 'add_rule') @mock.patch.object(iptables_manager.IptablesTable, 'add_chain') def test_update_roter(self, mock_add_chain, mock_add_rule): self.cth_ext.add_router(self.context, self.router) mock_add_chain.reset_mock() mock_add_rule.reset_mock() self.cth_ext.update_router(self.context, self.router) mock_add_chain.assert_not_called() mock_add_rule.assert_not_called() @mock.patch.object(iptables_manager.IptablesTable, 'add_rule') @mock.patch.object(iptables_manager.IptablesTable, 'add_chain') def test_add_conntrack_helper_update_router(self, mock_add_chain, mock_add_rule): self.cth_ext.add_router(self.context, self.router) # Create another conntrack helper with the same router_id mock_add_chain.reset_mock() mock_add_rule.reset_mock() test_conntrackhelper = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='tcp', port=21, helper='ftp', router_id=self.conntrack_helper1.router_id) self.conntrack_helpers.append(test_conntrackhelper) self.cth_ext.update_router(self.context, self.router) chain_name = (cth.CONNTRACK_HELPER_CHAIN_PREFIX + test_conntrackhelper.id)[ :constants.MAX_IPTABLES_CHAIN_LEN_WRAP] chain_rule = ('-p %(protocol)s --dport %(dport)s -j CT --helper ' '%(helper)s' % {'protocol': test_conntrackhelper.protocol, 'dport': test_conntrackhelper.port, 'helper': test_conntrackhelper.helper}) tag = cth.CONNTRACK_HELPER_PREFIX + test_conntrackhelper.id self.assertEqual(mock_add_chain.call_count, 6) self.assertEqual(mock_add_rule.call_count, 6) mock_add_chain.assert_has_calls([ mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN), mock.call(chain_name), mock.call(chain_name) ]) mock_add_rule.assert_has_calls([ mock.call(DEFAULT_RULE[0], DEFAULT_RULE[1]), mock.call(DEFAULT_RULE[0], DEFAULT_RULE[1]), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN, '-j %s-' % BINARY_NAME + chain_name, tag=tag), mock.call(cth.DEFAULT_CONNTRACK_HELPER_CHAIN, '-j %s-' % BINARY_NAME + chain_name, tag=tag), mock.call(chain_name, chain_rule, tag=tag), mock.call(chain_name, chain_rule, tag=tag) ]) @mock.patch.object(cth.ConntrackHelperMapping, 'clear_by_router_id') def test_delete_router(self, mock_clear_by_router_id): router_data = {'id': self.router_id, 'ha': False, 'distributed': False} self.cth_ext.delete_router(self.context, router_data) mock_clear_by_router_id.assert_called_with(self.router_id) class ConntrackHelperMappingTestCase(base.BaseTestCase): def setUp(self): super(ConntrackHelperMappingTestCase, self).setUp() self.mapping = cth.ConntrackHelperMapping() self.router1 = uuidutils.generate_uuid() self.router2 = uuidutils.generate_uuid() self.conntrack_helper1 = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='udp', port=69, helper='tftp', router_id=self.router1) self.conntrack_helper2 = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='udp', port=69, helper='tftp', router_id=self.router2) self.conntrack_helper3 = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='udp', port=21, helper='ftp', router_id=self.router1) self.conntrack_helper4 = cth_obj.ConntrackHelper( context=None, id=uuidutils.generate_uuid(), protocol='udp', port=21, helper='ftp', router_id=self.router2) self.conntrack_helper_dict = { self.conntrack_helper1.id: self.conntrack_helper1, self.conntrack_helper2.id: self.conntrack_helper2, self.conntrack_helper3.id: self.conntrack_helper3, self.conntrack_helper4.id: self.conntrack_helper4} def _set_cth(self): self.mapping.set_conntrack_helpers( self.conntrack_helper_dict.values()) def test_set_conntrack_helpers(self): self._set_cth() cth_ids = self.conntrack_helper_dict.keys() managed_cths = self.mapping.get_managed_conntrack_helpers() for cth_id, obj in managed_cths.items(): self.assertIn(cth_id, cth_ids) self.assertEqual(obj, self.conntrack_helper_dict[cth_id]) self.assertEqual( len(cth_ids), len(managed_cths.keys())) def test_update_conntrack_helper(self): self._set_cth() new_conntrack_helper1 = cth_obj.ConntrackHelper( context=None, id=self.conntrack_helper1.id, protocol='udp', port=6969, helper='tftp', router_id=self.router1) self.mapping.update_conntrack_helpers([new_conntrack_helper1]) managed_cths = self.mapping.get_managed_conntrack_helpers() self.assertEqual( new_conntrack_helper1, managed_cths[self.conntrack_helper1.id]) for router_id in self.mapping._router_conntrack_helper_mapping.keys(): self.assertIn(router_id, [self.router1, self.router2]) self.assertEqual( len([self.router1, self.router2]), len(self.mapping._router_conntrack_helper_mapping.keys())) def test_del_conntrack_helper(self): self._set_cth() self.mapping.del_conntrack_helpers([self.conntrack_helper3, self.conntrack_helper2, self.conntrack_helper4]) managed_cths = self.mapping.get_managed_conntrack_helpers() self.assertEqual([self.conntrack_helper1.id], list(managed_cths.keys())) self.assertNotIn(self.conntrack_helper3.id, self.mapping._router_conntrack_helper_mapping[ self.conntrack_helper3.router_id]) self.assertNotIn(self.router2, self.mapping._router_conntrack_helper_mapping.keys()) def test_clear_by_router_id(self): self._set_cth() self.mapping.clear_by_router_id(self.router2) managed_cths = self.mapping.get_managed_conntrack_helpers() self.assertNotIn(self.conntrack_helper2, managed_cths.keys()) self.assertNotIn(self.conntrack_helper4, managed_cths.keys()) def test_check_conntrack_helper_changes(self): self._set_cth() new_cth = cth_obj.ConntrackHelper( context=None, id=self.conntrack_helper1.id, protocol='udp', port=6969, helper='tftp', router_id=self.router1) self.assertTrue(self.mapping.check_conntrack_helper_changes(new_cth)) def test_check_conntrack_helper_changes_no_change(self): self._set_cth() new_cth = cth_obj.ConntrackHelper( context=None, id=self.conntrack_helper1.id, protocol='udp', port=69, helper='tftp', router_id=self.router1) self.assertFalse(self.mapping.check_conntrack_helper_changes(new_cth))