From ee83e0be069056c3c486075fed5f621208ece8e0 Mon Sep 17 00:00:00 2001 From: Manjeet Singh Bhatia Date: Mon, 9 Nov 2015 22:03:15 +0000 Subject: [PATCH] Reorganize and improve l3_agent functional tests This will reorganize the l3_agent functional tests Change-Id: I10008fd7216c8de47162657e280b7245c38f5154 Closes-Bug: #1501150 --- .../tests/functional/agent/l3/framework.py | 483 +++++ .../functional/agent/l3/test_dvr_router.py | 637 +++++++ .../functional/agent/l3/test_ha_router.py | 284 +++ .../functional/agent/l3/test_legacy_router.py | 199 ++ .../agent/l3/test_metadata_proxy.py | 152 ++ .../tests/functional/agent/test_l3_agent.py | 1635 +---------------- 6 files changed, 1757 insertions(+), 1633 deletions(-) create mode 100644 neutron/tests/functional/agent/l3/framework.py create mode 100644 neutron/tests/functional/agent/l3/test_dvr_router.py create mode 100644 neutron/tests/functional/agent/l3/test_ha_router.py create mode 100644 neutron/tests/functional/agent/l3/test_legacy_router.py create mode 100644 neutron/tests/functional/agent/l3/test_metadata_proxy.py diff --git a/neutron/tests/functional/agent/l3/framework.py b/neutron/tests/functional/agent/l3/framework.py new file mode 100644 index 000000000..465338a90 --- /dev/null +++ b/neutron/tests/functional/agent/l3/framework.py @@ -0,0 +1,483 @@ +# Copyright (c) 2014 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. + +import functools + +import mock +import netaddr +import testtools + +from oslo_config import cfg +from oslo_log import log as logging +from oslo_utils import uuidutils + +from neutron.agent.common import config as agent_config +from neutron.agent.common import ovs_lib +from neutron.agent.l3 import agent as neutron_l3_agent +from neutron.agent import l3_agent as l3_agent_main +from neutron.agent.linux import external_process +from neutron.agent.linux import ip_lib +from neutron.agent.linux import utils +from neutron.common import config as common_config +from neutron.common import constants as l3_constants +from neutron.common import utils as common_utils +from neutron.tests.common import l3_test_common +from neutron.tests.common import net_helpers +from neutron.tests.functional import base + + +LOG = logging.getLogger(__name__) +_uuid = uuidutils.generate_uuid + + +def get_ovs_bridge(br_name): + return ovs_lib.OVSBridge(br_name) + + +class L3AgentTestFramework(base.BaseSudoTestCase): + def setUp(self): + super(L3AgentTestFramework, self).setUp() + self.mock_plugin_api = mock.patch( + 'neutron.agent.l3.agent.L3PluginApi').start().return_value + mock.patch('neutron.agent.rpc.PluginReportStateAPI').start() + self.conf = self._configure_agent('agent1') + self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', + self.conf) + + def _get_config_opts(self): + config = cfg.ConfigOpts() + config.register_opts(common_config.core_opts) + config.register_opts(common_config.core_cli_opts) + logging.register_options(config) + agent_config.register_process_monitor_opts(config) + return config + + def _configure_agent(self, host, agent_mode='dvr_snat'): + conf = self._get_config_opts() + l3_agent_main.register_opts(conf) + conf.set_override( + 'interface_driver', + 'neutron.agent.linux.interface.OVSInterfaceDriver') + + br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge + br_ex = self.useFixture(net_helpers.OVSBridgeFixture()).bridge + conf.set_override('ovs_integration_bridge', br_int.br_name) + conf.set_override('external_network_bridge', br_ex.br_name) + + temp_dir = self.get_new_temp_dir() + get_temp_file_path = functools.partial(self.get_temp_file_path, + root=temp_dir) + conf.set_override('state_path', temp_dir.path) + # NOTE(cbrandily): log_file or log_dir must be set otherwise + # metadata_proxy_watch_log has no effect + conf.set_override('log_file', + get_temp_file_path('log_file')) + conf.set_override('metadata_proxy_socket', + get_temp_file_path('metadata_proxy')) + conf.set_override('ha_confs_path', + get_temp_file_path('ha_confs')) + conf.set_override('external_pids', + get_temp_file_path('external/pids')) + conf.set_override('host', host) + conf.set_override('agent_mode', agent_mode) + + return conf + + def _get_agent_ovs_integration_bridge(self, agent): + return get_ovs_bridge(agent.conf.ovs_integration_bridge) + + def generate_router_info(self, enable_ha, ip_version=4, extra_routes=True, + enable_fip=True, enable_snat=True, + dual_stack=False, v6_ext_gw_with_sub=True): + if ip_version == 6 and not dual_stack: + enable_snat = False + enable_fip = False + extra_routes = False + + return l3_test_common.prepare_router_data(ip_version=ip_version, + enable_snat=enable_snat, + enable_floating_ip=enable_fip, + enable_ha=enable_ha, + extra_routes=extra_routes, + dual_stack=dual_stack, + v6_ext_gw_with_sub=( + v6_ext_gw_with_sub)) + + def _test_conntrack_disassociate_fip(self, ha): + '''Test that conntrack immediately drops stateful connection + that uses floating IP once it's disassociated. + ''' + router_info = self.generate_router_info(enable_ha=ha) + router = self.manage_router(self.agent, router_info) + + port = net_helpers.get_free_namespace_port(l3_constants.PROTO_NAME_TCP, + router.ns_name) + client_address = '19.4.4.3' + server_address = '35.4.0.4' + + def clean_fips(router): + router.router[l3_constants.FLOATINGIP_KEY] = [] + + clean_fips(router) + self._add_fip(router, client_address, fixed_address=server_address) + router.process(self.agent) + + router_ns = ip_lib.IPWrapper(namespace=router.ns_name) + netcat = net_helpers.NetcatTester( + router.ns_name, router.ns_name, client_address, port, + protocol=net_helpers.NetcatTester.TCP) + self.addCleanup(netcat.stop_processes) + + def assert_num_of_conntrack_rules(n): + out = router_ns.netns.execute(["conntrack", "-L", + "--orig-src", client_address]) + self.assertEqual( + n, len([line for line in out.strip().split('\n') if line])) + + if ha: + utils.wait_until_true(lambda: router.ha_state == 'master') + + with self.assert_max_execution_time(100): + assert_num_of_conntrack_rules(0) + + self.assertTrue(netcat.test_connectivity()) + assert_num_of_conntrack_rules(1) + + clean_fips(router) + router.process(self.agent) + assert_num_of_conntrack_rules(0) + + with testtools.ExpectedException(RuntimeError): + netcat.test_connectivity() + + def _gateway_check(self, gateway_ip, external_device): + expected_gateway = gateway_ip + ip_vers = netaddr.IPAddress(expected_gateway).version + existing_gateway = (external_device.route.get_gateway( + ip_version=ip_vers).get('gateway')) + self.assertEqual(expected_gateway, existing_gateway) + + def _assert_ha_device(self, router): + def ha_router_dev_name_getter(not_used): + return router.get_ha_device_name() + self.assertTrue(self.device_exists_with_ips_and_mac( + router.router[l3_constants.HA_INTERFACE_KEY], + ha_router_dev_name_getter, router.ns_name)) + + def _assert_gateway(self, router, v6_ext_gw_with_sub=True): + external_port = router.get_ex_gw_port() + external_device_name = router.get_external_device_name( + external_port['id']) + external_device = ip_lib.IPDevice(external_device_name, + namespace=router.ns_name) + for subnet in external_port['subnets']: + self._gateway_check(subnet['gateway_ip'], external_device) + if not v6_ext_gw_with_sub: + self._gateway_check(self.agent.conf.ipv6_gateway, + external_device) + + def _assert_external_device(self, router): + external_port = router.get_ex_gw_port() + self.assertTrue(self.device_exists_with_ips_and_mac( + external_port, router.get_external_device_name, + router.ns_name)) + + def _router_lifecycle(self, enable_ha, ip_version=4, + dual_stack=False, v6_ext_gw_with_sub=True): + router_info = self.generate_router_info(enable_ha, ip_version, + dual_stack=dual_stack, + v6_ext_gw_with_sub=( + v6_ext_gw_with_sub)) + router = self.manage_router(self.agent, router_info) + + # Add multiple-IPv6-prefix internal router port + slaac = l3_constants.IPV6_SLAAC + slaac_mode = {'ra_mode': slaac, 'address_mode': slaac} + subnet_modes = [slaac_mode] * 2 + self._add_internal_interface_by_subnet(router.router, + count=2, + ip_version=6, + ipv6_subnet_modes=subnet_modes) + router.process(self.agent) + + if enable_ha: + port = router.get_ex_gw_port() + interface_name = router.get_external_device_name(port['id']) + self._assert_no_ip_addresses_on_interface(router.ns_name, + interface_name) + utils.wait_until_true(lambda: router.ha_state == 'master') + + # Keepalived notifies of a state transition when it starts, + # not when it ends. Thus, we have to wait until keepalived finishes + # configuring everything. We verify this by waiting until the last + # device has an IP address. + device = router.router[l3_constants.INTERFACE_KEY][-1] + device_exists = functools.partial( + self.device_exists_with_ips_and_mac, + device, + router.get_internal_device_name, + router.ns_name) + utils.wait_until_true(device_exists) + + self.assertTrue(self._namespace_exists(router.ns_name)) + utils.wait_until_true( + lambda: self._metadata_proxy_exists(self.agent.conf, router)) + self._assert_internal_devices(router) + self._assert_external_device(router) + if not (enable_ha and (ip_version == 6 or dual_stack)): + # Note(SridharG): enable the assert_gateway for IPv6 once + # keepalived on Ubuntu14.04 (i.e., check-neutron-dsvm-functional + # platform) is updated to 1.2.10 (or above). + # For more details: https://review.openstack.org/#/c/151284/ + self._assert_gateway(router, v6_ext_gw_with_sub) + self.assertTrue(self.floating_ips_configured(router)) + self._assert_snat_chains(router) + self._assert_floating_ip_chains(router) + self._assert_extra_routes(router) + ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4] + self._assert_onlink_subnet_routes(router, ip_versions) + self._assert_metadata_chains(router) + + # Verify router gateway interface is configured to receive Router Advts + # when IPv6 is enabled and no IPv6 gateway is configured. + if router.use_ipv6 and not v6_ext_gw_with_sub: + if not self.agent.conf.ipv6_gateway: + external_port = router.get_ex_gw_port() + external_device_name = router.get_external_device_name( + external_port['id']) + ip_wrapper = ip_lib.IPWrapper(namespace=router.ns_name) + ra_state = ip_wrapper.netns.execute(['sysctl', '-b', + 'net.ipv6.conf.%s.accept_ra' % external_device_name]) + self.assertEqual('2', ra_state) + + if enable_ha: + self._assert_ha_device(router) + self.assertTrue(router.keepalived_manager.get_process().active) + + self._delete_router(self.agent, router.router_id) + + self._assert_interfaces_deleted_from_ovs() + self._assert_router_does_not_exist(router) + if enable_ha: + self.assertFalse(router.keepalived_manager.get_process().active) + + def manage_router(self, agent, router): + self.addCleanup(agent._safe_router_removed, router['id']) + agent._process_added_router(router) + return agent.router_info[router['id']] + + def _delete_router(self, agent, router_id): + agent._router_removed(router_id) + + def _add_fip(self, router, fip_address, fixed_address='10.0.0.2', + host=None): + fip = {'id': _uuid(), + 'port_id': _uuid(), + 'floating_ip_address': fip_address, + 'fixed_ip_address': fixed_address, + 'host': host} + router.router[l3_constants.FLOATINGIP_KEY].append(fip) + + def _add_internal_interface_by_subnet(self, router, count=1, + ip_version=4, + ipv6_subnet_modes=None, + interface_id=None): + return l3_test_common.router_append_subnet(router, count, + ip_version, ipv6_subnet_modes, interface_id) + + def _namespace_exists(self, namespace): + ip = ip_lib.IPWrapper(namespace=namespace) + return ip.netns.exists(namespace) + + def _metadata_proxy_exists(self, conf, router): + pm = external_process.ProcessManager( + conf, + router.router_id, + router.ns_name) + return pm.active + + def device_exists_with_ips_and_mac(self, expected_device, name_getter, + namespace): + ip_cidrs = common_utils.fixed_ip_cidrs(expected_device['fixed_ips']) + return ip_lib.device_exists_with_ips_and_mac( + name_getter(expected_device['id']), ip_cidrs, + expected_device['mac_address'], namespace) + + @staticmethod + def _port_first_ip_cidr(port): + fixed_ip = port['fixed_ips'][0] + return common_utils.ip_to_cidr(fixed_ip['ip_address'], + fixed_ip['prefixlen']) + + def get_device_mtu(self, target_device, name_getter, namespace): + device = ip_lib.IPDevice(name_getter(target_device), namespace) + return device.link.mtu + + def get_expected_keepalive_configuration(self, router): + ha_device_name = router.get_ha_device_name() + external_port = router.get_ex_gw_port() + ex_port_ipv6 = ip_lib.get_ipv6_lladdr(external_port['mac_address']) + external_device_name = router.get_external_device_name( + external_port['id']) + external_device_cidr = self._port_first_ip_cidr(external_port) + internal_port = router.router[l3_constants.INTERFACE_KEY][0] + int_port_ipv6 = ip_lib.get_ipv6_lladdr(internal_port['mac_address']) + internal_device_name = router.get_internal_device_name( + internal_port['id']) + internal_device_cidr = self._port_first_ip_cidr(internal_port) + floating_ip_cidr = common_utils.ip_to_cidr( + router.get_floating_ips()[0]['floating_ip_address']) + default_gateway_ip = external_port['subnets'][0].get('gateway_ip') + extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr') + return """vrrp_instance VR_1 { + state BACKUP + interface %(ha_device_name)s + virtual_router_id 1 + priority 50 + garp_master_repeat 5 + garp_master_refresh 10 + nopreempt + advert_int 2 + track_interface { + %(ha_device_name)s + } + virtual_ipaddress { + 169.254.0.1/24 dev %(ha_device_name)s + } + virtual_ipaddress_excluded { + %(floating_ip_cidr)s dev %(external_device_name)s + %(external_device_cidr)s dev %(external_device_name)s + %(internal_device_cidr)s dev %(internal_device_name)s + %(ex_port_ipv6)s dev %(external_device_name)s scope link + %(int_port_ipv6)s dev %(internal_device_name)s scope link + } + virtual_routes { + 0.0.0.0/0 via %(default_gateway_ip)s dev %(external_device_name)s + 8.8.8.0/24 via 19.4.4.4 + %(extra_subnet_cidr)s dev %(external_device_name)s scope link + } +}""" % { + 'ha_device_name': ha_device_name, + 'external_device_name': external_device_name, + 'external_device_cidr': external_device_cidr, + 'internal_device_name': internal_device_name, + 'internal_device_cidr': internal_device_cidr, + 'floating_ip_cidr': floating_ip_cidr, + 'default_gateway_ip': default_gateway_ip, + 'int_port_ipv6': int_port_ipv6, + 'ex_port_ipv6': ex_port_ipv6, + 'extra_subnet_cidr': extra_subnet_cidr, + } + + def _get_rule(self, iptables_manager, table, chain, predicate): + rules = iptables_manager.get_chain(table, chain) + result = next(rule for rule in rules if predicate(rule)) + return result + + def _assert_router_does_not_exist(self, router): + # If the namespace assertion succeeds + # then the devices and iptable rules have also been deleted, + # so there's no need to check that explicitly. + self.assertFalse(self._namespace_exists(router.ns_name)) + utils.wait_until_true( + lambda: not self._metadata_proxy_exists(self.agent.conf, router)) + + def _assert_snat_chains(self, router): + self.assertFalse(router.iptables_manager.is_chain_empty( + 'nat', 'snat')) + self.assertFalse(router.iptables_manager.is_chain_empty( + 'nat', 'POSTROUTING')) + + def _assert_floating_ip_chains(self, router): + self.assertFalse(router.iptables_manager.is_chain_empty( + 'nat', 'float-snat')) + + def _assert_metadata_chains(self, router): + metadata_port_filter = lambda rule: ( + str(self.agent.conf.metadata_port) in rule.rule) + self.assertTrue(self._get_rule(router.iptables_manager, + 'nat', + 'PREROUTING', + metadata_port_filter)) + self.assertTrue(self._get_rule(router.iptables_manager, + 'filter', + 'INPUT', + metadata_port_filter)) + + def _assert_internal_devices(self, router): + internal_devices = router.router[l3_constants.INTERFACE_KEY] + self.assertTrue(len(internal_devices)) + for device in internal_devices: + self.assertTrue(self.device_exists_with_ips_and_mac( + device, router.get_internal_device_name, router.ns_name)) + + def _assert_extra_routes(self, router): + routes = ip_lib.get_routing_table(4, namespace=router.ns_name) + routes = [{'nexthop': route['nexthop'], + 'destination': route['destination']} for route in routes] + + for extra_route in router.router['routes']: + self.assertIn(extra_route, routes) + + def _assert_onlink_subnet_routes( + self, router, ip_versions, namespace=None): + ns_name = namespace or router.ns_name + routes = [] + for ip_version in ip_versions: + _routes = ip_lib.get_routing_table(ip_version, + namespace=ns_name) + routes.extend(_routes) + routes = set(route['destination'] for route in routes) + extra_subnets = router.get_ex_gw_port()['extra_subnets'] + for extra_subnet in (route['cidr'] for route in extra_subnets): + self.assertIn(extra_subnet, routes) + + def _assert_interfaces_deleted_from_ovs(self): + + def assert_ovs_bridge_empty(bridge_name): + bridge = ovs_lib.OVSBridge(bridge_name) + self.assertFalse(bridge.get_port_name_list()) + + assert_ovs_bridge_empty(self.agent.conf.ovs_integration_bridge) + assert_ovs_bridge_empty(self.agent.conf.external_network_bridge) + + def floating_ips_configured(self, router): + floating_ips = router.router[l3_constants.FLOATINGIP_KEY] + external_port = router.get_ex_gw_port() + return len(floating_ips) and all( + ip_lib.device_exists_with_ips_and_mac( + router.get_external_device_name(external_port['id']), + ['%s/32' % fip['floating_ip_address']], + external_port['mac_address'], + namespace=router.ns_name) for fip in floating_ips) + + def fail_ha_router(self, router): + device_name = router.get_ha_device_name() + ha_device = ip_lib.IPDevice(device_name, router.ha_namespace) + ha_device.link.set_down() + + @classmethod + def _get_addresses_on_device(cls, namespace, interface): + return [address['cidr'] for address in + ip_lib.IPDevice(interface, namespace=namespace).addr.list()] + + def _assert_no_ip_addresses_on_interface(self, namespace, interface): + self.assertEqual( + [], self._get_addresses_on_device(namespace, interface)) + + def _assert_ip_address_on_interface(self, + namespace, interface, ip_address): + self.assertIn( + ip_address, self._get_addresses_on_device(namespace, interface)) diff --git a/neutron/tests/functional/agent/l3/test_dvr_router.py b/neutron/tests/functional/agent/l3/test_dvr_router.py new file mode 100644 index 000000000..1cdb84bbd --- /dev/null +++ b/neutron/tests/functional/agent/l3/test_dvr_router.py @@ -0,0 +1,637 @@ +# Copyright (c) 2014 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. + +import functools + +import mock +import netaddr + +from neutron.agent.l3 import agent as neutron_l3_agent +from neutron.agent.l3 import dvr_snat_ns +from neutron.agent.l3 import namespaces +from neutron.agent.linux import ip_lib +from neutron.agent.linux import utils +from neutron.common import constants as l3_constants +from neutron.tests.common import l3_test_common +from neutron.tests.common import net_helpers +from neutron.tests.functional.agent.l3 import framework + + +DEVICE_OWNER_COMPUTE = l3_constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake' + + +class TestDvrRouter(framework.L3AgentTestFramework): + def test_dvr_router_lifecycle_without_ha_without_snat_with_fips(self): + self._dvr_router_lifecycle(enable_ha=False, enable_snat=False) + + def test_dvr_router_lifecycle_without_ha_with_snat_with_fips(self): + self._dvr_router_lifecycle(enable_ha=False, enable_snat=True) + + def test_dvr_router_lifecycle_ha_with_snat_with_fips(self): + self._dvr_router_lifecycle(enable_ha=True, enable_snat=True) + + def _helper_create_dvr_router_fips_for_ext_network( + self, agent_mode, **dvr_router_kwargs): + self.agent.conf.agent_mode = agent_mode + router_info = self.generate_dvr_router_info(**dvr_router_kwargs) + self.mock_plugin_api.get_external_network_id.return_value = ( + router_info['_floatingips'][0]['floating_network_id']) + router = self.manage_router(self.agent, router_info) + fip_ns = router.fip_ns.get_name() + return router, fip_ns + + def _validate_fips_for_external_network(self, router, fip_ns): + self.assertTrue(self._namespace_exists(router.ns_name)) + self.assertTrue(self._namespace_exists(fip_ns)) + self._assert_dvr_floating_ips(router) + self._assert_snat_namespace_does_not_exist(router) + + def test_dvr_router_fips_for_multiple_ext_networks(self): + agent_mode = 'dvr' + # Create the first router fip with external net1 + dvr_router1_kwargs = {'ip_address': '19.4.4.3', + 'subnet_cidr': '19.4.4.0/24', + 'gateway_ip': '19.4.4.1', + 'gateway_mac': 'ca:fe:de:ab:cd:ef'} + router1, fip1_ns = ( + self._helper_create_dvr_router_fips_for_ext_network( + agent_mode, **dvr_router1_kwargs)) + # Validate the fip with external net1 + self._validate_fips_for_external_network(router1, fip1_ns) + + # Create the second router fip with external net2 + dvr_router2_kwargs = {'ip_address': '19.4.5.3', + 'subnet_cidr': '19.4.5.0/24', + 'gateway_ip': '19.4.5.1', + 'gateway_mac': 'ca:fe:de:ab:cd:fe'} + router2, fip2_ns = ( + self._helper_create_dvr_router_fips_for_ext_network( + agent_mode, **dvr_router2_kwargs)) + # Validate the fip with external net2 + self._validate_fips_for_external_network(router2, fip2_ns) + + def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False, + custom_mtu=2000, + ip_version=4, + dual_stack=False): + '''Test dvr router lifecycle + + :param enable_ha: sets the ha value for the router. + :param enable_snat: the value of enable_snat is used + to set the agent_mode. + ''' + + # The value of agent_mode can be dvr, dvr_snat, or legacy. + # Since by definition this is a dvr (distributed = true) + # only dvr and dvr_snat are applicable + self.agent.conf.agent_mode = 'dvr_snat' if enable_snat else 'dvr' + self.agent.conf.network_device_mtu = custom_mtu + + # We get the router info particular to a dvr router + router_info = self.generate_dvr_router_info( + enable_ha, enable_snat) + + # We need to mock the get_agent_gateway_port return value + # because the whole L3PluginApi is mocked and we need the port + # gateway_port information before the l3_agent will create it. + # The port returned needs to have the same information as + # router_info['gw_port'] + self.mock_plugin_api.get_agent_gateway_port.return_value = router_info[ + 'gw_port'] + + # We also need to mock the get_external_network_id method to + # get the correct fip namespace. + self.mock_plugin_api.get_external_network_id.return_value = ( + router_info['_floatingips'][0]['floating_network_id']) + + # With all that set we can now ask the l3_agent to + # manage the router (create it, create namespaces, + # attach interfaces, etc...) + router = self.manage_router(self.agent, router_info) + if enable_ha: + port = router.get_ex_gw_port() + interface_name = router.get_external_device_name(port['id']) + self._assert_no_ip_addresses_on_interface(router.ha_namespace, + interface_name) + utils.wait_until_true(lambda: router.ha_state == 'master') + + # Keepalived notifies of a state transition when it starts, + # not when it ends. Thus, we have to wait until keepalived finishes + # configuring everything. We verify this by waiting until the last + # device has an IP address. + device = router.router[l3_constants.INTERFACE_KEY][-1] + device_exists = functools.partial( + self.device_exists_with_ips_and_mac, + device, + router.get_internal_device_name, + router.ns_name) + utils.wait_until_true(device_exists) + + ext_gateway_port = router_info['gw_port'] + self.assertTrue(self._namespace_exists(router.ns_name)) + utils.wait_until_true( + lambda: self._metadata_proxy_exists(self.agent.conf, router)) + self._assert_internal_devices(router) + self._assert_dvr_external_device(router) + self._assert_dvr_gateway(router) + self._assert_dvr_floating_ips(router) + self._assert_snat_chains(router) + self._assert_floating_ip_chains(router) + self._assert_metadata_chains(router) + self._assert_extra_routes(router) + self._assert_rfp_fpr_mtu(router, custom_mtu) + if enable_snat: + ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4] + snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + self._assert_onlink_subnet_routes( + router, ip_versions, snat_ns_name) + + self._delete_router(self.agent, router.router_id) + self._assert_fip_namespace_deleted(ext_gateway_port) + self._assert_router_does_not_exist(router) + self._assert_snat_namespace_does_not_exist(router) + + def generate_dvr_router_info(self, + enable_ha=False, + enable_snat=False, + agent=None, + **kwargs): + if not agent: + agent = self.agent + router = l3_test_common.prepare_router_data( + enable_snat=enable_snat, + enable_floating_ip=True, + enable_ha=enable_ha, + **kwargs) + internal_ports = router.get(l3_constants.INTERFACE_KEY, []) + router['distributed'] = True + router['gw_port_host'] = agent.conf.host + router['gw_port']['binding:host_id'] = agent.conf.host + floating_ip = router['_floatingips'][0] + floating_ip['floating_network_id'] = router['gw_port']['network_id'] + floating_ip['host'] = agent.conf.host + floating_ip['port_id'] = internal_ports[0]['id'] + floating_ip['status'] = 'ACTIVE' + + self._add_snat_port_info_to_router(router, internal_ports) + # FIP has a dependency on external gateway. So we need to create + # the snat_port info and fip_agent_gw_port_info irrespective of + # the agent type the dvr supports. The namespace creation is + # dependent on the agent_type. + external_gw_port = router['gw_port'] + self._add_fip_agent_gw_port_info_to_router(router, external_gw_port) + return router + + def _add_fip_agent_gw_port_info_to_router(self, router, external_gw_port): + # Add fip agent gateway port information to the router_info + fip_gw_port_list = router.get( + l3_constants.FLOATINGIP_AGENT_INTF_KEY, []) + if not fip_gw_port_list and external_gw_port: + # Get values from external gateway port + fixed_ip = external_gw_port['fixed_ips'][0] + float_subnet = external_gw_port['subnets'][0] + port_ip = fixed_ip['ip_address'] + # Pick an ip address which is not the same as port_ip + fip_gw_port_ip = str(netaddr.IPAddress(port_ip) + 5) + # Add floatingip agent gateway port info to router + prefixlen = netaddr.IPNetwork(float_subnet['cidr']).prefixlen + router[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = [ + {'subnets': [ + {'cidr': float_subnet['cidr'], + 'gateway_ip': float_subnet['gateway_ip'], + 'id': fixed_ip['subnet_id']}], + 'network_id': external_gw_port['network_id'], + 'device_owner': l3_constants.DEVICE_OWNER_AGENT_GW, + 'mac_address': 'fa:16:3e:80:8d:89', + 'binding:host_id': self.agent.conf.host, + 'fixed_ips': [{'subnet_id': fixed_ip['subnet_id'], + 'ip_address': fip_gw_port_ip, + 'prefixlen': prefixlen}], + 'id': framework._uuid(), + 'device_id': framework._uuid()} + ] + + def _add_snat_port_info_to_router(self, router, internal_ports): + # Add snat port information to the router + snat_port_list = router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) + if not snat_port_list and internal_ports: + # Get values from internal port + port = internal_ports[0] + fixed_ip = port['fixed_ips'][0] + snat_subnet = port['subnets'][0] + port_ip = fixed_ip['ip_address'] + # Pick an ip address which is not the same as port_ip + snat_ip = str(netaddr.IPAddress(port_ip) + 5) + # Add the info to router as the first snat port + # in the list of snat ports + prefixlen = netaddr.IPNetwork(snat_subnet['cidr']).prefixlen + router[l3_constants.SNAT_ROUTER_INTF_KEY] = [ + {'subnets': [ + {'cidr': snat_subnet['cidr'], + 'gateway_ip': snat_subnet['gateway_ip'], + 'id': fixed_ip['subnet_id']}], + 'network_id': port['network_id'], + 'device_owner': l3_constants.DEVICE_OWNER_ROUTER_SNAT, + 'mac_address': 'fa:16:3e:80:8d:89', + 'fixed_ips': [{'subnet_id': fixed_ip['subnet_id'], + 'ip_address': snat_ip, + 'prefixlen': prefixlen}], + 'id': framework._uuid(), + 'device_id': framework._uuid()} + ] + + def _assert_dvr_external_device(self, router): + external_port = router.get_ex_gw_port() + snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + + # if the agent is in dvr_snat mode, then we have to check + # that the correct ports and ip addresses exist in the + # snat_ns_name namespace + if self.agent.conf.agent_mode == 'dvr_snat': + device_exists = functools.partial( + self.device_exists_with_ips_and_mac, + external_port, + router.get_external_device_name, + snat_ns_name) + utils.wait_until_true(device_exists) + # if the agent is in dvr mode then the snat_ns_name namespace + # should not be present at all: + elif self.agent.conf.agent_mode == 'dvr': + self.assertFalse( + self._namespace_exists(snat_ns_name), + "namespace %s was found but agent is in dvr mode not dvr_snat" + % (str(snat_ns_name)) + ) + # if the agent is anything else the test is misconfigured + # we force a test failure with message + else: + self.assertTrue(False, " agent not configured for dvr or dvr_snat") + + def _assert_dvr_gateway(self, router): + gateway_expected_in_snat_namespace = ( + self.agent.conf.agent_mode == 'dvr_snat' + ) + if gateway_expected_in_snat_namespace: + self._assert_dvr_snat_gateway(router) + self._assert_removal_of_already_deleted_gateway_device(router) + + snat_namespace_should_not_exist = ( + self.agent.conf.agent_mode == 'dvr' + ) + if snat_namespace_should_not_exist: + self._assert_snat_namespace_does_not_exist(router) + + def _assert_dvr_snat_gateway(self, router): + namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + external_port = router.get_ex_gw_port() + external_device_name = router.get_external_device_name( + external_port['id']) + external_device = ip_lib.IPDevice(external_device_name, + namespace=namespace) + existing_gateway = ( + external_device.route.get_gateway().get('gateway')) + expected_gateway = external_port['subnets'][0]['gateway_ip'] + self.assertEqual(expected_gateway, existing_gateway) + + def _assert_removal_of_already_deleted_gateway_device(self, router): + namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + device = ip_lib.IPDevice("fakedevice", + namespace=namespace) + + # Assert that no exception is thrown for this case + self.assertIsNone(router._delete_gateway_device_if_exists( + device, "192.168.0.1", 0)) + + def _assert_snat_namespace_does_not_exist(self, router): + namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + self.assertFalse(self._namespace_exists(namespace)) + + def _assert_dvr_floating_ips(self, router): + # in the fip namespace: + # Check that the fg- (floatingip_agent_gateway) + # is created with the ip address of the external gateway port + floating_ips = router.router[l3_constants.FLOATINGIP_KEY] + self.assertTrue(floating_ips) + # We need to fetch the floatingip agent gateway port info + # from the router_info + floating_agent_gw_port = ( + router.router[l3_constants.FLOATINGIP_AGENT_INTF_KEY]) + self.assertTrue(floating_agent_gw_port) + + external_gw_port = floating_agent_gw_port[0] + fip_ns = self.agent.get_fip_ns(floating_ips[0]['floating_network_id']) + fip_ns_name = fip_ns.get_name() + fg_port_created_successfully = ip_lib.device_exists_with_ips_and_mac( + fip_ns.get_ext_device_name(external_gw_port['id']), + [self._port_first_ip_cidr(external_gw_port)], + external_gw_port['mac_address'], + namespace=fip_ns_name) + self.assertTrue(fg_port_created_successfully) + # Check fpr-router device has been created + device_name = fip_ns.get_int_device_name(router.router_id) + fpr_router_device_created_successfully = ip_lib.device_exists( + device_name, namespace=fip_ns_name) + self.assertTrue(fpr_router_device_created_successfully) + + # In the router namespace + # Check rfp- is created correctly + for fip in floating_ips: + device_name = fip_ns.get_rtr_ext_device_name(router.router_id) + self.assertTrue(ip_lib.device_exists( + device_name, namespace=router.ns_name)) + + def test_dvr_router_rem_fips_on_restarted_agent(self): + self.agent.conf.agent_mode = 'dvr_snat' + router_info = self.generate_dvr_router_info() + router1 = self.manage_router(self.agent, router_info) + fip_ns = router1.fip_ns.get_name() + self.assertTrue(self._namespace_exists(fip_ns)) + restarted_agent = neutron_l3_agent.L3NATAgentWithStateReport( + self.agent.host, self.agent.conf) + router1.router[l3_constants.FLOATINGIP_KEY] = [] + self.manage_router(restarted_agent, router1.router) + self._assert_dvr_snat_gateway(router1) + self.assertTrue(self._namespace_exists(fip_ns)) + + def test_dvr_router_add_fips_on_restarted_agent(self): + self.agent.conf.agent_mode = 'dvr' + router_info = self.generate_dvr_router_info() + router = self.manage_router(self.agent, router_info) + floating_ips = router.router[l3_constants.FLOATINGIP_KEY] + router_ns = router.ns_name + fip_rule_prio_1 = self._get_fixed_ip_rule_priority( + router_ns, floating_ips[0]['fixed_ip_address']) + restarted_agent = neutron_l3_agent.L3NATAgent( + self.agent.host, self.agent.conf) + floating_ips[0]['floating_ip_address'] = '21.4.4.2' + floating_ips[0]['fixed_ip_address'] = '10.0.0.2' + self.manage_router(restarted_agent, router_info) + fip_rule_prio_2 = self._get_fixed_ip_rule_priority( + router_ns, floating_ips[0]['fixed_ip_address']) + self.assertNotEqual(fip_rule_prio_1, fip_rule_prio_2) + + def _get_fixed_ip_rule_priority(self, namespace, fip): + iprule = ip_lib.IPRule(namespace) + lines = iprule.rule._as_root([4], ['show']).splitlines() + for line in lines: + if fip in line: + info = iprule.rule._parse_line(4, line) + return info['priority'] + + def test_dvr_router_add_internal_network_set_arp_cache(self): + # Check that, when the router is set up and there are + # existing ports on the uplinked subnet, the ARP + # cache is properly populated. + self.agent.conf.agent_mode = 'dvr_snat' + router_info = l3_test_common.prepare_router_data() + router_info['distributed'] = True + expected_neighbor = '35.4.1.10' + port_data = { + 'fixed_ips': [{'ip_address': expected_neighbor}], + 'mac_address': 'fa:3e:aa:bb:cc:dd', + 'device_owner': DEVICE_OWNER_COMPUTE + } + self.agent.plugin_rpc.get_ports_by_subnet.return_value = [port_data] + router1 = self.manage_router(self.agent, router_info) + internal_device = router1.get_internal_device_name( + router_info['_interfaces'][0]['id']) + neighbors = ip_lib.IPDevice(internal_device, router1.ns_name).neigh + self.assertEqual(expected_neighbor, + neighbors.show(ip_version=4).split()[0]) + + def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500): + dev_mtu = self.get_device_mtu( + router.router_id, router.fip_ns.get_rtr_ext_device_name, + router.ns_name) + self.assertEqual(expected_mtu, dev_mtu) + dev_mtu = self.get_device_mtu( + router.router_id, router.fip_ns.get_int_device_name, + router.fip_ns.get_name()) + self.assertEqual(expected_mtu, dev_mtu) + + def test_dvr_router_fip_agent_mismatch(self): + """Test to validate the floatingip agent mismatch. + + This test validates the condition where floatingip agent + gateway port host mismatches with the agent and so the + binding will not be there. + + """ + self.agent.conf.agent_mode = 'dvr' + router_info = self.generate_dvr_router_info() + floating_ip = router_info['_floatingips'][0] + floating_ip['host'] = 'my_new_host' + # In this case the floatingip binding is different and so it + # should not create the floatingip namespace on the given agent. + # This is also like there is no current binding. + router1 = self.manage_router(self.agent, router_info) + fip_ns = router1.fip_ns.get_name() + self.assertTrue(self._namespace_exists(router1.ns_name)) + self.assertFalse(self._namespace_exists(fip_ns)) + self._assert_snat_namespace_does_not_exist(router1) + + def test_dvr_router_fip_late_binding(self): + """Test to validate the floatingip migration or latebinding. + + This test validates the condition where floatingip private + port changes while migration or when the private port host + binding is done later after floatingip association. + + """ + self.agent.conf.agent_mode = 'dvr' + router_info = self.generate_dvr_router_info() + fip_agent_gw_port = router_info[l3_constants.FLOATINGIP_AGENT_INTF_KEY] + # Now let us not pass the FLOATINGIP_AGENT_INTF_KEY, to emulate + # that the server did not create the port, since there was no valid + # host binding. + router_info[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = [] + self.mock_plugin_api.get_agent_gateway_port.return_value = ( + fip_agent_gw_port[0]) + router1 = self.manage_router(self.agent, router_info) + fip_ns = router1.fip_ns.get_name() + self.assertTrue(self._namespace_exists(router1.ns_name)) + self.assertTrue(self._namespace_exists(fip_ns)) + self._assert_snat_namespace_does_not_exist(router1) + + def _assert_snat_namespace_exists(self, router): + namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + self.assertTrue(self._namespace_exists(namespace)) + + def _get_dvr_snat_namespace_device_status( + self, router, internal_dev_name=None): + """Function returns the internal and external device status.""" + snat_ns = dvr_snat_ns.SnatNamespace.get_snat_ns_name( + router.router_id) + external_port = router.get_ex_gw_port() + external_device_name = router.get_external_device_name( + external_port['id']) + qg_device_created_successfully = ip_lib.device_exists( + external_device_name, namespace=snat_ns) + sg_device_created_successfully = ip_lib.device_exists( + internal_dev_name, namespace=snat_ns) + return qg_device_created_successfully, sg_device_created_successfully + + def test_dvr_router_snat_namespace_with_interface_remove(self): + """Test to validate the snat namespace with interface remove. + + This test validates the snat namespace for all the external + and internal devices. It also validates if the internal + device corresponding to the router interface is removed + when the router interface is deleted. + """ + self.agent.conf.agent_mode = 'dvr_snat' + router_info = self.generate_dvr_router_info() + snat_internal_port = router_info[l3_constants.SNAT_ROUTER_INTF_KEY] + router1 = self.manage_router(self.agent, router_info) + csnat_internal_port = ( + router1.router[l3_constants.SNAT_ROUTER_INTF_KEY]) + # Now save the internal device name to verify later + internal_device_name = router1._get_snat_int_device_name( + csnat_internal_port[0]['id']) + self._assert_snat_namespace_exists(router1) + qg_device, sg_device = self._get_dvr_snat_namespace_device_status( + router1, internal_dev_name=internal_device_name) + self.assertTrue(qg_device) + self.assertTrue(sg_device) + self.assertEqual(router1.snat_ports, snat_internal_port) + # Now let us not pass INTERFACE_KEY, to emulate + # the interface has been removed. + router1.router[l3_constants.INTERFACE_KEY] = [] + # Now let us not pass the SNAT_ROUTER_INTF_KEY, to emulate + # that the server did not send it, since the interface has been + # removed. + router1.router[l3_constants.SNAT_ROUTER_INTF_KEY] = [] + self.agent._process_updated_router(router1.router) + router_updated = self.agent.router_info[router_info['id']] + self._assert_snat_namespace_exists(router_updated) + qg_device, sg_device = self._get_dvr_snat_namespace_device_status( + router_updated, internal_dev_name=internal_device_name) + self.assertFalse(sg_device) + self.assertTrue(qg_device) + + def _mocked_dvr_ha_router(self, agent): + r_info = self.generate_dvr_router_info(enable_ha=True, + enable_snat=True, + agent=agent) + + r_snat_ns_name = namespaces.build_ns_name(dvr_snat_ns.SNAT_NS_PREFIX, + r_info['id']) + + mocked_r_snat_ns_name = r_snat_ns_name + '@' + agent.host + r_ns_name = namespaces.build_ns_name(namespaces.NS_PREFIX, + r_info['id']) + + mocked_r_ns_name = r_ns_name + '@' + agent.host + + return r_info, mocked_r_ns_name, mocked_r_snat_ns_name + + def _setup_dvr_ha_agents(self): + self.agent.conf.agent_mode = 'dvr_snat' + + conf = self._configure_agent('agent2') + self.failover_agent = neutron_l3_agent.L3NATAgentWithStateReport( + 'agent2', conf) + self.failover_agent.conf.agent_mode = 'dvr_snat' + + def _setup_dvr_ha_bridges(self): + br_int_1 = self._get_agent_ovs_integration_bridge(self.agent) + br_int_2 = self._get_agent_ovs_integration_bridge(self.failover_agent) + + veth1, veth2 = self.useFixture(net_helpers.VethFixture()).ports + br_int_1.add_port(veth1.name) + br_int_2.add_port(veth2.name) + + def _create_dvr_ha_router(self, agent): + get_ns_name = mock.patch.object(namespaces.RouterNamespace, + '_get_ns_name').start() + get_snat_ns_name = mock.patch.object(dvr_snat_ns.SnatNamespace, + 'get_snat_ns_name').start() + (r_info, + mocked_r_ns_name, + mocked_r_snat_ns_name) = self._mocked_dvr_ha_router(agent) + get_ns_name.return_value = mocked_r_ns_name + get_snat_ns_name.return_value = mocked_r_snat_ns_name + router = self.manage_router(agent, r_info) + return router + + def _assert_ip_addresses_in_dvr_ha_snat_namespace(self, router): + namespace = router.ha_namespace + ex_gw_port = router.get_ex_gw_port() + snat_port = router.get_snat_interfaces()[0] + ex_gw_port_name = router.get_external_device_name( + ex_gw_port['id']) + snat_port_name = router._get_snat_int_device_name( + snat_port['id']) + + ip = ex_gw_port["fixed_ips"][0]['ip_address'] + prefix_len = ex_gw_port["fixed_ips"][0]['prefixlen'] + ex_gw_port_cidr = ip + "/" + str(prefix_len) + ip = snat_port["fixed_ips"][0]['ip_address'] + prefix_len = snat_port["fixed_ips"][0]['prefixlen'] + snat_port_cidr = ip + "/" + str(prefix_len) + + self._assert_ip_address_on_interface(namespace, + ex_gw_port_name, + ex_gw_port_cidr) + self._assert_ip_address_on_interface(namespace, + snat_port_name, + snat_port_cidr) + + def _assert_no_ip_addresses_in_dvr_ha_snat_namespace(self, router): + namespace = router.ha_namespace + ex_gw_port = router.get_ex_gw_port() + snat_port = router.get_snat_interfaces()[0] + ex_gw_port_name = router.get_external_device_name( + ex_gw_port['id']) + snat_port_name = router._get_snat_int_device_name( + snat_port['id']) + + self._assert_no_ip_addresses_on_interface(namespace, + snat_port_name) + self._assert_no_ip_addresses_on_interface(namespace, + ex_gw_port_name) + + def test_dvr_ha_router_failover(self): + self._setup_dvr_ha_agents() + self._setup_dvr_ha_bridges() + + router1 = self._create_dvr_ha_router(self.agent) + router2 = self._create_dvr_ha_router(self.failover_agent) + + utils.wait_until_true(lambda: router1.ha_state == 'master') + utils.wait_until_true(lambda: router2.ha_state == 'backup') + + self._assert_ip_addresses_in_dvr_ha_snat_namespace(router1) + self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router2) + + self.fail_ha_router(router1) + + utils.wait_until_true(lambda: router2.ha_state == 'master') + utils.wait_until_true(lambda: router1.ha_state == 'backup') + + self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2) + self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router1) + + def _assert_fip_namespace_deleted(self, ext_gateway_port): + ext_net_id = ext_gateway_port['network_id'] + self.agent.fipnamespace_delete_on_ext_net( + self.agent.context, ext_net_id) + self._assert_interfaces_deleted_from_ovs() diff --git a/neutron/tests/functional/agent/l3/test_ha_router.py b/neutron/tests/functional/agent/l3/test_ha_router.py new file mode 100644 index 000000000..1bdffa6e2 --- /dev/null +++ b/neutron/tests/functional/agent/l3/test_ha_router.py @@ -0,0 +1,284 @@ +# Copyright (c) 2014 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. + +import copy + +import mock +import six + +from neutron.agent.l3 import agent as neutron_l3_agent +from neutron.agent.l3 import namespaces +from neutron.agent.linux import ip_lib +from neutron.agent.linux import utils +from neutron.common import constants as l3_constants +from neutron.common import utils as common_utils +from neutron.tests.common import l3_test_common +from neutron.tests.common import net_helpers +from neutron.tests.functional.agent.l3 import framework + + +class L3HATestCase(framework.L3AgentTestFramework): + + def test_keepalived_state_change_notification(self): + enqueue_mock = mock.patch.object( + self.agent, 'enqueue_state_change').start() + router_info = self.generate_router_info(enable_ha=True) + router = self.manage_router(self.agent, router_info) + utils.wait_until_true(lambda: router.ha_state == 'master') + + self.fail_ha_router(router) + utils.wait_until_true(lambda: router.ha_state == 'backup') + + utils.wait_until_true(lambda: enqueue_mock.call_count == 3) + calls = [args[0] for args in enqueue_mock.call_args_list] + self.assertEqual((router.router_id, 'backup'), calls[0]) + self.assertEqual((router.router_id, 'master'), calls[1]) + self.assertEqual((router.router_id, 'backup'), calls[2]) + + def _expected_rpc_report(self, expected): + calls = (args[0][1] for args in + self.agent.plugin_rpc.update_ha_routers_states.call_args_list) + + # Get the last state reported for each router + actual_router_states = {} + for call in calls: + for router_id, state in six.iteritems(call): + actual_router_states[router_id] = state + + return actual_router_states == expected + + def test_keepalived_state_change_bulk_rpc(self): + router_info = self.generate_router_info(enable_ha=True) + router1 = self.manage_router(self.agent, router_info) + self.fail_ha_router(router1) + router_info = self.generate_router_info(enable_ha=True) + router2 = self.manage_router(self.agent, router_info) + + utils.wait_until_true(lambda: router1.ha_state == 'backup') + utils.wait_until_true(lambda: router2.ha_state == 'master') + utils.wait_until_true( + lambda: self._expected_rpc_report( + {router1.router_id: 'standby', router2.router_id: 'active'})) + + def test_ha_router_lifecycle(self): + self._router_lifecycle(enable_ha=True) + + def test_conntrack_disassociate_fip_ha_router(self): + self._test_conntrack_disassociate_fip(ha=True) + + def test_ipv6_ha_router_lifecycle(self): + self._router_lifecycle(enable_ha=True, ip_version=6) + + def test_ipv6_ha_router_lifecycle_with_no_gw_subnet(self): + self.agent.conf.set_override('ipv6_gateway', + 'fe80::f816:3eff:fe2e:1') + self._router_lifecycle(enable_ha=True, ip_version=6, + v6_ext_gw_with_sub=False) + + def test_ipv6_ha_router_lifecycle_with_no_gw_subnet_for_router_advts(self): + # Verify that router gw interface is configured to receive Router + # Advts from upstream router when no external gateway is configured. + self._router_lifecycle(enable_ha=True, dual_stack=True, + v6_ext_gw_with_sub=False) + + def test_keepalived_configuration(self): + router_info = self.generate_router_info(enable_ha=True) + router = self.manage_router(self.agent, router_info) + expected = self.get_expected_keepalive_configuration(router) + + self.assertEqual(expected, + router.keepalived_manager.get_conf_on_disk()) + + # Add a new FIP and change the GW IP address + router.router = copy.deepcopy(router.router) + existing_fip = '19.4.4.2' + new_fip = '19.4.4.3' + self._add_fip(router, new_fip) + subnet_id = framework._uuid() + fixed_ips = [{'ip_address': '19.4.4.10', + 'prefixlen': 24, + 'subnet_id': subnet_id}] + subnets = [{'id': subnet_id, + 'cidr': '19.4.4.0/24', + 'gateway_ip': '19.4.4.5'}] + router.router['gw_port']['subnets'] = subnets + router.router['gw_port']['fixed_ips'] = fixed_ips + + router.process(self.agent) + + # Get the updated configuration and assert that both FIPs are in, + # and that the GW IP address was updated. + new_config = router.keepalived_manager.config.get_config_str() + old_gw = '0.0.0.0/0 via 19.4.4.1' + new_gw = '0.0.0.0/0 via 19.4.4.5' + old_external_device_ip = '19.4.4.4' + new_external_device_ip = '19.4.4.10' + self.assertIn(existing_fip, new_config) + self.assertIn(new_fip, new_config) + self.assertNotIn(old_gw, new_config) + self.assertIn(new_gw, new_config) + external_port = router.get_ex_gw_port() + external_device_name = router.get_external_device_name( + external_port['id']) + self.assertNotIn('%s/24 dev %s' % + (old_external_device_ip, external_device_name), + new_config) + self.assertIn('%s/24 dev %s' % + (new_external_device_ip, external_device_name), + new_config) + + def test_ha_router_conf_on_restarted_agent(self): + router_info = self.generate_router_info(enable_ha=True) + router1 = self.manage_router(self.agent, router_info) + self._add_fip(router1, '192.168.111.12') + restarted_agent = neutron_l3_agent.L3NATAgentWithStateReport( + self.agent.host, self.agent.conf) + self.manage_router(restarted_agent, router1.router) + utils.wait_until_true(lambda: self.floating_ips_configured(router1)) + self.assertIn( + router1._get_primary_vip(), + self._get_addresses_on_device( + router1.ns_name, + router1.get_ha_device_name())) + + def test_ha_router_ipv6_radvd_status(self): + router_info = self.generate_router_info(ip_version=6, enable_ha=True) + router1 = self.manage_router(self.agent, router_info) + utils.wait_until_true(lambda: router1.ha_state == 'master') + utils.wait_until_true(lambda: router1.radvd.enabled) + + def _check_lla_status(router, expected): + internal_devices = router.router[l3_constants.INTERFACE_KEY] + for device in internal_devices: + lladdr = ip_lib.get_ipv6_lladdr(device['mac_address']) + exists = ip_lib.device_exists_with_ips_and_mac( + router.get_internal_device_name(device['id']), [lladdr], + device['mac_address'], router.ns_name) + self.assertEqual(expected, exists) + + _check_lla_status(router1, True) + + device_name = router1.get_ha_device_name() + ha_device = ip_lib.IPDevice(device_name, namespace=router1.ns_name) + ha_device.link.set_down() + + utils.wait_until_true(lambda: router1.ha_state == 'backup') + utils.wait_until_true(lambda: not router1.radvd.enabled, timeout=10) + _check_lla_status(router1, False) + + def test_ha_router_process_ipv6_subnets_to_existing_port(self): + router_info = self.generate_router_info(enable_ha=True, ip_version=6) + router = self.manage_router(self.agent, router_info) + + def verify_ip_in_keepalived_config(router, iface): + config = router.keepalived_manager.config.get_config_str() + ip_cidrs = common_utils.fixed_ip_cidrs(iface['fixed_ips']) + for ip_addr in ip_cidrs: + self.assertIn(ip_addr, config) + + interface_id = router.router[l3_constants.INTERFACE_KEY][0]['id'] + slaac = l3_constants.IPV6_SLAAC + slaac_mode = {'ra_mode': slaac, 'address_mode': slaac} + + # Add a second IPv6 subnet to the router internal interface. + self._add_internal_interface_by_subnet(router.router, count=1, + ip_version=6, ipv6_subnet_modes=[slaac_mode], + interface_id=interface_id) + router.process(self.agent) + utils.wait_until_true(lambda: router.ha_state == 'master') + + # Verify that router internal interface is present and is configured + # with IP address from both the subnets. + internal_iface = router.router[l3_constants.INTERFACE_KEY][0] + self.assertEqual(2, len(internal_iface['fixed_ips'])) + self._assert_internal_devices(router) + + # Verify that keepalived config is properly updated. + verify_ip_in_keepalived_config(router, internal_iface) + + # Remove one subnet from the router internal iface + interfaces = copy.deepcopy(router.router.get( + l3_constants.INTERFACE_KEY, [])) + fixed_ips, subnets = [], [] + fixed_ips.append(interfaces[0]['fixed_ips'][0]) + subnets.append(interfaces[0]['subnets'][0]) + interfaces[0].update({'fixed_ips': fixed_ips, 'subnets': subnets}) + router.router[l3_constants.INTERFACE_KEY] = interfaces + router.process(self.agent) + + # Verify that router internal interface has a single ipaddress + internal_iface = router.router[l3_constants.INTERFACE_KEY][0] + self.assertEqual(1, len(internal_iface['fixed_ips'])) + self._assert_internal_devices(router) + + # Verify that keepalived config is properly updated. + verify_ip_in_keepalived_config(router, internal_iface) + + def test_delete_external_gateway_on_standby_router(self): + router_info = self.generate_router_info(enable_ha=True) + router = self.manage_router(self.agent, router_info) + + self.fail_ha_router(router) + utils.wait_until_true(lambda: router.ha_state == 'backup') + + # The purpose of the test is to simply make sure no exception is raised + port = router.get_ex_gw_port() + interface_name = router.get_external_device_name(port['id']) + router.external_gateway_removed(port, interface_name) + + +class L3HATestFailover(framework.L3AgentTestFramework): + + NESTED_NAMESPACE_SEPARATOR = '@' + + def setUp(self): + super(L3HATestFailover, self).setUp() + conf = self._configure_agent('agent2') + self.failover_agent = neutron_l3_agent.L3NATAgentWithStateReport( + 'agent2', conf) + + br_int_1 = self._get_agent_ovs_integration_bridge(self.agent) + br_int_2 = self._get_agent_ovs_integration_bridge(self.failover_agent) + + veth1, veth2 = self.useFixture(net_helpers.VethFixture()).ports + br_int_1.add_port(veth1.name) + br_int_2.add_port(veth2.name) + + def test_ha_router_failover(self): + router_info = self.generate_router_info(enable_ha=True) + get_ns_name = mock.patch.object( + namespaces.RouterNamespace, '_get_ns_name').start() + get_ns_name.return_value = "%s%s%s" % ( + 'qrouter-' + router_info['id'], + self.NESTED_NAMESPACE_SEPARATOR, self.agent.host) + router1 = self.manage_router(self.agent, router_info) + + router_info_2 = copy.deepcopy(router_info) + router_info_2[l3_constants.HA_INTERFACE_KEY] = ( + l3_test_common.get_ha_interface(ip='169.254.192.2', + mac='22:22:22:22:22:22')) + + get_ns_name.return_value = "%s%s%s" % ( + namespaces.RouterNamespace._get_ns_name(router_info_2['id']), + self.NESTED_NAMESPACE_SEPARATOR, self.failover_agent.host) + router2 = self.manage_router(self.failover_agent, router_info_2) + + utils.wait_until_true(lambda: router1.ha_state == 'master') + utils.wait_until_true(lambda: router2.ha_state == 'backup') + + self.fail_ha_router(router1) + + utils.wait_until_true(lambda: router2.ha_state == 'master') + utils.wait_until_true(lambda: router1.ha_state == 'backup') diff --git a/neutron/tests/functional/agent/l3/test_legacy_router.py b/neutron/tests/functional/agent/l3/test_legacy_router.py new file mode 100644 index 000000000..2f075970d --- /dev/null +++ b/neutron/tests/functional/agent/l3/test_legacy_router.py @@ -0,0 +1,199 @@ +# Copyright (c) 2014 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. + + +import mock + +from neutron.agent.l3 import namespace_manager +from neutron.agent.l3 import namespaces +from neutron.callbacks import events +from neutron.callbacks import registry +from neutron.callbacks import resources +from neutron.common import constants as l3_constants +from neutron.tests.common import machine_fixtures +from neutron.tests.common import net_helpers +from neutron.tests.functional.agent.l3 import framework + + +class L3AgentTestCase(framework.L3AgentTestFramework): + + def test_agent_notifications_for_router_events(self): + """Test notifications for router create, update, and delete. + + Make sure that when the agent sends notifications of router events + for router create, update, and delete, that the correct handler is + called with the right resource, event, and router information. + """ + event_handler = mock.Mock() + registry.subscribe(event_handler, + resources.ROUTER, events.BEFORE_CREATE) + registry.subscribe(event_handler, + resources.ROUTER, events.AFTER_CREATE) + registry.subscribe(event_handler, + resources.ROUTER, events.BEFORE_UPDATE) + registry.subscribe(event_handler, + resources.ROUTER, events.AFTER_UPDATE) + registry.subscribe(event_handler, + resources.ROUTER, events.BEFORE_DELETE) + registry.subscribe(event_handler, + resources.ROUTER, events.AFTER_DELETE) + + router_info = self.generate_router_info(enable_ha=False) + router = self.manage_router(self.agent, router_info) + self.agent._process_updated_router(router.router) + self._delete_router(self.agent, router.router_id) + + expected_calls = [ + mock.call('router', 'before_create', self.agent, router=router), + mock.call('router', 'after_create', self.agent, router=router), + mock.call('router', 'before_update', self.agent, router=router), + mock.call('router', 'after_update', self.agent, router=router), + mock.call('router', 'before_delete', self.agent, router=router), + mock.call('router', 'after_delete', self.agent, router=router)] + event_handler.assert_has_calls(expected_calls) + + def test_legacy_router_lifecycle(self): + self._router_lifecycle(enable_ha=False, dual_stack=True) + + def test_legacy_router_lifecycle_with_no_gateway_subnet(self): + self.agent.conf.set_override('ipv6_gateway', + 'fe80::f816:3eff:fe2e:1') + self._router_lifecycle(enable_ha=False, dual_stack=True, + v6_ext_gw_with_sub=False) + + def test_conntrack_disassociate_fip_legacy_router(self): + self._test_conntrack_disassociate_fip(ha=False) + + def _test_periodic_sync_routers_task(self, + routers_to_keep, + routers_deleted, + routers_deleted_during_resync): + ns_names_to_retrieve = set() + deleted_routers_info = [] + for r in routers_to_keep: + ri = self.manage_router(self.agent, r) + ns_names_to_retrieve.add(ri.ns_name) + for r in routers_deleted + routers_deleted_during_resync: + ri = self.manage_router(self.agent, r) + deleted_routers_info.append(ri) + ns_names_to_retrieve.add(ri.ns_name) + + mocked_get_routers = self.mock_plugin_api.get_routers + mocked_get_routers.return_value = (routers_to_keep + + routers_deleted_during_resync) + # clear agent router_info as it will be after restart + self.agent.router_info = {} + + # Synchronize the agent with the plug-in + with mock.patch.object(namespace_manager.NamespaceManager, 'list_all', + return_value=ns_names_to_retrieve): + self.agent.periodic_sync_routers_task(self.agent.context) + + # Mock the plugin RPC API so a known external network id is returned + # when the router updates are processed by the agent + external_network_id = framework._uuid() + self.mock_plugin_api.get_external_network_id.return_value = ( + external_network_id) + + # Plug external_gateway_info in the routers that are not going to be + # deleted by the agent when it processes the updates. Otherwise, + # _process_router_if_compatible in the agent fails + for r in routers_to_keep: + r['external_gateway_info'] = {'network_id': external_network_id} + + # while sync updates are still in the queue, higher priority + # router_deleted events may be added there as well + for r in routers_deleted_during_resync: + self.agent.router_deleted(self.agent.context, r['id']) + + # make sure all events are processed + while not self.agent._queue._queue.empty(): + self.agent._process_router_update() + + for r in routers_to_keep: + self.assertIn(r['id'], self.agent.router_info) + self.assertTrue(self._namespace_exists(namespaces.NS_PREFIX + + r['id'])) + for ri in deleted_routers_info: + self.assertNotIn(ri.router_id, + self.agent.router_info) + self._assert_router_does_not_exist(ri) + + def test_periodic_sync_routers_task(self): + routers_to_keep = [] + for i in range(2): + routers_to_keep.append(self.generate_router_info(False)) + self._test_periodic_sync_routers_task(routers_to_keep, + routers_deleted=[], + routers_deleted_during_resync=[]) + + def test_periodic_sync_routers_task_routers_deleted_while_agent_down(self): + routers_to_keep = [] + routers_deleted = [] + for i in range(2): + routers_to_keep.append(self.generate_router_info(False)) + for i in range(2): + routers_deleted.append(self.generate_router_info(False)) + self._test_periodic_sync_routers_task(routers_to_keep, + routers_deleted, + routers_deleted_during_resync=[]) + + def test_periodic_sync_routers_task_routers_deleted_while_agent_sync(self): + routers_to_keep = [] + routers_deleted_during_resync = [] + for i in range(2): + routers_to_keep.append(self.generate_router_info(False)) + for i in range(2): + routers_deleted_during_resync.append( + self.generate_router_info(False)) + self._test_periodic_sync_routers_task( + routers_to_keep, + routers_deleted=[], + routers_deleted_during_resync=routers_deleted_during_resync) + + def test_fip_connection_from_same_subnet(self): + '''Test connection to floatingip which is associated with + fixed_ip on the same subnet of the source fixed_ip. + In other words it confirms that return packets surely + go through the router. + ''' + router_info = self.generate_router_info(enable_ha=False) + router = self.manage_router(self.agent, router_info) + router_ip_cidr = self._port_first_ip_cidr(router.internal_ports[0]) + router_ip = router_ip_cidr.partition('/')[0] + + br_int = framework.get_ovs_bridge( + self.agent.conf.ovs_integration_bridge) + + src_machine, dst_machine = self.useFixture( + machine_fixtures.PeerMachines( + br_int, + net_helpers.increment_ip_cidr(router_ip_cidr), + router_ip)).machines + + dst_fip = '19.4.4.10' + router.router[l3_constants.FLOATINGIP_KEY] = [] + self._add_fip(router, dst_fip, fixed_address=dst_machine.ip) + router.process(self.agent) + + protocol_port = net_helpers.get_free_namespace_port( + l3_constants.PROTO_NAME_TCP, dst_machine.namespace) + # client sends to fip + netcat = net_helpers.NetcatTester( + src_machine.namespace, dst_machine.namespace, + dst_fip, protocol_port, + protocol=net_helpers.NetcatTester.TCP) + self.addCleanup(netcat.stop_processes) + self.assertTrue(netcat.test_connectivity()) diff --git a/neutron/tests/functional/agent/l3/test_metadata_proxy.py b/neutron/tests/functional/agent/l3/test_metadata_proxy.py new file mode 100644 index 000000000..fb0aa973f --- /dev/null +++ b/neutron/tests/functional/agent/l3/test_metadata_proxy.py @@ -0,0 +1,152 @@ +# 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 os.path +import time + +import webob +import webob.dec +import webob.exc + +from neutron.agent.linux import dhcp +from neutron.agent.linux import utils +from neutron.tests.common import machine_fixtures +from neutron.tests.common import net_helpers +from neutron.tests.functional.agent.l3 import framework +from neutron.tests.functional.agent.linux import helpers + + +METADATA_REQUEST_TIMEOUT = 60 +METADATA_REQUEST_SLEEP = 5 + + +class MetadataFakeProxyHandler(object): + + def __init__(self, status): + self.status = status + + @webob.dec.wsgify() + def __call__(self, req): + return webob.Response(status=self.status) + + +class MetadataL3AgentTestCase(framework.L3AgentTestFramework): + + SOCKET_MODE = 0o644 + + def _create_metadata_fake_server(self, status): + server = utils.UnixDomainWSGIServer('metadata-fake-server') + self.addCleanup(server.stop) + + # NOTE(cbrandily): TempDir fixture creates a folder with 0o700 + # permissions but metadata_proxy_socket folder must be readable by all + # users + self.useFixture( + helpers.RecursivePermDirFixture( + os.path.dirname(self.agent.conf.metadata_proxy_socket), 0o555)) + server.start(MetadataFakeProxyHandler(status), + self.agent.conf.metadata_proxy_socket, + workers=0, backlog=4096, mode=self.SOCKET_MODE) + + def _query_metadata_proxy(self, machine): + url = 'http://%(host)s:%(port)s' % {'host': dhcp.METADATA_DEFAULT_IP, + 'port': dhcp.METADATA_PORT} + cmd = 'curl', '--max-time', METADATA_REQUEST_TIMEOUT, '-D-', url + i = 0 + CONNECTION_REFUSED_TIMEOUT = METADATA_REQUEST_TIMEOUT // 2 + while i <= CONNECTION_REFUSED_TIMEOUT: + try: + raw_headers = machine.execute(cmd) + break + except RuntimeError as e: + if 'Connection refused' in str(e): + time.sleep(METADATA_REQUEST_SLEEP) + i += METADATA_REQUEST_SLEEP + else: + self.fail('metadata proxy unreachable ' + 'on %s before timeout' % url) + + if i > CONNECTION_REFUSED_TIMEOUT: + self.fail('Timed out waiting metadata proxy to become available') + return raw_headers.splitlines()[0] + + def test_access_to_metadata_proxy(self): + """Test access to the l3-agent metadata proxy. + + The test creates: + * A l3-agent metadata service: + * A router (which creates a metadata proxy in the router namespace), + * A fake metadata server + * A "client" namespace (simulating a vm) with a port on router + internal subnet. + + The test queries from the "client" namespace the metadata proxy on + http://169.254.169.254 and asserts that the metadata proxy added + the X-Forwarded-For and X-Neutron-Router-Id headers to the request + and forwarded the http request to the fake metadata server and the + response to the "client" namespace. + """ + router_info = self.generate_router_info(enable_ha=False) + router = self.manage_router(self.agent, router_info) + self._create_metadata_fake_server(webob.exc.HTTPOk.code) + + # Create and configure client namespace + router_ip_cidr = self._port_first_ip_cidr(router.internal_ports[0]) + br_int = framework.get_ovs_bridge( + self.agent.conf.ovs_integration_bridge) + + machine = self.useFixture( + machine_fixtures.FakeMachine( + br_int, + net_helpers.increment_ip_cidr(router_ip_cidr), + router_ip_cidr.partition('/')[0])) + + # Query metadata proxy + firstline = self._query_metadata_proxy(machine) + + # Check status code + self.assertIn(str(webob.exc.HTTPOk.code), firstline.split()) + + +class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase): + """Test metadata proxy with least privileged user. + + The least privileged user has uid=65534 and is commonly named 'nobody' but + not always, that's why we use its uid. + """ + + SOCKET_MODE = 0o664 + + def setUp(self): + super(UnprivilegedUserMetadataL3AgentTestCase, self).setUp() + self.agent.conf.set_override('metadata_proxy_user', '65534') + self.agent.conf.set_override('metadata_proxy_watch_log', False) + + +class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase): + """Test metadata proxy with least privileged user/group. + + The least privileged user has uid=65534 and is commonly named 'nobody' but + not always, that's why we use its uid. + Its group has gid=65534 and is commonly named 'nobody' or 'nogroup', that's + why we use its gid. + """ + + SOCKET_MODE = 0o666 + + def setUp(self): + super(UnprivilegedUserGroupMetadataL3AgentTestCase, self).setUp() + self.agent.conf.set_override('metadata_proxy_user', '65534') + self.agent.conf.set_override('metadata_proxy_group', '65534') + self.agent.conf.set_override('metadata_proxy_watch_log', False) diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index d2c0d8fbe..1e550f2c3 100644 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -13,921 +13,15 @@ # License for the specific language governing permissions and limitations # under the License. -import copy -import functools -import os.path -import time - import mock -import netaddr -from oslo_config import cfg -from oslo_log import log as logging -from oslo_utils import uuidutils -import six -import testtools -import webob -import webob.dec -import webob.exc -from neutron.agent.common import config as agent_config -from neutron.agent.common import ovs_lib -from neutron.agent.l3 import agent as neutron_l3_agent -from neutron.agent.l3 import dvr_snat_ns -from neutron.agent.l3 import namespace_manager -from neutron.agent.l3 import namespaces -from neutron.agent import l3_agent as l3_agent_main -from neutron.agent.linux import dhcp -from neutron.agent.linux import external_process -from neutron.agent.linux import ip_lib -from neutron.agent.linux import utils -from neutron.callbacks import events -from neutron.callbacks import registry -from neutron.callbacks import resources -from neutron.common import config as common_config -from neutron.common import constants as l3_constants from neutron.common import topics -from neutron.common import utils as common_utils -from neutron.tests.common import l3_test_common -from neutron.tests.common import machine_fixtures -from neutron.tests.common import net_helpers -from neutron.tests.functional.agent.linux import helpers -from neutron.tests.functional import base +from neutron.tests.functional.agent.l3 import framework from neutron.tests.functional import test_service -LOG = logging.getLogger(__name__) -_uuid = uuidutils.generate_uuid - -METADATA_REQUEST_TIMEOUT = 60 -METADATA_REQUEST_SLEEP = 5 - -DEVICE_OWNER_COMPUTE = l3_constants.DEVICE_OWNER_COMPUTE_PREFIX + 'fake' - - -def get_ovs_bridge(br_name): - return ovs_lib.OVSBridge(br_name) - - -class L3AgentTestFramework(base.BaseSudoTestCase): - def setUp(self): - super(L3AgentTestFramework, self).setUp() - self.mock_plugin_api = mock.patch( - 'neutron.agent.l3.agent.L3PluginApi').start().return_value - mock.patch('neutron.agent.rpc.PluginReportStateAPI').start() - self.conf = self._configure_agent('agent1') - self.agent = neutron_l3_agent.L3NATAgentWithStateReport('agent1', - self.conf) - - def _get_config_opts(self): - config = cfg.ConfigOpts() - config.register_opts(common_config.core_opts) - config.register_opts(common_config.core_cli_opts) - logging.register_options(config) - agent_config.register_process_monitor_opts(config) - return config - - def _configure_agent(self, host, agent_mode='dvr_snat'): - conf = self._get_config_opts() - l3_agent_main.register_opts(conf) - conf.set_override( - 'interface_driver', - 'neutron.agent.linux.interface.OVSInterfaceDriver') - - br_int = self.useFixture(net_helpers.OVSBridgeFixture()).bridge - br_ex = self.useFixture(net_helpers.OVSBridgeFixture()).bridge - conf.set_override('ovs_integration_bridge', br_int.br_name) - conf.set_override('external_network_bridge', br_ex.br_name) - - temp_dir = self.get_new_temp_dir() - get_temp_file_path = functools.partial(self.get_temp_file_path, - root=temp_dir) - conf.set_override('state_path', temp_dir.path) - # NOTE(cbrandily): log_file or log_dir must be set otherwise - # metadata_proxy_watch_log has no effect - conf.set_override('log_file', - get_temp_file_path('log_file')) - conf.set_override('metadata_proxy_socket', - get_temp_file_path('metadata_proxy')) - conf.set_override('ha_confs_path', - get_temp_file_path('ha_confs')) - conf.set_override('external_pids', - get_temp_file_path('external/pids')) - conf.set_override('host', host) - conf.set_override('agent_mode', agent_mode) - - return conf - - def _get_agent_ovs_integration_bridge(self, agent): - return get_ovs_bridge(agent.conf.ovs_integration_bridge) - - def generate_router_info(self, enable_ha, ip_version=4, extra_routes=True, - enable_fip=True, enable_snat=True, - dual_stack=False, v6_ext_gw_with_sub=True): - if ip_version == 6 and not dual_stack: - enable_snat = False - enable_fip = False - extra_routes = False - - return l3_test_common.prepare_router_data(ip_version=ip_version, - enable_snat=enable_snat, - enable_floating_ip=enable_fip, - enable_ha=enable_ha, - extra_routes=extra_routes, - dual_stack=dual_stack, - v6_ext_gw_with_sub=( - v6_ext_gw_with_sub)) - - def manage_router(self, agent, router): - self.addCleanup(agent._safe_router_removed, router['id']) - agent._process_added_router(router) - return agent.router_info[router['id']] - - def _delete_router(self, agent, router_id): - agent._router_removed(router_id) - - def _add_fip(self, router, fip_address, fixed_address='10.0.0.2', - host=None): - fip = {'id': _uuid(), - 'port_id': _uuid(), - 'floating_ip_address': fip_address, - 'fixed_ip_address': fixed_address, - 'host': host} - router.router[l3_constants.FLOATINGIP_KEY].append(fip) - - def _add_internal_interface_by_subnet(self, router, count=1, - ip_version=4, - ipv6_subnet_modes=None, - interface_id=None): - return l3_test_common.router_append_subnet(router, count, - ip_version, ipv6_subnet_modes, interface_id) - - def _namespace_exists(self, namespace): - ip = ip_lib.IPWrapper(namespace=namespace) - return ip.netns.exists(namespace) - - def _metadata_proxy_exists(self, conf, router): - pm = external_process.ProcessManager( - conf, - router.router_id, - router.ns_name) - return pm.active - - def device_exists_with_ips_and_mac(self, expected_device, name_getter, - namespace): - ip_cidrs = common_utils.fixed_ip_cidrs(expected_device['fixed_ips']) - return ip_lib.device_exists_with_ips_and_mac( - name_getter(expected_device['id']), ip_cidrs, - expected_device['mac_address'], namespace) - - @staticmethod - def _port_first_ip_cidr(port): - fixed_ip = port['fixed_ips'][0] - return common_utils.ip_to_cidr(fixed_ip['ip_address'], - fixed_ip['prefixlen']) - - def get_device_mtu(self, target_device, name_getter, namespace): - device = ip_lib.IPDevice(name_getter(target_device), namespace) - return device.link.mtu - - def get_expected_keepalive_configuration(self, router): - ha_device_name = router.get_ha_device_name() - external_port = router.get_ex_gw_port() - ex_port_ipv6 = ip_lib.get_ipv6_lladdr(external_port['mac_address']) - external_device_name = router.get_external_device_name( - external_port['id']) - external_device_cidr = self._port_first_ip_cidr(external_port) - internal_port = router.router[l3_constants.INTERFACE_KEY][0] - int_port_ipv6 = ip_lib.get_ipv6_lladdr(internal_port['mac_address']) - internal_device_name = router.get_internal_device_name( - internal_port['id']) - internal_device_cidr = self._port_first_ip_cidr(internal_port) - floating_ip_cidr = common_utils.ip_to_cidr( - router.get_floating_ips()[0]['floating_ip_address']) - default_gateway_ip = external_port['subnets'][0].get('gateway_ip') - extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr') - return """vrrp_instance VR_1 { - state BACKUP - interface %(ha_device_name)s - virtual_router_id 1 - priority 50 - garp_master_repeat 5 - garp_master_refresh 10 - nopreempt - advert_int 2 - track_interface { - %(ha_device_name)s - } - virtual_ipaddress { - 169.254.0.1/24 dev %(ha_device_name)s - } - virtual_ipaddress_excluded { - %(floating_ip_cidr)s dev %(external_device_name)s - %(external_device_cidr)s dev %(external_device_name)s - %(internal_device_cidr)s dev %(internal_device_name)s - %(ex_port_ipv6)s dev %(external_device_name)s scope link - %(int_port_ipv6)s dev %(internal_device_name)s scope link - } - virtual_routes { - 0.0.0.0/0 via %(default_gateway_ip)s dev %(external_device_name)s - 8.8.8.0/24 via 19.4.4.4 - %(extra_subnet_cidr)s dev %(external_device_name)s scope link - } -}""" % { - 'ha_device_name': ha_device_name, - 'external_device_name': external_device_name, - 'external_device_cidr': external_device_cidr, - 'internal_device_name': internal_device_name, - 'internal_device_cidr': internal_device_cidr, - 'floating_ip_cidr': floating_ip_cidr, - 'default_gateway_ip': default_gateway_ip, - 'int_port_ipv6': int_port_ipv6, - 'ex_port_ipv6': ex_port_ipv6, - 'extra_subnet_cidr': extra_subnet_cidr, - } - - def _get_rule(self, iptables_manager, table, chain, predicate): - rules = iptables_manager.get_chain(table, chain) - result = next(rule for rule in rules if predicate(rule)) - return result - - def _assert_router_does_not_exist(self, router): - # If the namespace assertion succeeds - # then the devices and iptable rules have also been deleted, - # so there's no need to check that explicitly. - self.assertFalse(self._namespace_exists(router.ns_name)) - utils.wait_until_true( - lambda: not self._metadata_proxy_exists(self.agent.conf, router)) - - def _assert_snat_chains(self, router): - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'snat')) - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'POSTROUTING')) - - def _assert_floating_ip_chains(self, router): - self.assertFalse(router.iptables_manager.is_chain_empty( - 'nat', 'float-snat')) - - def _assert_metadata_chains(self, router): - metadata_port_filter = lambda rule: ( - str(self.agent.conf.metadata_port) in rule.rule) - self.assertTrue(self._get_rule(router.iptables_manager, - 'nat', - 'PREROUTING', - metadata_port_filter)) - self.assertTrue(self._get_rule(router.iptables_manager, - 'filter', - 'INPUT', - metadata_port_filter)) - - def _assert_internal_devices(self, router): - internal_devices = router.router[l3_constants.INTERFACE_KEY] - self.assertTrue(len(internal_devices)) - for device in internal_devices: - self.assertTrue(self.device_exists_with_ips_and_mac( - device, router.get_internal_device_name, router.ns_name)) - - def _assert_extra_routes(self, router): - routes = ip_lib.get_routing_table(4, namespace=router.ns_name) - routes = [{'nexthop': route['nexthop'], - 'destination': route['destination']} for route in routes] - - for extra_route in router.router['routes']: - self.assertIn(extra_route, routes) - - def _assert_onlink_subnet_routes( - self, router, ip_versions, namespace=None): - ns_name = namespace or router.ns_name - routes = [] - for ip_version in ip_versions: - _routes = ip_lib.get_routing_table(ip_version, - namespace=ns_name) - routes.extend(_routes) - routes = set(route['destination'] for route in routes) - extra_subnets = router.get_ex_gw_port()['extra_subnets'] - for extra_subnet in (route['cidr'] for route in extra_subnets): - self.assertIn(extra_subnet, routes) - - def _assert_interfaces_deleted_from_ovs(self): - def assert_ovs_bridge_empty(bridge_name): - bridge = ovs_lib.OVSBridge(bridge_name) - self.assertFalse(bridge.get_port_name_list()) - - assert_ovs_bridge_empty(self.agent.conf.ovs_integration_bridge) - assert_ovs_bridge_empty(self.agent.conf.external_network_bridge) - - def floating_ips_configured(self, router): - floating_ips = router.router[l3_constants.FLOATINGIP_KEY] - external_port = router.get_ex_gw_port() - return len(floating_ips) and all( - ip_lib.device_exists_with_ips_and_mac( - router.get_external_device_name(external_port['id']), - ['%s/32' % fip['floating_ip_address']], - external_port['mac_address'], - namespace=router.ns_name) for fip in floating_ips) - - def fail_ha_router(self, router): - device_name = router.get_ha_device_name() - ha_device = ip_lib.IPDevice(device_name, router.ha_namespace) - ha_device.link.set_down() - - @classmethod - def _get_addresses_on_device(cls, namespace, interface): - return [address['cidr'] for address in - ip_lib.IPDevice(interface, namespace=namespace).addr.list()] - - def _assert_no_ip_addresses_on_interface(self, namespace, interface): - self.assertEqual( - [], self._get_addresses_on_device(namespace, interface)) - - def _assert_ip_address_on_interface(self, - namespace, interface, ip_address): - self.assertIn( - ip_address, self._get_addresses_on_device(namespace, interface)) - - -class L3AgentTestCase(L3AgentTestFramework): - - def test_keepalived_state_change_notification(self): - enqueue_mock = mock.patch.object( - self.agent, 'enqueue_state_change').start() - router_info = self.generate_router_info(enable_ha=True) - router = self.manage_router(self.agent, router_info) - utils.wait_until_true(lambda: router.ha_state == 'master') - - self.fail_ha_router(router) - utils.wait_until_true(lambda: router.ha_state == 'backup') - - utils.wait_until_true(lambda: enqueue_mock.call_count == 3) - calls = [args[0] for args in enqueue_mock.call_args_list] - self.assertEqual((router.router_id, 'backup'), calls[0]) - self.assertEqual((router.router_id, 'master'), calls[1]) - self.assertEqual((router.router_id, 'backup'), calls[2]) - - def _expected_rpc_report(self, expected): - calls = (args[0][1] for args in - self.agent.plugin_rpc.update_ha_routers_states.call_args_list) - - # Get the last state reported for each router - actual_router_states = {} - for call in calls: - for router_id, state in six.iteritems(call): - actual_router_states[router_id] = state - - return actual_router_states == expected - - def test_keepalived_state_change_bulk_rpc(self): - router_info = self.generate_router_info(enable_ha=True) - router1 = self.manage_router(self.agent, router_info) - self.fail_ha_router(router1) - router_info = self.generate_router_info(enable_ha=True) - router2 = self.manage_router(self.agent, router_info) - - utils.wait_until_true(lambda: router1.ha_state == 'backup') - utils.wait_until_true(lambda: router2.ha_state == 'master') - utils.wait_until_true( - lambda: self._expected_rpc_report( - {router1.router_id: 'standby', router2.router_id: 'active'})) - - def test_agent_notifications_for_router_events(self): - """Test notifications for router create, update, and delete. - - Make sure that when the agent sends notifications of router events - for router create, update, and delete, that the correct handler is - called with the right resource, event, and router information. - """ - event_handler = mock.Mock() - registry.subscribe(event_handler, - resources.ROUTER, events.BEFORE_CREATE) - registry.subscribe(event_handler, - resources.ROUTER, events.AFTER_CREATE) - registry.subscribe(event_handler, - resources.ROUTER, events.BEFORE_UPDATE) - registry.subscribe(event_handler, - resources.ROUTER, events.AFTER_UPDATE) - registry.subscribe(event_handler, - resources.ROUTER, events.BEFORE_DELETE) - registry.subscribe(event_handler, - resources.ROUTER, events.AFTER_DELETE) - - router_info = self.generate_router_info(enable_ha=False) - router = self.manage_router(self.agent, router_info) - self.agent._process_updated_router(router.router) - self._delete_router(self.agent, router.router_id) - - expected_calls = [ - mock.call('router', 'before_create', self.agent, router=router), - mock.call('router', 'after_create', self.agent, router=router), - mock.call('router', 'before_update', self.agent, router=router), - mock.call('router', 'after_update', self.agent, router=router), - mock.call('router', 'before_delete', self.agent, router=router), - mock.call('router', 'after_delete', self.agent, router=router)] - event_handler.assert_has_calls(expected_calls) - - def test_legacy_router_lifecycle(self): - self._router_lifecycle(enable_ha=False, dual_stack=True) - - def test_legacy_router_lifecycle_with_no_gateway_subnet(self): - self.agent.conf.set_override('ipv6_gateway', - 'fe80::f816:3eff:fe2e:1') - self._router_lifecycle(enable_ha=False, dual_stack=True, - v6_ext_gw_with_sub=False) - - def test_ha_router_lifecycle(self): - self._router_lifecycle(enable_ha=True) - - def test_conntrack_disassociate_fip_legacy_router(self): - self._test_conntrack_disassociate_fip(ha=False) - - def test_conntrack_disassociate_fip_ha_router(self): - self._test_conntrack_disassociate_fip(ha=True) - - def _test_conntrack_disassociate_fip(self, ha): - '''Test that conntrack immediately drops stateful connection - that uses floating IP once it's disassociated. - ''' - router_info = self.generate_router_info(enable_ha=ha) - router = self.manage_router(self.agent, router_info) - - port = net_helpers.get_free_namespace_port(l3_constants.PROTO_NAME_TCP, - router.ns_name) - client_address = '19.4.4.3' - server_address = '35.4.0.4' - - def clean_fips(router): - router.router[l3_constants.FLOATINGIP_KEY] = [] - - clean_fips(router) - self._add_fip(router, client_address, fixed_address=server_address) - router.process(self.agent) - - router_ns = ip_lib.IPWrapper(namespace=router.ns_name) - netcat = net_helpers.NetcatTester( - router.ns_name, router.ns_name, client_address, port, - protocol=net_helpers.NetcatTester.TCP) - self.addCleanup(netcat.stop_processes) - - def assert_num_of_conntrack_rules(n): - out = router_ns.netns.execute(["conntrack", "-L", - "--orig-src", client_address]) - self.assertEqual( - n, len([line for line in out.strip().split('\n') if line])) - - if ha: - utils.wait_until_true(lambda: router.ha_state == 'master') - - with self.assert_max_execution_time(100): - assert_num_of_conntrack_rules(0) - - self.assertTrue(netcat.test_connectivity()) - assert_num_of_conntrack_rules(1) - - clean_fips(router) - router.process(self.agent) - assert_num_of_conntrack_rules(0) - - with testtools.ExpectedException(RuntimeError): - netcat.test_connectivity() - - def test_ipv6_ha_router_lifecycle(self): - self._router_lifecycle(enable_ha=True, ip_version=6) - - def test_ipv6_ha_router_lifecycle_with_no_gw_subnet(self): - self.agent.conf.set_override('ipv6_gateway', - 'fe80::f816:3eff:fe2e:1') - self._router_lifecycle(enable_ha=True, ip_version=6, - v6_ext_gw_with_sub=False) - - def test_ipv6_ha_router_lifecycle_with_no_gw_subnet_for_router_advts(self): - # Verify that router gw interface is configured to receive Router - # Advts from upstream router when no external gateway is configured. - self._router_lifecycle(enable_ha=True, dual_stack=True, - v6_ext_gw_with_sub=False) - - def test_keepalived_configuration(self): - router_info = self.generate_router_info(enable_ha=True) - router = self.manage_router(self.agent, router_info) - expected = self.get_expected_keepalive_configuration(router) - - self.assertEqual(expected, - router.keepalived_manager.get_conf_on_disk()) - - # Add a new FIP and change the GW IP address - router.router = copy.deepcopy(router.router) - existing_fip = '19.4.4.2' - new_fip = '19.4.4.3' - self._add_fip(router, new_fip) - subnet_id = _uuid() - fixed_ips = [{'ip_address': '19.4.4.10', - 'prefixlen': 24, - 'subnet_id': subnet_id}] - subnets = [{'id': subnet_id, - 'cidr': '19.4.4.0/24', - 'gateway_ip': '19.4.4.5'}] - router.router['gw_port']['subnets'] = subnets - router.router['gw_port']['fixed_ips'] = fixed_ips - - router.process(self.agent) - - # Get the updated configuration and assert that both FIPs are in, - # and that the GW IP address was updated. - new_config = router.keepalived_manager.config.get_config_str() - old_gw = '0.0.0.0/0 via 19.4.4.1' - new_gw = '0.0.0.0/0 via 19.4.4.5' - old_external_device_ip = '19.4.4.4' - new_external_device_ip = '19.4.4.10' - self.assertIn(existing_fip, new_config) - self.assertIn(new_fip, new_config) - self.assertNotIn(old_gw, new_config) - self.assertIn(new_gw, new_config) - external_port = router.get_ex_gw_port() - external_device_name = router.get_external_device_name( - external_port['id']) - self.assertNotIn('%s/24 dev %s' % - (old_external_device_ip, external_device_name), - new_config) - self.assertIn('%s/24 dev %s' % - (new_external_device_ip, external_device_name), - new_config) - - def _test_periodic_sync_routers_task(self, - routers_to_keep, - routers_deleted, - routers_deleted_during_resync): - ns_names_to_retrieve = set() - deleted_routers_info = [] - for r in routers_to_keep: - ri = self.manage_router(self.agent, r) - ns_names_to_retrieve.add(ri.ns_name) - for r in routers_deleted + routers_deleted_during_resync: - ri = self.manage_router(self.agent, r) - deleted_routers_info.append(ri) - ns_names_to_retrieve.add(ri.ns_name) - - mocked_get_routers = self.mock_plugin_api.get_routers - mocked_get_routers.return_value = (routers_to_keep + - routers_deleted_during_resync) - # clear agent router_info as it will be after restart - self.agent.router_info = {} - - # Synchronize the agent with the plug-in - with mock.patch.object(namespace_manager.NamespaceManager, 'list_all', - return_value=ns_names_to_retrieve): - self.agent.periodic_sync_routers_task(self.agent.context) - - # Mock the plugin RPC API so a known external network id is returned - # when the router updates are processed by the agent - external_network_id = _uuid() - self.mock_plugin_api.get_external_network_id.return_value = ( - external_network_id) - - # Plug external_gateway_info in the routers that are not going to be - # deleted by the agent when it processes the updates. Otherwise, - # _process_router_if_compatible in the agent fails - for r in routers_to_keep: - r['external_gateway_info'] = {'network_id': external_network_id} - - # while sync updates are still in the queue, higher priority - # router_deleted events may be added there as well - for r in routers_deleted_during_resync: - self.agent.router_deleted(self.agent.context, r['id']) - - # make sure all events are processed - while not self.agent._queue._queue.empty(): - self.agent._process_router_update() - - for r in routers_to_keep: - self.assertIn(r['id'], self.agent.router_info) - self.assertTrue(self._namespace_exists(namespaces.NS_PREFIX + - r['id'])) - for ri in deleted_routers_info: - self.assertNotIn(ri.router_id, - self.agent.router_info) - self._assert_router_does_not_exist(ri) - - def test_periodic_sync_routers_task(self): - routers_to_keep = [] - for i in range(2): - routers_to_keep.append(self.generate_router_info(False)) - self._test_periodic_sync_routers_task(routers_to_keep, - routers_deleted=[], - routers_deleted_during_resync=[]) - - def test_periodic_sync_routers_task_routers_deleted_while_agent_down(self): - routers_to_keep = [] - routers_deleted = [] - for i in range(2): - routers_to_keep.append(self.generate_router_info(False)) - for i in range(2): - routers_deleted.append(self.generate_router_info(False)) - self._test_periodic_sync_routers_task(routers_to_keep, - routers_deleted, - routers_deleted_during_resync=[]) - - def test_periodic_sync_routers_task_routers_deleted_while_agent_sync(self): - routers_to_keep = [] - routers_deleted_during_resync = [] - for i in range(2): - routers_to_keep.append(self.generate_router_info(False)) - for i in range(2): - routers_deleted_during_resync.append( - self.generate_router_info(False)) - self._test_periodic_sync_routers_task( - routers_to_keep, - routers_deleted=[], - routers_deleted_during_resync=routers_deleted_during_resync) - - def _router_lifecycle(self, enable_ha, ip_version=4, - dual_stack=False, v6_ext_gw_with_sub=True): - router_info = self.generate_router_info(enable_ha, ip_version, - dual_stack=dual_stack, - v6_ext_gw_with_sub=( - v6_ext_gw_with_sub)) - router = self.manage_router(self.agent, router_info) - - # Add multiple-IPv6-prefix internal router port - slaac = l3_constants.IPV6_SLAAC - slaac_mode = {'ra_mode': slaac, 'address_mode': slaac} - subnet_modes = [slaac_mode] * 2 - self._add_internal_interface_by_subnet(router.router, - count=2, - ip_version=6, - ipv6_subnet_modes=subnet_modes) - router.process(self.agent) - - if enable_ha: - port = router.get_ex_gw_port() - interface_name = router.get_external_device_name(port['id']) - self._assert_no_ip_addresses_on_interface(router.ns_name, - interface_name) - utils.wait_until_true(lambda: router.ha_state == 'master') - - # Keepalived notifies of a state transition when it starts, - # not when it ends. Thus, we have to wait until keepalived finishes - # configuring everything. We verify this by waiting until the last - # device has an IP address. - device = router.router[l3_constants.INTERFACE_KEY][-1] - device_exists = functools.partial( - self.device_exists_with_ips_and_mac, - device, - router.get_internal_device_name, - router.ns_name) - utils.wait_until_true(device_exists) - - self.assertTrue(self._namespace_exists(router.ns_name)) - utils.wait_until_true( - lambda: self._metadata_proxy_exists(self.agent.conf, router)) - self._assert_internal_devices(router) - self._assert_external_device(router) - if not (enable_ha and (ip_version == 6 or dual_stack)): - # Note(SridharG): enable the assert_gateway for IPv6 once - # keepalived on Ubuntu14.04 (i.e., check-neutron-dsvm-functional - # platform) is updated to 1.2.10 (or above). - # For more details: https://review.openstack.org/#/c/151284/ - self._assert_gateway(router, v6_ext_gw_with_sub) - self.assertTrue(self.floating_ips_configured(router)) - self._assert_snat_chains(router) - self._assert_floating_ip_chains(router) - self._assert_extra_routes(router) - ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4] - self._assert_onlink_subnet_routes(router, ip_versions) - self._assert_metadata_chains(router) - - # Verify router gateway interface is configured to receive Router Advts - # when IPv6 is enabled and no IPv6 gateway is configured. - if router.use_ipv6 and not v6_ext_gw_with_sub: - if not self.agent.conf.ipv6_gateway: - external_port = router.get_ex_gw_port() - external_device_name = router.get_external_device_name( - external_port['id']) - ip_wrapper = ip_lib.IPWrapper(namespace=router.ns_name) - ra_state = ip_wrapper.netns.execute(['sysctl', '-b', - 'net.ipv6.conf.%s.accept_ra' % external_device_name]) - self.assertEqual('2', ra_state) - - if enable_ha: - self._assert_ha_device(router) - self.assertTrue(router.keepalived_manager.get_process().active) - - self._delete_router(self.agent, router.router_id) - - self._assert_interfaces_deleted_from_ovs() - self._assert_router_does_not_exist(router) - if enable_ha: - self.assertFalse(router.keepalived_manager.get_process().active) - - def _assert_external_device(self, router): - external_port = router.get_ex_gw_port() - self.assertTrue(self.device_exists_with_ips_and_mac( - external_port, router.get_external_device_name, - router.ns_name)) - - def _assert_gateway(self, router, v6_ext_gw_with_sub=True): - external_port = router.get_ex_gw_port() - external_device_name = router.get_external_device_name( - external_port['id']) - external_device = ip_lib.IPDevice(external_device_name, - namespace=router.ns_name) - for subnet in external_port['subnets']: - self._gateway_check(subnet['gateway_ip'], external_device) - if not v6_ext_gw_with_sub: - self._gateway_check(self.agent.conf.ipv6_gateway, - external_device) - - def _gateway_check(self, gateway_ip, external_device): - expected_gateway = gateway_ip - ip_vers = netaddr.IPAddress(expected_gateway).version - existing_gateway = (external_device.route.get_gateway( - ip_version=ip_vers).get('gateway')) - self.assertEqual(expected_gateway, existing_gateway) - - def _assert_ha_device(self, router): - def ha_router_dev_name_getter(not_used): - return router.get_ha_device_name() - self.assertTrue(self.device_exists_with_ips_and_mac( - router.router[l3_constants.HA_INTERFACE_KEY], - ha_router_dev_name_getter, router.ns_name)) - - def test_ha_router_conf_on_restarted_agent(self): - router_info = self.generate_router_info(enable_ha=True) - router1 = self.manage_router(self.agent, router_info) - self._add_fip(router1, '192.168.111.12') - restarted_agent = neutron_l3_agent.L3NATAgentWithStateReport( - self.agent.host, self.agent.conf) - self.manage_router(restarted_agent, router1.router) - utils.wait_until_true(lambda: self.floating_ips_configured(router1)) - self.assertIn( - router1._get_primary_vip(), - self._get_addresses_on_device( - router1.ns_name, - router1.get_ha_device_name())) - - def test_fip_connection_from_same_subnet(self): - '''Test connection to floatingip which is associated with - fixed_ip on the same subnet of the source fixed_ip. - In other words it confirms that return packets surely - go through the router. - ''' - router_info = self.generate_router_info(enable_ha=False) - router = self.manage_router(self.agent, router_info) - router_ip_cidr = self._port_first_ip_cidr(router.internal_ports[0]) - router_ip = router_ip_cidr.partition('/')[0] - - br_int = get_ovs_bridge(self.agent.conf.ovs_integration_bridge) - src_machine, dst_machine = self.useFixture( - machine_fixtures.PeerMachines( - br_int, - net_helpers.increment_ip_cidr(router_ip_cidr), - router_ip)).machines - - dst_fip = '19.4.4.10' - router.router[l3_constants.FLOATINGIP_KEY] = [] - self._add_fip(router, dst_fip, fixed_address=dst_machine.ip) - router.process(self.agent) - - protocol_port = net_helpers.get_free_namespace_port( - l3_constants.PROTO_NAME_TCP, dst_machine.namespace) - # client sends to fip - netcat = net_helpers.NetcatTester( - src_machine.namespace, dst_machine.namespace, - dst_fip, protocol_port, - protocol=net_helpers.NetcatTester.TCP) - self.addCleanup(netcat.stop_processes) - self.assertTrue(netcat.test_connectivity()) - - def test_delete_external_gateway_on_standby_router(self): - router_info = self.generate_router_info(enable_ha=True) - router = self.manage_router(self.agent, router_info) - - self.fail_ha_router(router) - utils.wait_until_true(lambda: router.ha_state == 'backup') - - # The purpose of the test is to simply make sure no exception is raised - port = router.get_ex_gw_port() - interface_name = router.get_external_device_name(port['id']) - router.external_gateway_removed(port, interface_name) - - -class L3HATestFramework(L3AgentTestFramework): - - NESTED_NAMESPACE_SEPARATOR = '@' - - def setUp(self): - super(L3HATestFramework, self).setUp() - self.conf = self._configure_agent('agent2') - self.failover_agent = neutron_l3_agent.L3NATAgentWithStateReport( - 'agent2', self.conf) - - br_int_1 = self._get_agent_ovs_integration_bridge(self.agent) - br_int_2 = self._get_agent_ovs_integration_bridge(self.failover_agent) - - veth1, veth2 = self.useFixture(net_helpers.VethFixture()).ports - br_int_1.add_port(veth1.name) - br_int_2.add_port(veth2.name) - - def test_ha_router_failover(self): - router_info = self.generate_router_info(enable_ha=True) - get_ns_name = mock.patch.object( - namespaces.RouterNamespace, '_get_ns_name').start() - get_ns_name.return_value = "%s%s%s" % ( - 'qrouter-' + router_info['id'], - self.NESTED_NAMESPACE_SEPARATOR, self.agent.host) - router1 = self.manage_router(self.agent, router_info) - - router_info_2 = copy.deepcopy(router_info) - router_info_2[l3_constants.HA_INTERFACE_KEY] = ( - l3_test_common.get_ha_interface(ip='169.254.192.2', - mac='22:22:22:22:22:22')) - - get_ns_name.return_value = "%s%s%s" % ( - namespaces.RouterNamespace._get_ns_name(router_info_2['id']), - self.NESTED_NAMESPACE_SEPARATOR, self.failover_agent.host) - router2 = self.manage_router(self.failover_agent, router_info_2) - - utils.wait_until_true(lambda: router1.ha_state == 'master') - utils.wait_until_true(lambda: router2.ha_state == 'backup') - - self.fail_ha_router(router1) - - utils.wait_until_true(lambda: router2.ha_state == 'master') - utils.wait_until_true(lambda: router1.ha_state == 'backup') - - def test_ha_router_ipv6_radvd_status(self): - router_info = self.generate_router_info(ip_version=6, enable_ha=True) - router1 = self.manage_router(self.agent, router_info) - utils.wait_until_true(lambda: router1.ha_state == 'master') - utils.wait_until_true(lambda: router1.radvd.enabled) - - def _check_lla_status(router, expected): - internal_devices = router.router[l3_constants.INTERFACE_KEY] - for device in internal_devices: - lladdr = ip_lib.get_ipv6_lladdr(device['mac_address']) - exists = ip_lib.device_exists_with_ips_and_mac( - router.get_internal_device_name(device['id']), [lladdr], - device['mac_address'], router.ns_name) - self.assertEqual(expected, exists) - - _check_lla_status(router1, True) - - device_name = router1.get_ha_device_name() - ha_device = ip_lib.IPDevice(device_name, namespace=router1.ns_name) - ha_device.link.set_down() - - utils.wait_until_true(lambda: router1.ha_state == 'backup') - utils.wait_until_true(lambda: not router1.radvd.enabled, timeout=10) - _check_lla_status(router1, False) - - def test_ha_router_process_ipv6_subnets_to_existing_port(self): - router_info = self.generate_router_info(enable_ha=True, ip_version=6) - router = self.manage_router(self.agent, router_info) - - def verify_ip_in_keepalived_config(router, iface): - config = router.keepalived_manager.config.get_config_str() - ip_cidrs = common_utils.fixed_ip_cidrs(iface['fixed_ips']) - for ip_addr in ip_cidrs: - self.assertIn(ip_addr, config) - - interface_id = router.router[l3_constants.INTERFACE_KEY][0]['id'] - slaac = l3_constants.IPV6_SLAAC - slaac_mode = {'ra_mode': slaac, 'address_mode': slaac} - - # Add a second IPv6 subnet to the router internal interface. - self._add_internal_interface_by_subnet(router.router, count=1, - ip_version=6, ipv6_subnet_modes=[slaac_mode], - interface_id=interface_id) - router.process(self.agent) - utils.wait_until_true(lambda: router.ha_state == 'master') - - # Verify that router internal interface is present and is configured - # with IP address from both the subnets. - internal_iface = router.router[l3_constants.INTERFACE_KEY][0] - self.assertEqual(2, len(internal_iface['fixed_ips'])) - self._assert_internal_devices(router) - - # Verify that keepalived config is properly updated. - verify_ip_in_keepalived_config(router, internal_iface) - - # Remove one subnet from the router internal iface - interfaces = copy.deepcopy(router.router.get( - l3_constants.INTERFACE_KEY, [])) - fixed_ips, subnets = [], [] - fixed_ips.append(interfaces[0]['fixed_ips'][0]) - subnets.append(interfaces[0]['subnets'][0]) - interfaces[0].update({'fixed_ips': fixed_ips, 'subnets': subnets}) - router.router[l3_constants.INTERFACE_KEY] = interfaces - router.process(self.agent) - - # Verify that router internal interface has a single ipaddress - internal_iface = router.router[l3_constants.INTERFACE_KEY][0] - self.assertEqual(1, len(internal_iface['fixed_ips'])) - self._assert_internal_devices(router) - - # Verify that keepalived config is properly updated. - verify_ip_in_keepalived_config(router, internal_iface) - - class TestL3AgentRestart(test_service.TestServiceRestart, - L3AgentTestFramework): + framework.L3AgentTestFramework): def _start_l3_agent(self, workers=1): with mock.patch("neutron.service.Service.start") as start_method: @@ -941,728 +35,3 @@ class TestL3AgentRestart(test_service.TestServiceRestart, def test_restart_l3_agent_on_sighup(self): self._test_restart_service_on_sighup(service=self._start_l3_agent, workers=1) - - -class MetadataFakeProxyHandler(object): - - def __init__(self, status): - self.status = status - - @webob.dec.wsgify() - def __call__(self, req): - return webob.Response(status=self.status) - - -class MetadataL3AgentTestCase(L3AgentTestFramework): - - SOCKET_MODE = 0o644 - - def _create_metadata_fake_server(self, status): - server = utils.UnixDomainWSGIServer('metadata-fake-server') - self.addCleanup(server.stop) - - # NOTE(cbrandily): TempDir fixture creates a folder with 0o700 - # permissions but metadata_proxy_socket folder must be readable by all - # users - self.useFixture( - helpers.RecursivePermDirFixture( - os.path.dirname(self.agent.conf.metadata_proxy_socket), 0o555)) - server.start(MetadataFakeProxyHandler(status), - self.agent.conf.metadata_proxy_socket, - workers=0, backlog=4096, mode=self.SOCKET_MODE) - - def _query_metadata_proxy(self, machine): - url = 'http://%(host)s:%(port)s' % {'host': dhcp.METADATA_DEFAULT_IP, - 'port': dhcp.METADATA_PORT} - cmd = 'curl', '--max-time', METADATA_REQUEST_TIMEOUT, '-D-', url - i = 0 - CONNECTION_REFUSED_TIMEOUT = METADATA_REQUEST_TIMEOUT // 2 - while i <= CONNECTION_REFUSED_TIMEOUT: - try: - raw_headers = machine.execute(cmd) - break - except RuntimeError as e: - if 'Connection refused' in str(e): - time.sleep(METADATA_REQUEST_SLEEP) - i += METADATA_REQUEST_SLEEP - else: - self.fail('metadata proxy unreachable ' - 'on %s before timeout' % url) - - if i > CONNECTION_REFUSED_TIMEOUT: - self.fail('Timed out waiting metadata proxy to become available') - return raw_headers.splitlines()[0] - - def test_access_to_metadata_proxy(self): - """Test access to the l3-agent metadata proxy. - - The test creates: - * A l3-agent metadata service: - * A router (which creates a metadata proxy in the router namespace), - * A fake metadata server - * A "client" namespace (simulating a vm) with a port on router - internal subnet. - - The test queries from the "client" namespace the metadata proxy on - http://169.254.169.254 and asserts that the metadata proxy added - the X-Forwarded-For and X-Neutron-Router-Id headers to the request - and forwarded the http request to the fake metadata server and the - response to the "client" namespace. - """ - router_info = self.generate_router_info(enable_ha=False) - router = self.manage_router(self.agent, router_info) - self._create_metadata_fake_server(webob.exc.HTTPOk.code) - - # Create and configure client namespace - router_ip_cidr = self._port_first_ip_cidr(router.internal_ports[0]) - br_int = get_ovs_bridge(self.agent.conf.ovs_integration_bridge) - - machine = self.useFixture( - machine_fixtures.FakeMachine( - br_int, - net_helpers.increment_ip_cidr(router_ip_cidr), - router_ip_cidr.partition('/')[0])) - - # Query metadata proxy - firstline = self._query_metadata_proxy(machine) - - # Check status code - self.assertIn(str(webob.exc.HTTPOk.code), firstline.split()) - - -class UnprivilegedUserMetadataL3AgentTestCase(MetadataL3AgentTestCase): - """Test metadata proxy with least privileged user. - - The least privileged user has uid=65534 and is commonly named 'nobody' but - not always, that's why we use its uid. - """ - - SOCKET_MODE = 0o664 - - def setUp(self): - super(UnprivilegedUserMetadataL3AgentTestCase, self).setUp() - self.agent.conf.set_override('metadata_proxy_user', '65534') - self.agent.conf.set_override('metadata_proxy_watch_log', False) - - -class UnprivilegedUserGroupMetadataL3AgentTestCase(MetadataL3AgentTestCase): - """Test metadata proxy with least privileged user/group. - - The least privileged user has uid=65534 and is commonly named 'nobody' but - not always, that's why we use its uid. - Its group has gid=65534 and is commonly named 'nobody' or 'nogroup', that's - why we use its gid. - """ - - SOCKET_MODE = 0o666 - - def setUp(self): - super(UnprivilegedUserGroupMetadataL3AgentTestCase, self).setUp() - self.agent.conf.set_override('metadata_proxy_user', '65534') - self.agent.conf.set_override('metadata_proxy_group', '65534') - self.agent.conf.set_override('metadata_proxy_watch_log', False) - - -class TestDvrRouter(L3AgentTestFramework): - def test_dvr_router_lifecycle_without_ha_without_snat_with_fips(self): - self._dvr_router_lifecycle(enable_ha=False, enable_snat=False) - - def test_dvr_router_lifecycle_without_ha_with_snat_with_fips(self): - self._dvr_router_lifecycle(enable_ha=False, enable_snat=True) - - def test_dvr_router_lifecycle_ha_with_snat_with_fips(self): - self._dvr_router_lifecycle(enable_ha=True, enable_snat=True) - - def _helper_create_dvr_router_fips_for_ext_network( - self, agent_mode, **dvr_router_kwargs): - self.agent.conf.agent_mode = agent_mode - router_info = self.generate_dvr_router_info(**dvr_router_kwargs) - self.mock_plugin_api.get_external_network_id.return_value = ( - router_info['_floatingips'][0]['floating_network_id']) - router = self.manage_router(self.agent, router_info) - fip_ns = router.fip_ns.get_name() - return router, fip_ns - - def _validate_fips_for_external_network(self, router, fip_ns): - self.assertTrue(self._namespace_exists(router.ns_name)) - self.assertTrue(self._namespace_exists(fip_ns)) - self._assert_dvr_floating_ips(router) - self._assert_snat_namespace_does_not_exist(router) - - def test_dvr_router_fips_for_multiple_ext_networks(self): - agent_mode = 'dvr' - # Create the first router fip with external net1 - dvr_router1_kwargs = {'ip_address': '19.4.4.3', - 'subnet_cidr': '19.4.4.0/24', - 'gateway_ip': '19.4.4.1', - 'gateway_mac': 'ca:fe:de:ab:cd:ef'} - router1, fip1_ns = ( - self._helper_create_dvr_router_fips_for_ext_network( - agent_mode, **dvr_router1_kwargs)) - # Validate the fip with external net1 - self._validate_fips_for_external_network(router1, fip1_ns) - - # Create the second router fip with external net2 - dvr_router2_kwargs = {'ip_address': '19.4.5.3', - 'subnet_cidr': '19.4.5.0/24', - 'gateway_ip': '19.4.5.1', - 'gateway_mac': 'ca:fe:de:ab:cd:fe'} - router2, fip2_ns = ( - self._helper_create_dvr_router_fips_for_ext_network( - agent_mode, **dvr_router2_kwargs)) - # Validate the fip with external net2 - self._validate_fips_for_external_network(router2, fip2_ns) - - def _dvr_router_lifecycle(self, enable_ha=False, enable_snat=False, - custom_mtu=2000, - ip_version=4, - dual_stack=False): - '''Test dvr router lifecycle - - :param enable_ha: sets the ha value for the router. - :param enable_snat: the value of enable_snat is used - to set the agent_mode. - ''' - - # The value of agent_mode can be dvr, dvr_snat, or legacy. - # Since by definition this is a dvr (distributed = true) - # only dvr and dvr_snat are applicable - self.agent.conf.agent_mode = 'dvr_snat' if enable_snat else 'dvr' - self.agent.conf.network_device_mtu = custom_mtu - - # We get the router info particular to a dvr router - router_info = self.generate_dvr_router_info( - enable_ha, enable_snat) - - # We need to mock the get_agent_gateway_port return value - # because the whole L3PluginApi is mocked and we need the port - # gateway_port information before the l3_agent will create it. - # The port returned needs to have the same information as - # router_info['gw_port'] - self.mock_plugin_api.get_agent_gateway_port.return_value = router_info[ - 'gw_port'] - - # We also need to mock the get_external_network_id method to - # get the correct fip namespace. - self.mock_plugin_api.get_external_network_id.return_value = ( - router_info['_floatingips'][0]['floating_network_id']) - - # With all that set we can now ask the l3_agent to - # manage the router (create it, create namespaces, - # attach interfaces, etc...) - router = self.manage_router(self.agent, router_info) - if enable_ha: - port = router.get_ex_gw_port() - interface_name = router.get_external_device_name(port['id']) - self._assert_no_ip_addresses_on_interface(router.ha_namespace, - interface_name) - utils.wait_until_true(lambda: router.ha_state == 'master') - - # Keepalived notifies of a state transition when it starts, - # not when it ends. Thus, we have to wait until keepalived finishes - # configuring everything. We verify this by waiting until the last - # device has an IP address. - device = router.router[l3_constants.INTERFACE_KEY][-1] - device_exists = functools.partial( - self.device_exists_with_ips_and_mac, - device, - router.get_internal_device_name, - router.ns_name) - utils.wait_until_true(device_exists) - - ext_gateway_port = router_info['gw_port'] - self.assertTrue(self._namespace_exists(router.ns_name)) - utils.wait_until_true( - lambda: self._metadata_proxy_exists(self.agent.conf, router)) - self._assert_internal_devices(router) - self._assert_dvr_external_device(router) - self._assert_dvr_gateway(router) - self._assert_dvr_floating_ips(router) - self._assert_snat_chains(router) - self._assert_floating_ip_chains(router) - self._assert_metadata_chains(router) - self._assert_extra_routes(router) - self._assert_rfp_fpr_mtu(router, custom_mtu) - if enable_snat: - ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4] - snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - self._assert_onlink_subnet_routes( - router, ip_versions, snat_ns_name) - - self._delete_router(self.agent, router.router_id) - self._assert_fip_namespace_deleted(ext_gateway_port) - self._assert_router_does_not_exist(router) - self._assert_snat_namespace_does_not_exist(router) - - def generate_dvr_router_info(self, - enable_ha=False, - enable_snat=False, - agent=None, - **kwargs): - if not agent: - agent = self.agent - router = l3_test_common.prepare_router_data( - enable_snat=enable_snat, - enable_floating_ip=True, - enable_ha=enable_ha, - **kwargs) - internal_ports = router.get(l3_constants.INTERFACE_KEY, []) - router['distributed'] = True - router['gw_port_host'] = agent.conf.host - router['gw_port']['binding:host_id'] = agent.conf.host - floating_ip = router['_floatingips'][0] - floating_ip['floating_network_id'] = router['gw_port']['network_id'] - floating_ip['host'] = agent.conf.host - floating_ip['port_id'] = internal_ports[0]['id'] - floating_ip['status'] = 'ACTIVE' - - self._add_snat_port_info_to_router(router, internal_ports) - # FIP has a dependency on external gateway. So we need to create - # the snat_port info and fip_agent_gw_port_info irrespective of - # the agent type the dvr supports. The namespace creation is - # dependent on the agent_type. - external_gw_port = router['gw_port'] - self._add_fip_agent_gw_port_info_to_router(router, external_gw_port) - return router - - def _add_fip_agent_gw_port_info_to_router(self, router, external_gw_port): - # Add fip agent gateway port information to the router_info - fip_gw_port_list = router.get( - l3_constants.FLOATINGIP_AGENT_INTF_KEY, []) - if not fip_gw_port_list and external_gw_port: - # Get values from external gateway port - fixed_ip = external_gw_port['fixed_ips'][0] - float_subnet = external_gw_port['subnets'][0] - port_ip = fixed_ip['ip_address'] - # Pick an ip address which is not the same as port_ip - fip_gw_port_ip = str(netaddr.IPAddress(port_ip) + 5) - # Add floatingip agent gateway port info to router - prefixlen = netaddr.IPNetwork(float_subnet['cidr']).prefixlen - router[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = [ - {'subnets': [ - {'cidr': float_subnet['cidr'], - 'gateway_ip': float_subnet['gateway_ip'], - 'id': fixed_ip['subnet_id']}], - 'network_id': external_gw_port['network_id'], - 'device_owner': l3_constants.DEVICE_OWNER_AGENT_GW, - 'mac_address': 'fa:16:3e:80:8d:89', - 'binding:host_id': self.agent.conf.host, - 'fixed_ips': [{'subnet_id': fixed_ip['subnet_id'], - 'ip_address': fip_gw_port_ip, - 'prefixlen': prefixlen}], - 'id': _uuid(), - 'device_id': _uuid()} - ] - - def _add_snat_port_info_to_router(self, router, internal_ports): - # Add snat port information to the router - snat_port_list = router.get(l3_constants.SNAT_ROUTER_INTF_KEY, []) - if not snat_port_list and internal_ports: - # Get values from internal port - port = internal_ports[0] - fixed_ip = port['fixed_ips'][0] - snat_subnet = port['subnets'][0] - port_ip = fixed_ip['ip_address'] - # Pick an ip address which is not the same as port_ip - snat_ip = str(netaddr.IPAddress(port_ip) + 5) - # Add the info to router as the first snat port - # in the list of snat ports - prefixlen = netaddr.IPNetwork(snat_subnet['cidr']).prefixlen - router[l3_constants.SNAT_ROUTER_INTF_KEY] = [ - {'subnets': [ - {'cidr': snat_subnet['cidr'], - 'gateway_ip': snat_subnet['gateway_ip'], - 'id': fixed_ip['subnet_id']}], - 'network_id': port['network_id'], - 'device_owner': l3_constants.DEVICE_OWNER_ROUTER_SNAT, - 'mac_address': 'fa:16:3e:80:8d:89', - 'fixed_ips': [{'subnet_id': fixed_ip['subnet_id'], - 'ip_address': snat_ip, - 'prefixlen': prefixlen}], - 'id': _uuid(), - 'device_id': _uuid()} - ] - - def _assert_dvr_external_device(self, router): - external_port = router.get_ex_gw_port() - snat_ns_name = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - - # if the agent is in dvr_snat mode, then we have to check - # that the correct ports and ip addresses exist in the - # snat_ns_name namespace - if self.agent.conf.agent_mode == 'dvr_snat': - device_exists = functools.partial( - self.device_exists_with_ips_and_mac, - external_port, - router.get_external_device_name, - snat_ns_name) - utils.wait_until_true(device_exists) - # if the agent is in dvr mode then the snat_ns_name namespace - # should not be present at all: - elif self.agent.conf.agent_mode == 'dvr': - self.assertFalse( - self._namespace_exists(snat_ns_name), - "namespace %s was found but agent is in dvr mode not dvr_snat" - % (str(snat_ns_name)) - ) - # if the agent is anything else the test is misconfigured - # we force a test failure with message - else: - self.assertTrue(False, " agent not configured for dvr or dvr_snat") - - def _assert_dvr_gateway(self, router): - gateway_expected_in_snat_namespace = ( - self.agent.conf.agent_mode == 'dvr_snat' - ) - if gateway_expected_in_snat_namespace: - self._assert_dvr_snat_gateway(router) - self._assert_removal_of_already_deleted_gateway_device(router) - - snat_namespace_should_not_exist = ( - self.agent.conf.agent_mode == 'dvr' - ) - if snat_namespace_should_not_exist: - self._assert_snat_namespace_does_not_exist(router) - - def _assert_dvr_snat_gateway(self, router): - namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - external_port = router.get_ex_gw_port() - external_device_name = router.get_external_device_name( - external_port['id']) - external_device = ip_lib.IPDevice(external_device_name, - namespace=namespace) - existing_gateway = ( - external_device.route.get_gateway().get('gateway')) - expected_gateway = external_port['subnets'][0]['gateway_ip'] - self.assertEqual(expected_gateway, existing_gateway) - - def _assert_removal_of_already_deleted_gateway_device(self, router): - namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - device = ip_lib.IPDevice("fakedevice", - namespace=namespace) - - # Assert that no exception is thrown for this case - self.assertIsNone(router._delete_gateway_device_if_exists( - device, "192.168.0.1", 0)) - - def _assert_snat_namespace_does_not_exist(self, router): - namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - self.assertFalse(self._namespace_exists(namespace)) - - def _assert_dvr_floating_ips(self, router): - # in the fip namespace: - # Check that the fg- (floatingip_agent_gateway) - # is created with the ip address of the external gateway port - floating_ips = router.router[l3_constants.FLOATINGIP_KEY] - self.assertTrue(floating_ips) - # We need to fetch the floatingip agent gateway port info - # from the router_info - floating_agent_gw_port = ( - router.router[l3_constants.FLOATINGIP_AGENT_INTF_KEY]) - self.assertTrue(floating_agent_gw_port) - - external_gw_port = floating_agent_gw_port[0] - fip_ns = self.agent.get_fip_ns(floating_ips[0]['floating_network_id']) - fip_ns_name = fip_ns.get_name() - fg_port_created_successfully = ip_lib.device_exists_with_ips_and_mac( - fip_ns.get_ext_device_name(external_gw_port['id']), - [self._port_first_ip_cidr(external_gw_port)], - external_gw_port['mac_address'], - namespace=fip_ns_name) - self.assertTrue(fg_port_created_successfully) - # Check fpr-router device has been created - device_name = fip_ns.get_int_device_name(router.router_id) - fpr_router_device_created_successfully = ip_lib.device_exists( - device_name, namespace=fip_ns_name) - self.assertTrue(fpr_router_device_created_successfully) - - # In the router namespace - # Check rfp- is created correctly - for fip in floating_ips: - device_name = fip_ns.get_rtr_ext_device_name(router.router_id) - self.assertTrue(ip_lib.device_exists( - device_name, namespace=router.ns_name)) - - def test_dvr_router_rem_fips_on_restarted_agent(self): - self.agent.conf.agent_mode = 'dvr_snat' - router_info = self.generate_dvr_router_info() - router1 = self.manage_router(self.agent, router_info) - fip_ns = router1.fip_ns.get_name() - self.assertTrue(self._namespace_exists(fip_ns)) - restarted_agent = neutron_l3_agent.L3NATAgentWithStateReport( - self.agent.host, self.agent.conf) - router1.router[l3_constants.FLOATINGIP_KEY] = [] - self.manage_router(restarted_agent, router1.router) - self._assert_dvr_snat_gateway(router1) - self.assertTrue(self._namespace_exists(fip_ns)) - - def test_dvr_router_add_fips_on_restarted_agent(self): - self.agent.conf.agent_mode = 'dvr' - router_info = self.generate_dvr_router_info() - router = self.manage_router(self.agent, router_info) - floating_ips = router.router[l3_constants.FLOATINGIP_KEY] - router_ns = router.ns_name - fip_rule_prio_1 = self._get_fixed_ip_rule_priority( - router_ns, floating_ips[0]['fixed_ip_address']) - restarted_agent = neutron_l3_agent.L3NATAgent( - self.agent.host, self.agent.conf) - floating_ips[0]['floating_ip_address'] = '21.4.4.2' - floating_ips[0]['fixed_ip_address'] = '10.0.0.2' - self.manage_router(restarted_agent, router_info) - fip_rule_prio_2 = self._get_fixed_ip_rule_priority( - router_ns, floating_ips[0]['fixed_ip_address']) - self.assertNotEqual(fip_rule_prio_1, fip_rule_prio_2) - - def _get_fixed_ip_rule_priority(self, namespace, fip): - iprule = ip_lib.IPRule(namespace) - lines = iprule.rule._as_root([4], ['show']).splitlines() - for line in lines: - if fip in line: - info = iprule.rule._parse_line(4, line) - return info['priority'] - - def test_dvr_router_add_internal_network_set_arp_cache(self): - # Check that, when the router is set up and there are - # existing ports on the uplinked subnet, the ARP - # cache is properly populated. - self.agent.conf.agent_mode = 'dvr_snat' - router_info = l3_test_common.prepare_router_data() - router_info['distributed'] = True - expected_neighbor = '35.4.1.10' - port_data = { - 'fixed_ips': [{'ip_address': expected_neighbor}], - 'mac_address': 'fa:3e:aa:bb:cc:dd', - 'device_owner': DEVICE_OWNER_COMPUTE - } - self.agent.plugin_rpc.get_ports_by_subnet.return_value = [port_data] - router1 = self.manage_router(self.agent, router_info) - internal_device = router1.get_internal_device_name( - router_info['_interfaces'][0]['id']) - neighbors = ip_lib.IPDevice(internal_device, router1.ns_name).neigh - self.assertEqual(expected_neighbor, - neighbors.show(ip_version=4).split()[0]) - - def _assert_rfp_fpr_mtu(self, router, expected_mtu=1500): - dev_mtu = self.get_device_mtu( - router.router_id, router.fip_ns.get_rtr_ext_device_name, - router.ns_name) - self.assertEqual(expected_mtu, dev_mtu) - dev_mtu = self.get_device_mtu( - router.router_id, router.fip_ns.get_int_device_name, - router.fip_ns.get_name()) - self.assertEqual(expected_mtu, dev_mtu) - - def test_dvr_router_fip_agent_mismatch(self): - """Test to validate the floatingip agent mismatch. - - This test validates the condition where floatingip agent - gateway port host mismatches with the agent and so the - binding will not be there. - - """ - self.agent.conf.agent_mode = 'dvr' - router_info = self.generate_dvr_router_info() - floating_ip = router_info['_floatingips'][0] - floating_ip['host'] = 'my_new_host' - # In this case the floatingip binding is different and so it - # should not create the floatingip namespace on the given agent. - # This is also like there is no current binding. - router1 = self.manage_router(self.agent, router_info) - fip_ns = router1.fip_ns.get_name() - self.assertTrue(self._namespace_exists(router1.ns_name)) - self.assertFalse(self._namespace_exists(fip_ns)) - self._assert_snat_namespace_does_not_exist(router1) - - def test_dvr_router_fip_late_binding(self): - """Test to validate the floatingip migration or latebinding. - - This test validates the condition where floatingip private - port changes while migration or when the private port host - binding is done later after floatingip association. - - """ - self.agent.conf.agent_mode = 'dvr' - router_info = self.generate_dvr_router_info() - fip_agent_gw_port = router_info[l3_constants.FLOATINGIP_AGENT_INTF_KEY] - # Now let us not pass the FLOATINGIP_AGENT_INTF_KEY, to emulate - # that the server did not create the port, since there was no valid - # host binding. - router_info[l3_constants.FLOATINGIP_AGENT_INTF_KEY] = [] - self.mock_plugin_api.get_agent_gateway_port.return_value = ( - fip_agent_gw_port[0]) - router1 = self.manage_router(self.agent, router_info) - fip_ns = router1.fip_ns.get_name() - self.assertTrue(self._namespace_exists(router1.ns_name)) - self.assertTrue(self._namespace_exists(fip_ns)) - self._assert_snat_namespace_does_not_exist(router1) - - def _assert_snat_namespace_exists(self, router): - namespace = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - self.assertTrue(self._namespace_exists(namespace)) - - def _get_dvr_snat_namespace_device_status( - self, router, internal_dev_name=None): - """Function returns the internal and external device status.""" - snat_ns = dvr_snat_ns.SnatNamespace.get_snat_ns_name( - router.router_id) - external_port = router.get_ex_gw_port() - external_device_name = router.get_external_device_name( - external_port['id']) - qg_device_created_successfully = ip_lib.device_exists( - external_device_name, namespace=snat_ns) - sg_device_created_successfully = ip_lib.device_exists( - internal_dev_name, namespace=snat_ns) - return qg_device_created_successfully, sg_device_created_successfully - - def test_dvr_router_snat_namespace_with_interface_remove(self): - """Test to validate the snat namespace with interface remove. - - This test validates the snat namespace for all the external - and internal devices. It also validates if the internal - device corresponding to the router interface is removed - when the router interface is deleted. - """ - self.agent.conf.agent_mode = 'dvr_snat' - router_info = self.generate_dvr_router_info() - snat_internal_port = router_info[l3_constants.SNAT_ROUTER_INTF_KEY] - router1 = self.manage_router(self.agent, router_info) - csnat_internal_port = ( - router1.router[l3_constants.SNAT_ROUTER_INTF_KEY]) - # Now save the internal device name to verify later - internal_device_name = router1._get_snat_int_device_name( - csnat_internal_port[0]['id']) - self._assert_snat_namespace_exists(router1) - qg_device, sg_device = self._get_dvr_snat_namespace_device_status( - router1, internal_dev_name=internal_device_name) - self.assertTrue(qg_device) - self.assertTrue(sg_device) - self.assertEqual(router1.snat_ports, snat_internal_port) - # Now let us not pass INTERFACE_KEY, to emulate - # the interface has been removed. - router1.router[l3_constants.INTERFACE_KEY] = [] - # Now let us not pass the SNAT_ROUTER_INTF_KEY, to emulate - # that the server did not send it, since the interface has been - # removed. - router1.router[l3_constants.SNAT_ROUTER_INTF_KEY] = [] - self.agent._process_updated_router(router1.router) - router_updated = self.agent.router_info[router_info['id']] - self._assert_snat_namespace_exists(router_updated) - qg_device, sg_device = self._get_dvr_snat_namespace_device_status( - router_updated, internal_dev_name=internal_device_name) - self.assertFalse(sg_device) - self.assertTrue(qg_device) - - def _mocked_dvr_ha_router(self, agent): - r_info = self.generate_dvr_router_info(enable_ha=True, - enable_snat=True, - agent=agent) - - r_snat_ns_name = namespaces.build_ns_name(dvr_snat_ns.SNAT_NS_PREFIX, - r_info['id']) - - mocked_r_snat_ns_name = r_snat_ns_name + '@' + agent.host - r_ns_name = namespaces.build_ns_name(namespaces.NS_PREFIX, - r_info['id']) - - mocked_r_ns_name = r_ns_name + '@' + agent.host - - return r_info, mocked_r_ns_name, mocked_r_snat_ns_name - - def _setup_dvr_ha_agents(self): - self.agent.conf.agent_mode = 'dvr_snat' - - self.conf = self._configure_agent('agent2') - self.failover_agent = neutron_l3_agent.L3NATAgentWithStateReport( - 'agent2', self.conf) - self.failover_agent.conf.agent_mode = 'dvr_snat' - - def _setup_dvr_ha_bridges(self): - br_int_1 = self._get_agent_ovs_integration_bridge(self.agent) - br_int_2 = self._get_agent_ovs_integration_bridge(self.failover_agent) - - veth1, veth2 = self.useFixture(net_helpers.VethFixture()).ports - br_int_1.add_port(veth1.name) - br_int_2.add_port(veth2.name) - - def _create_dvr_ha_router(self, agent): - get_ns_name = mock.patch.object(namespaces.RouterNamespace, - '_get_ns_name').start() - get_snat_ns_name = mock.patch.object(dvr_snat_ns.SnatNamespace, - 'get_snat_ns_name').start() - (r_info, - mocked_r_ns_name, - mocked_r_snat_ns_name) = self._mocked_dvr_ha_router(agent) - get_ns_name.return_value = mocked_r_ns_name - get_snat_ns_name.return_value = mocked_r_snat_ns_name - router = self.manage_router(agent, r_info) - return router - - def _assert_ip_addresses_in_dvr_ha_snat_namespace(self, router): - namespace = router.ha_namespace - ex_gw_port = router.get_ex_gw_port() - snat_port = router.get_snat_interfaces()[0] - ex_gw_port_name = router.get_external_device_name( - ex_gw_port['id']) - snat_port_name = router._get_snat_int_device_name( - snat_port['id']) - - ip = ex_gw_port["fixed_ips"][0]['ip_address'] - prefix_len = ex_gw_port["fixed_ips"][0]['prefixlen'] - ex_gw_port_cidr = ip + "/" + str(prefix_len) - ip = snat_port["fixed_ips"][0]['ip_address'] - prefix_len = snat_port["fixed_ips"][0]['prefixlen'] - snat_port_cidr = ip + "/" + str(prefix_len) - - self._assert_ip_address_on_interface(namespace, - ex_gw_port_name, - ex_gw_port_cidr) - self._assert_ip_address_on_interface(namespace, - snat_port_name, - snat_port_cidr) - - def _assert_no_ip_addresses_in_dvr_ha_snat_namespace(self, router): - namespace = router.ha_namespace - ex_gw_port = router.get_ex_gw_port() - snat_port = router.get_snat_interfaces()[0] - ex_gw_port_name = router.get_external_device_name( - ex_gw_port['id']) - snat_port_name = router._get_snat_int_device_name( - snat_port['id']) - - self._assert_no_ip_addresses_on_interface(namespace, - snat_port_name) - self._assert_no_ip_addresses_on_interface(namespace, - ex_gw_port_name) - - def test_dvr_ha_router_failover(self): - self._setup_dvr_ha_agents() - self._setup_dvr_ha_bridges() - - router1 = self._create_dvr_ha_router(self.agent) - router2 = self._create_dvr_ha_router(self.failover_agent) - - utils.wait_until_true(lambda: router1.ha_state == 'master') - utils.wait_until_true(lambda: router2.ha_state == 'backup') - - self._assert_ip_addresses_in_dvr_ha_snat_namespace(router1) - self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router2) - - self.fail_ha_router(router1) - - utils.wait_until_true(lambda: router2.ha_state == 'master') - utils.wait_until_true(lambda: router1.ha_state == 'backup') - - self._assert_ip_addresses_in_dvr_ha_snat_namespace(router2) - self._assert_no_ip_addresses_in_dvr_ha_snat_namespace(router1) - - def _assert_fip_namespace_deleted(self, ext_gateway_port): - ext_net_id = ext_gateway_port['network_id'] - self.agent.fipnamespace_delete_on_ext_net( - self.agent.context, ext_net_id) - self._assert_interfaces_deleted_from_ovs() -- 2.45.2