diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index 059c598aed5..3d0bd852b35 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -687,7 +687,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, self.agent_gateway_port = None def _destroy_router_namespace(self, ns): - router_id = ns[len(NS_PREFIX):] + router_id = self.get_router_id(ns) ra.disable_ipv6_ra(router_id, ns, self.root_helper) if self.conf.enable_metadata_proxy: self._destroy_metadata_proxy(router_id, ns) @@ -1242,6 +1242,9 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, def get_ns_name(self, router_id): return (NS_PREFIX + router_id) + def get_router_id(self, ns_name): + return ns_name[len(NS_PREFIX):] + def get_snat_ns_name(self, router_id): return (SNAT_NS_PREFIX + router_id) diff --git a/neutron/tests/common/__init__.py b/neutron/tests/common/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/common/agents/__init__.py b/neutron/tests/common/agents/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/common/agents/l3_agent.py b/neutron/tests/common/agents/l3_agent.py new file mode 100644 index 00000000000..92ad587fa47 --- /dev/null +++ b/neutron/tests/common/agents/l3_agent.py @@ -0,0 +1,29 @@ +# Copyright 2014 Red Hat, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from neutron.agent import l3_agent + + +class TestL3NATAgent(l3_agent.L3NATAgentWithStateReport): + NESTED_NAMESPACE_SEPARATOR = '@' + + def get_ns_name(self, router_id): + ns_name = super(TestL3NATAgent, self).get_ns_name(router_id) + return "%s%s%s" % (ns_name, self.NESTED_NAMESPACE_SEPARATOR, self.host) + + def get_router_id(self, ns_name): + # 'ns_name' should be in the format of: 'qrouter-@'. + return super(TestL3NATAgent, self).get_router_id( + ns_name.split(self.NESTED_NAMESPACE_SEPARATOR)[0]) diff --git a/neutron/tests/functional/agent/linux/base.py b/neutron/tests/functional/agent/linux/base.py index ce82d5b2aa2..d667a4325b6 100644 --- a/neutron/tests/functional/agent/linux/base.py +++ b/neutron/tests/functional/agent/linux/base.py @@ -25,6 +25,18 @@ from neutron.tests.functional import base as functional_base BR_PREFIX = 'test-br' ICMP_BLOCK_RULE = '-p icmp -j DROP' +VETH_PREFIX = 'tst-vth' + + +#TODO(jschwarz): Move these two functions to neutron/tests/common/ +def get_rand_name(max_length=None, prefix='test'): + name = prefix + str(random.randint(1, 0x7fffffff)) + return name[:max_length] if max_length is not None else name + + +def get_rand_veth_name(): + return get_rand_name(max_length=n_const.DEVICE_NAME_MAX_LEN, + prefix=VETH_PREFIX) class BaseLinuxTestCase(functional_base.BaseSudoTestCase): @@ -37,10 +49,6 @@ class BaseLinuxTestCase(functional_base.BaseSudoTestCase): self.skipTest(skip_msg) raise - def get_rand_name(self, max_length=None, prefix='test'): - name = prefix + str(random.randint(1, 0x7fffffff)) - return name[:max_length] if max_length is not None else name - def create_resource(self, name_prefix, creation_func, *args, **kwargs): """Create a new resource that does not already exist. @@ -51,13 +59,21 @@ class BaseLinuxTestCase(functional_base.BaseSudoTestCase): :param *args *kwargs: These will be passed to the create function. """ while True: - name = self.get_rand_name(max_length=n_const.DEVICE_NAME_MAX_LEN, - prefix=name_prefix) + name = get_rand_name(max_length=n_const.DEVICE_NAME_MAX_LEN, + prefix=name_prefix) try: return creation_func(name, *args, **kwargs) except RuntimeError: continue + def create_veth(self): + ip_wrapper = ip_lib.IPWrapper(self.root_helper) + name1 = get_rand_veth_name() + name2 = get_rand_veth_name() + self.addCleanup(ip_wrapper.del_veth, name1) + veth1, veth2 = ip_wrapper.add_veth(name1, name2) + return veth1, veth2 + class BaseOVSLinuxTestCase(BaseLinuxTestCase): def setUp(self): @@ -69,13 +85,14 @@ class BaseOVSLinuxTestCase(BaseLinuxTestCase): self.addCleanup(br.destroy) return br + def get_ovs_bridge(self, br_name): + return ovs_lib.OVSBridge(br_name, self.root_helper) + class BaseIPVethTestCase(BaseLinuxTestCase): SRC_ADDRESS = '192.168.0.1' DST_ADDRESS = '192.168.0.2' BROADCAST_ADDRESS = '192.168.0.255' - SRC_VETH = 'source' - DST_VETH = 'destination' def setUp(self): super(BaseIPVethTestCase, self).setUp() @@ -105,8 +122,8 @@ class BaseIPVethTestCase(BaseLinuxTestCase): src_addr = src_addr or self.SRC_ADDRESS dst_addr = dst_addr or self.DST_ADDRESS broadcast_addr = broadcast_addr or self.BROADCAST_ADDRESS - src_veth = src_veth or self.SRC_VETH - dst_veth = dst_veth or self.DST_VETH + src_veth = src_veth or get_rand_veth_name() + dst_veth = dst_veth or get_rand_veth_name() src_ns = src_ns or self._create_namespace() dst_ns = dst_ns or self._create_namespace() diff --git a/neutron/tests/functional/agent/linux/test_ip_lib.py b/neutron/tests/functional/agent/linux/test_ip_lib.py index 8473bbddcad..f0b42150f80 100644 --- a/neutron/tests/functional/agent/linux/test_ip_lib.py +++ b/neutron/tests/functional/agent/linux/test_ip_lib.py @@ -49,11 +49,11 @@ class IpLibTestFramework(base.BaseLinuxTestCase): def generate_device_details(self, name=None, ip_cidr=None, mac_address=None, namespace=None): - return Device(name or self.get_rand_name(), + return Device(name or base.get_rand_name(), ip_cidr or '240.0.0.1/24', mac_address or utils.get_random_mac('fa:16:3e:00:00:00'.split(':')), - namespace or self.get_rand_name()) + namespace or base.get_rand_name()) def _safe_delete_device(self, device): try: diff --git a/neutron/tests/functional/agent/test_l3_agent.py b/neutron/tests/functional/agent/test_l3_agent.py index 77d1b15cbb7..99f1d1edd4e 100644 --- a/neutron/tests/functional/agent/test_l3_agent.py +++ b/neutron/tests/functional/agent/test_l3_agent.py @@ -15,16 +15,19 @@ import copy +import fixtures import mock from oslo.config import cfg -from neutron.agent.common import config +from neutron.agent.common import config as agent_config from neutron.agent import l3_agent from neutron.agent.linux import external_process from neutron.agent.linux import ip_lib +from neutron.common import config as common_config from neutron.common import constants as l3_constants from neutron.openstack.common import log as logging from neutron.openstack.common import uuidutils +from neutron.tests.common.agents import l3_agent as l3_test_agent from neutron.tests.functional.agent.linux import base from neutron.tests.unit import test_l3_agent @@ -36,48 +39,68 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase): def setUp(self): super(L3AgentTestFramework, self).setUp() self.check_sudo_enabled() - self._configure() + mock.patch('neutron.agent.l3_agent.L3PluginApi').start() + self.agent = self._configure_agent('agent1') - def _configure(self): - l3_agent._register_opts(cfg.CONF) + def _get_config_opts(self): + config = cfg.ConfigOpts() + config.register_opts(common_config.core_opts) + config.register_opts(common_config.core_cli_opts) + config.register_cli_opts(logging.common_cli_opts) + config.register_cli_opts(logging.logging_cli_opts) + config.register_opts(logging.generic_log_opts) + config.register_opts(logging.log_opts) + return config + + def _configure_agent(self, host): + conf = self._get_config_opts() + l3_agent._register_opts(conf) cfg.CONF.set_override('debug', False) - config.setup_logging() - cfg.CONF.set_override( + agent_config.setup_logging() + conf.set_override( 'interface_driver', 'neutron.agent.linux.interface.OVSInterfaceDriver') - cfg.CONF.set_override('router_delete_namespaces', True) - cfg.CONF.set_override('root_helper', self.root_helper, group='AGENT') - cfg.CONF.set_override('use_namespaces', True) - cfg.CONF.set_override('enable_metadata_proxy', True) + conf.set_override('router_delete_namespaces', True) + conf.set_override('root_helper', self.root_helper, group='AGENT') br_int = self.create_ovs_bridge() - cfg.CONF.set_override('ovs_integration_bridge', br_int.br_name) br_ex = self.create_ovs_bridge() - cfg.CONF.set_override('external_network_bridge', br_ex.br_name) + conf.set_override('ovs_integration_bridge', br_int.br_name) + conf.set_override('external_network_bridge', br_ex.br_name) - mock.patch('neutron.agent.l3_agent.L3PluginApi').start() + temp_dir = self.useFixture(fixtures.TempDir()).path + conf.set_override('state_path', temp_dir) + conf.set_override('metadata_proxy_socket', + '%s/metadata_proxy' % temp_dir) + conf.set_override('ha_confs_path', + '%s/ha_confs' % temp_dir) + conf.set_override('external_pids', + '%s/external/pids' % temp_dir) + conf.set_override('host', host) + agent = l3_test_agent.TestL3NATAgent(host, conf) + mock.patch.object(agent, '_arping').start() - self.agent = l3_agent.L3NATAgent('localhost', cfg.CONF) + return agent - mock.patch.object(self.agent, '_send_gratuitous_arp_packet').start() + def generate_router_info(self, enable_ha): + return test_l3_agent.prepare_router_data(enable_snat=True, + enable_floating_ip=True, + enable_ha=enable_ha) - def manage_router(self, enable_ha): - router = test_l3_agent.prepare_router_data(enable_snat=True, - enable_floating_ip=True, - enable_ha=enable_ha) - self.addCleanup(self._delete_router, router['id']) - ri = self._create_router(router) + def manage_router(self, agent, router): + self.addCleanup(self._delete_router, agent, router['id']) + ri = self._create_router(agent, router) return ri - def _create_router(self, router): - self.agent._router_added(router['id'], router) - ri = self.agent.router_info[router['id']] + def _create_router(self, agent, router): + agent._router_added(router['id'], router) + ri = agent.router_info[router['id']] ri.router = router - self.agent.process_router(ri) + agent.process_router(ri) return ri - def _delete_router(self, router_id): - self.agent._router_removed(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'): fip = {'id': _uuid(), @@ -90,9 +113,9 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase): ip = ip_lib.IPWrapper(self.root_helper, router.ns_name) return ip.netns.exists(router.ns_name) - def _metadata_proxy_exists(self, router): + def _metadata_proxy_exists(self, conf, router): pm = external_process.ProcessManager( - cfg.CONF, + conf, router.router_id, self.root_helper, router.ns_name) @@ -107,7 +130,7 @@ class L3AgentTestFramework(base.BaseOVSLinuxTestCase): namespace, self.root_helper) def get_expected_keepalive_configuration(self, router): - ha_confs_path = cfg.CONF.ha_confs_path + ha_confs_path = self.agent.conf.ha_confs_path router_id = router.router_id ha_device_name = self.agent.get_ha_device_name(router.ha_port['id']) ha_device_cidr = router.ha_port['ip_cidr'] @@ -174,7 +197,8 @@ class L3AgentTestCase(L3AgentTestFramework): self._router_lifecycle(enable_ha=True) def test_keepalived_configuration(self): - router = self.manage_router(enable_ha=True) + 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, @@ -205,7 +229,8 @@ class L3AgentTestCase(L3AgentTestFramework): self.assertIn(new_external_device_ip, new_config) def _router_lifecycle(self, enable_ha): - router = self.manage_router(enable_ha) + router_info = self.generate_router_info(enable_ha) + router = self.manage_router(self.agent, router_info) if enable_ha: self.wait_until(lambda: router.ha_state == 'master') @@ -220,7 +245,7 @@ class L3AgentTestCase(L3AgentTestFramework): router.ns_name) self.assertTrue(self._namespace_exists(router)) - self.assertTrue(self._metadata_proxy_exists(router)) + self.assertTrue(self._metadata_proxy_exists(self.agent.conf, router)) self._assert_internal_devices(router) self._assert_external_device(router) self._assert_gateway(router) @@ -232,7 +257,7 @@ class L3AgentTestCase(L3AgentTestFramework): self._assert_ha_device(router) self.assertTrue(router.keepalived_manager.process.active) - self._delete_router(router.router_id) + self._delete_router(self.agent, router.router_id) self._assert_router_does_not_exist(router) if enable_ha: @@ -289,9 +314,47 @@ class L3AgentTestCase(L3AgentTestFramework): # 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)) - self.assertFalse(self._metadata_proxy_exists(router)) + self.assertFalse(self._metadata_proxy_exists(self.agent.conf, router)) def _assert_ha_device(self, router): self.assertTrue(self.device_exists_with_ip_mac( router.router[l3_constants.HA_INTERFACE_KEY], self.agent.get_ha_device_name, router.ns_name)) + + +class L3HATestFramework(L3AgentTestFramework): + def setUp(self): + super(L3HATestFramework, self).setUp() + self.failover_agent = self._configure_agent('agent2') + + br_int_1 = self.get_ovs_bridge( + self.agent.conf.ovs_integration_bridge) + br_int_2 = self.get_ovs_bridge( + self.failover_agent.conf.ovs_integration_bridge) + + veth1, veth2 = self.create_veth() + 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) + router1 = self.manage_router(self.agent, router_info) + + router_info_2 = copy.deepcopy(router_info) + router_info_2[l3_constants.HA_INTERFACE_KEY] = ( + test_l3_agent.get_ha_interface(ip='169.254.0.3', + mac='22:22:22:22:22:22')) + + router2 = self.manage_router(self.failover_agent, router_info_2) + + self.wait_until(lambda: router1.ha_state == 'master') + self.wait_until(lambda: router2.ha_state == 'backup') + + device_name = self.agent.get_ha_device_name( + router1.router[l3_constants.HA_INTERFACE_KEY]['id']) + ha_device = ip_lib.IPDevice(device_name, self.root_helper, + router1.ns_name) + ha_device.link.set_down() + + self.wait_until(lambda: router2.ha_state == 'master') + self.wait_until(lambda: router1.ha_state == 'fault') diff --git a/neutron/tests/unit/test_l3_agent.py b/neutron/tests/unit/test_l3_agent.py index 3a5424cbb3f..c44fc43b040 100644 --- a/neutron/tests/unit/test_l3_agent.py +++ b/neutron/tests/unit/test_l3_agent.py @@ -283,14 +283,17 @@ def _get_subnet_id(port): return port['fixed_ips'][0]['subnet_id'] -def get_ha_interface(): +#TODO(jschwarz): This is a shared function with both the unit tests +# and the functional tests, and should be moved elsewhere (probably +# neutron/tests/common/). +def get_ha_interface(ip='169.254.0.2', mac='12:34:56:78:2b:5d'): return {'admin_state_up': True, 'device_id': _uuid(), 'device_owner': 'network:router_ha_interface', - 'fixed_ips': [{'ip_address': '169.254.0.2', + 'fixed_ips': [{'ip_address': ip, 'subnet_id': _uuid()}], 'id': _uuid(), - 'mac_address': '12:34:56:78:2b:5d', + 'mac_address': mac, 'name': u'L3 HA Admin port 0', 'network_id': _uuid(), 'status': u'ACTIVE',