For DVR, FIPs should be hosted on the single node which hosts the VM assigned with the fixed_ip of the FIP. The l3_agent should only take action on the correct FIP per host by filtering the FIPs based on the 'host' value of the FIP. A recent refactor on the l3_agent moved the host filtering logic from process_router_floating_ip_addresses() to _get_external_device_interface_name(). The local floating_ips var was not altered as it was before the refactor. This resulted in network disruption across multiple hosts since more than one namespace contained the FIP. This problem would only be seen in a mutli-host environment where the same router hosting FIPs was present on more than one node. The fix is to return the host filtering logic by adding a call to get_floating_ips(). In addition, the unit test test_process_router_dist_floating_ip_add() was modified to pass two FIPs instead of one. One FIP matches the host of the agent, one does not. Only one should be processed, not two. Change-Id: I67b19f6228af392519fff89b13283b43921552bf Closes-bug: #1389880
2359 lines
101 KiB
Python
2359 lines
101 KiB
Python
# Copyright 2012 VMware, 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 contextlib
|
|
import copy
|
|
import datetime
|
|
|
|
import mock
|
|
import netaddr
|
|
from oslo.config import cfg
|
|
from oslo import messaging
|
|
from testtools import matchers
|
|
|
|
from neutron.agent.common import config as agent_config
|
|
from neutron.agent import l3_agent
|
|
from neutron.agent import l3_ha_agent
|
|
from neutron.agent.linux import interface
|
|
from neutron.common import config as base_config
|
|
from neutron.common import constants as l3_constants
|
|
from neutron.common import exceptions as n_exc
|
|
from neutron.common import rpc as n_rpc
|
|
from neutron.openstack.common import processutils
|
|
from neutron.openstack.common import uuidutils
|
|
from neutron.plugins.common import constants as p_const
|
|
from neutron.tests import base
|
|
|
|
|
|
_uuid = uuidutils.generate_uuid
|
|
HOSTNAME = 'myhost'
|
|
FAKE_ID = _uuid()
|
|
FAKE_ID_2 = _uuid()
|
|
FIP_PRI = 32768
|
|
|
|
|
|
class TestExclusiveRouterProcessor(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(TestExclusiveRouterProcessor, self).setUp()
|
|
|
|
def test_i_am_master(self):
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
not_master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
master_2 = l3_agent.ExclusiveRouterProcessor(FAKE_ID_2)
|
|
not_master_2 = l3_agent.ExclusiveRouterProcessor(FAKE_ID_2)
|
|
|
|
self.assertTrue(master._i_am_master())
|
|
self.assertFalse(not_master._i_am_master())
|
|
self.assertTrue(master_2._i_am_master())
|
|
self.assertFalse(not_master_2._i_am_master())
|
|
|
|
master.__exit__(None, None, None)
|
|
master_2.__exit__(None, None, None)
|
|
|
|
def test_master(self):
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
not_master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
master_2 = l3_agent.ExclusiveRouterProcessor(FAKE_ID_2)
|
|
not_master_2 = l3_agent.ExclusiveRouterProcessor(FAKE_ID_2)
|
|
|
|
self.assertEqual(master._master, master)
|
|
self.assertEqual(not_master._master, master)
|
|
self.assertEqual(master_2._master, master_2)
|
|
self.assertEqual(not_master_2._master, master_2)
|
|
|
|
master.__exit__(None, None, None)
|
|
master_2.__exit__(None, None, None)
|
|
|
|
def test__enter__(self):
|
|
self.assertFalse(FAKE_ID in l3_agent.ExclusiveRouterProcessor._masters)
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
master.__enter__()
|
|
self.assertTrue(FAKE_ID in l3_agent.ExclusiveRouterProcessor._masters)
|
|
master.__exit__(None, None, None)
|
|
|
|
def test__exit__(self):
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
not_master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
master.__enter__()
|
|
self.assertTrue(FAKE_ID in l3_agent.ExclusiveRouterProcessor._masters)
|
|
not_master.__enter__()
|
|
not_master.__exit__(None, None, None)
|
|
self.assertTrue(FAKE_ID in l3_agent.ExclusiveRouterProcessor._masters)
|
|
master.__exit__(None, None, None)
|
|
self.assertFalse(FAKE_ID in l3_agent.ExclusiveRouterProcessor._masters)
|
|
|
|
def test_data_fetched_since(self):
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
self.assertEqual(master._get_router_data_timestamp(),
|
|
datetime.datetime.min)
|
|
|
|
ts1 = datetime.datetime.utcnow() - datetime.timedelta(seconds=10)
|
|
ts2 = datetime.datetime.utcnow()
|
|
|
|
master.fetched_and_processed(ts2)
|
|
self.assertEqual(master._get_router_data_timestamp(), ts2)
|
|
master.fetched_and_processed(ts1)
|
|
self.assertEqual(master._get_router_data_timestamp(), ts2)
|
|
|
|
master.__exit__(None, None, None)
|
|
|
|
def test_updates(self):
|
|
master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
not_master = l3_agent.ExclusiveRouterProcessor(FAKE_ID)
|
|
|
|
master.queue_update(l3_agent.RouterUpdate(FAKE_ID, 0))
|
|
not_master.queue_update(l3_agent.RouterUpdate(FAKE_ID, 0))
|
|
|
|
for update in not_master.updates():
|
|
raise Exception("Only the master should process a router")
|
|
|
|
self.assertEqual(2, len([i for i in master.updates()]))
|
|
|
|
|
|
class TestLinkLocalAddrAllocator(base.BaseTestCase):
|
|
def setUp(self):
|
|
super(TestLinkLocalAddrAllocator, self).setUp()
|
|
self.subnet = netaddr.IPNetwork('169.254.31.0/24')
|
|
|
|
def test__init__(self):
|
|
a = l3_agent.LinkLocalAllocator('/file', self.subnet.cidr)
|
|
self.assertEqual('/file', a.state_file)
|
|
self.assertEqual({}, a.allocations)
|
|
|
|
def test__init__readfile(self):
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_read') as read:
|
|
read.return_value = ["da873ca2,169.254.31.28/31\n"]
|
|
a = l3_agent.LinkLocalAllocator('/file', self.subnet.cidr)
|
|
|
|
self.assertTrue('da873ca2' in a.remembered)
|
|
self.assertEqual({}, a.allocations)
|
|
|
|
def test_allocate(self):
|
|
a = l3_agent.LinkLocalAllocator('/file', self.subnet.cidr)
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write') as write:
|
|
subnet = a.allocate('deadbeef')
|
|
|
|
self.assertTrue('deadbeef' in a.allocations)
|
|
self.assertTrue(subnet not in a.pool)
|
|
self._check_allocations(a.allocations)
|
|
write.assert_called_once_with(['deadbeef,%s\n' % subnet.cidr])
|
|
|
|
def test_allocate_from_file(self):
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_read') as read:
|
|
read.return_value = ["deadbeef,169.254.31.88/31\n"]
|
|
a = l3_agent.LinkLocalAllocator('/file', self.subnet.cidr)
|
|
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write') as write:
|
|
subnet = a.allocate('deadbeef')
|
|
|
|
self.assertEqual(netaddr.IPNetwork('169.254.31.88/31'), subnet)
|
|
self.assertTrue(subnet not in a.pool)
|
|
self._check_allocations(a.allocations)
|
|
self.assertFalse(write.called)
|
|
|
|
def test_allocate_exhausted_pool(self):
|
|
subnet = netaddr.IPNetwork('169.254.31.0/31')
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_read') as read:
|
|
read.return_value = ["deadbeef,169.254.31.0/31\n"]
|
|
a = l3_agent.LinkLocalAllocator('/file', subnet.cidr)
|
|
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write') as write:
|
|
allocation = a.allocate('abcdef12')
|
|
|
|
self.assertEqual(subnet, allocation)
|
|
self.assertFalse('deadbeef' in a.allocations)
|
|
self.assertTrue('abcdef12' in a.allocations)
|
|
self.assertTrue(allocation not in a.pool)
|
|
self._check_allocations(a.allocations)
|
|
write.assert_called_once_with(['abcdef12,%s\n' % allocation.cidr])
|
|
|
|
self.assertRaises(RuntimeError, a.allocate, 'deadbeef')
|
|
|
|
def test_release(self):
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write') as write:
|
|
a = l3_agent.LinkLocalAllocator('/file', self.subnet.cidr)
|
|
subnet = a.allocate('deadbeef')
|
|
write.reset_mock()
|
|
a.release('deadbeef')
|
|
|
|
self.assertTrue('deadbeef' not in a.allocations)
|
|
self.assertTrue(subnet in a.pool)
|
|
self.assertEqual({}, a.allocations)
|
|
write.assert_called_once_with([])
|
|
|
|
def _check_allocations(self, allocations):
|
|
for key, subnet in allocations.items():
|
|
self.assertTrue(subnet in self.subnet)
|
|
self.assertEqual(subnet.prefixlen, 31)
|
|
|
|
|
|
def router_append_interface(router, count=1, ip_version=4, ra_mode=None,
|
|
addr_mode=None):
|
|
if ip_version == 4:
|
|
ip_pool = '35.4.%i.4'
|
|
cidr_pool = '35.4.%i.0/24'
|
|
gw_pool = '35.4.%i.1'
|
|
elif ip_version == 6:
|
|
ip_pool = 'fd01:%x::6'
|
|
cidr_pool = 'fd01:%x::/64'
|
|
gw_pool = 'fd01:%x::1'
|
|
else:
|
|
raise ValueError("Invalid ip_version: %s" % ip_version)
|
|
|
|
interfaces = router[l3_constants.INTERFACE_KEY]
|
|
current = sum(
|
|
[netaddr.IPNetwork(p['subnet']['cidr']).version == ip_version
|
|
for p in interfaces])
|
|
|
|
for i in range(current, current + count):
|
|
interfaces.append(
|
|
{'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'admin_state_up': True,
|
|
'fixed_ips': [{'ip_address': ip_pool % i,
|
|
'subnet_id': _uuid()}],
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'subnet': {'cidr': cidr_pool % i,
|
|
'gateway_ip': gw_pool % i,
|
|
'ipv6_ra_mode': ra_mode,
|
|
'ipv6_address_mode': addr_mode}})
|
|
|
|
|
|
def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1,
|
|
enable_floating_ip=False, enable_ha=False):
|
|
if ip_version == 4:
|
|
ip_addr = '19.4.4.4'
|
|
cidr = '19.4.4.0/24'
|
|
gateway_ip = '19.4.4.1'
|
|
elif ip_version == 6:
|
|
ip_addr = 'fd00::4'
|
|
cidr = 'fd00::/64'
|
|
gateway_ip = 'fd00::1'
|
|
else:
|
|
raise ValueError("Invalid ip_version: %s" % ip_version)
|
|
|
|
router_id = _uuid()
|
|
ex_gw_port = {'id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'network_id': _uuid(),
|
|
'fixed_ips': [{'ip_address': ip_addr,
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'cidr': cidr,
|
|
'gateway_ip': gateway_ip}}
|
|
|
|
router = {
|
|
'id': router_id,
|
|
'distributed': False,
|
|
l3_constants.INTERFACE_KEY: [],
|
|
'routes': [],
|
|
'gw_port': ex_gw_port}
|
|
|
|
if enable_floating_ip:
|
|
router[l3_constants.FLOATINGIP_KEY] = [{
|
|
'id': _uuid(),
|
|
'port_id': _uuid(),
|
|
'floating_ip_address': '19.4.4.2',
|
|
'fixed_ip_address': '10.0.0.1'}]
|
|
|
|
router_append_interface(router, count=num_internal_ports,
|
|
ip_version=ip_version)
|
|
if enable_ha:
|
|
router['ha'] = True
|
|
router['ha_vr_id'] = 1
|
|
router[l3_constants.HA_INTERFACE_KEY] = get_ha_interface()
|
|
|
|
if enable_snat is not None:
|
|
router['enable_snat'] = enable_snat
|
|
return router
|
|
|
|
|
|
def _get_subnet_id(port):
|
|
return port['fixed_ips'][0]['subnet_id']
|
|
|
|
|
|
def get_ha_interface():
|
|
return {'admin_state_up': True,
|
|
'device_id': _uuid(),
|
|
'device_owner': 'network:router_ha_interface',
|
|
'fixed_ips': [{'ip_address': '169.254.0.2',
|
|
'subnet_id': _uuid()}],
|
|
'id': _uuid(),
|
|
'mac_address': '12:34:56:78:2b:5d',
|
|
'name': u'L3 HA Admin port 0',
|
|
'network_id': _uuid(),
|
|
'status': u'ACTIVE',
|
|
'subnet': {'cidr': '169.254.0.0/24',
|
|
'gateway_ip': '169.254.0.1',
|
|
'id': _uuid()},
|
|
'tenant_id': '',
|
|
'agent_id': _uuid(),
|
|
'agent_host': 'aaa',
|
|
'priority': 1}
|
|
|
|
|
|
class TestBasicRouterOperations(base.BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestBasicRouterOperations, self).setUp()
|
|
self.conf = agent_config.setup_conf()
|
|
self.conf.register_opts(base_config.core_opts)
|
|
self.conf.register_opts(l3_agent.L3NATAgent.OPTS)
|
|
self.conf.register_opts(l3_ha_agent.OPTS)
|
|
agent_config.register_interface_driver_opts_helper(self.conf)
|
|
agent_config.register_use_namespaces_opts_helper(self.conf)
|
|
agent_config.register_root_helper(self.conf)
|
|
self.conf.register_opts(interface.OPTS)
|
|
self.conf.set_override('router_id', 'fake_id')
|
|
self.conf.set_override('interface_driver',
|
|
'neutron.agent.linux.interface.NullDriver')
|
|
self.conf.set_override('send_arp_for_ha', 1)
|
|
self.conf.set_override('state_path', '')
|
|
self.conf.root_helper = 'sudo'
|
|
|
|
self.device_exists_p = mock.patch(
|
|
'neutron.agent.linux.ip_lib.device_exists')
|
|
self.device_exists = self.device_exists_p.start()
|
|
|
|
mock.patch('neutron.agent.l3_ha_agent.AgentMixin'
|
|
'._init_ha_conf_path').start()
|
|
mock.patch('neutron.agent.linux.keepalived.KeepalivedNotifierMixin'
|
|
'._get_full_config_file_path').start()
|
|
|
|
self.utils_exec_p = mock.patch(
|
|
'neutron.agent.linux.utils.execute')
|
|
self.utils_exec = self.utils_exec_p.start()
|
|
|
|
self.utils_replace_file_p = mock.patch(
|
|
'neutron.agent.linux.utils.replace_file')
|
|
self.utils_replace_file = self.utils_replace_file_p.start()
|
|
|
|
self.external_process_p = mock.patch(
|
|
'neutron.agent.linux.external_process.ProcessManager')
|
|
self.external_process = self.external_process_p.start()
|
|
|
|
self.send_arp_p = mock.patch(
|
|
'neutron.agent.l3_agent.L3NATAgent._send_gratuitous_arp_packet')
|
|
self.send_arp = self.send_arp_p.start()
|
|
|
|
self.dvr_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
|
|
driver_cls = self.dvr_cls_p.start()
|
|
self.mock_driver = mock.MagicMock()
|
|
self.mock_driver.DEV_NAME_LEN = (
|
|
interface.LinuxInterfaceDriver.DEV_NAME_LEN)
|
|
driver_cls.return_value = self.mock_driver
|
|
|
|
self.ip_cls_p = mock.patch('neutron.agent.linux.ip_lib.IPWrapper')
|
|
ip_cls = self.ip_cls_p.start()
|
|
self.mock_ip = mock.MagicMock()
|
|
ip_cls.return_value = self.mock_ip
|
|
|
|
ip_rule = mock.patch('neutron.agent.linux.ip_lib.IpRule').start()
|
|
self.mock_rule = mock.MagicMock()
|
|
ip_rule.return_value = self.mock_rule
|
|
|
|
ip_dev = mock.patch('neutron.agent.linux.ip_lib.IPDevice').start()
|
|
self.mock_ip_dev = mock.MagicMock()
|
|
ip_dev.return_value = self.mock_ip_dev
|
|
|
|
self.l3pluginApi_cls_p = mock.patch(
|
|
'neutron.agent.l3_agent.L3PluginApi')
|
|
l3pluginApi_cls = self.l3pluginApi_cls_p.start()
|
|
self.plugin_api = mock.MagicMock()
|
|
l3pluginApi_cls.return_value = self.plugin_api
|
|
|
|
self.looping_call_p = mock.patch(
|
|
'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
|
|
self.looping_call_p.start()
|
|
|
|
self.snat_ports = [{'subnet': {'cidr': '152.2.0.0/16',
|
|
'gateway_ip': '152.2.0.1',
|
|
'id': _uuid()},
|
|
'network_id': _uuid(),
|
|
'device_owner': 'network:router_centralized_snat',
|
|
'ip_cidr': '152.2.0.13/16',
|
|
'mac_address': 'fa:16:3e:80:8d:80',
|
|
'fixed_ips': [{'subnet_id': _uuid(),
|
|
'ip_address': '152.2.0.13'}],
|
|
'id': _uuid(), 'device_id': _uuid()},
|
|
{'subnet': {'cidr': '152.10.0.0/16',
|
|
'gateway_ip': '152.10.0.1',
|
|
'id': _uuid()},
|
|
'network_id': _uuid(),
|
|
'device_owner': 'network:router_centralized_snat',
|
|
'ip_cidr': '152.10.0.13/16',
|
|
'mac_address': 'fa:16:3e:80:8d:80',
|
|
'fixed_ips': [{'subnet_id': _uuid(),
|
|
'ip_address': '152.10.0.13'}],
|
|
'id': _uuid(), 'device_id': _uuid()}]
|
|
|
|
def _prepare_internal_network_data(self):
|
|
port_id = _uuid()
|
|
router_id = _uuid()
|
|
network_id = _uuid()
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
router_id = router['id']
|
|
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper,
|
|
router=router)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
cidr = '99.0.1.9/24'
|
|
mac = 'ca:fe:de:ad:be:ef'
|
|
port = {'network_id': network_id,
|
|
'id': port_id, 'ip_cidr': cidr,
|
|
'mac_address': mac}
|
|
|
|
return agent, ri, port
|
|
|
|
def test__sync_routers_task_raise_exception(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_routers.side_effect = Exception()
|
|
with mock.patch.object(agent, '_cleanup_namespaces') as f:
|
|
agent._sync_routers_task(agent.context)
|
|
self.assertFalse(f.called)
|
|
|
|
def test__sync_routers_task_call_clean_stale_namespaces(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_routers.return_value = []
|
|
with mock.patch.object(agent, '_cleanup_namespaces') as f:
|
|
agent._sync_routers_task(agent.context)
|
|
self.assertTrue(f.called)
|
|
|
|
def test_router_info_create(self):
|
|
id = _uuid()
|
|
ns = "ns-" + id
|
|
ri = l3_agent.RouterInfo(id, self.conf.root_helper, {}, ns_name=ns)
|
|
|
|
self.assertTrue(ri.ns_name.endswith(id))
|
|
|
|
def test_router_info_create_with_router(self):
|
|
id = _uuid()
|
|
ex_gw_port = {'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'fixed_ips': [{'ip_address': '19.4.4.4',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'cidr': '19.4.4.0/24',
|
|
'gateway_ip': '19.4.4.1'}}
|
|
router = {
|
|
'id': _uuid(),
|
|
'enable_snat': True,
|
|
'routes': [],
|
|
'gw_port': ex_gw_port}
|
|
ns = "ns-" + id
|
|
ri = l3_agent.RouterInfo(id, self.conf.root_helper, router, ns_name=ns)
|
|
self.assertTrue(ri.ns_name.endswith(id))
|
|
self.assertEqual(ri.router, router)
|
|
|
|
def test_agent_create(self):
|
|
l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
def _test_internal_network_action(self, action):
|
|
agent, ri, port = self._prepare_internal_network_data()
|
|
interface_name = agent.get_internal_device_name(port['id'])
|
|
|
|
if action == 'add':
|
|
self.device_exists.return_value = False
|
|
agent.internal_network_added(ri, port)
|
|
self.assertEqual(self.mock_driver.plug.call_count, 1)
|
|
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
|
|
self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
|
|
'99.0.1.9')
|
|
elif action == 'remove':
|
|
self.device_exists.return_value = True
|
|
agent.internal_network_removed(ri, port)
|
|
self.assertEqual(self.mock_driver.unplug.call_count, 1)
|
|
else:
|
|
raise Exception("Invalid action %s" % action)
|
|
|
|
def _test_internal_network_action_dist(self, action):
|
|
agent, ri, port = self._prepare_internal_network_data()
|
|
ri.router['distributed'] = True
|
|
ri.router['gw_port_host'] = HOSTNAME
|
|
agent.host = HOSTNAME
|
|
agent.conf.agent_mode = 'dvr_snat'
|
|
sn_port = {'fixed_ips': [{'ip_address': '20.0.0.31',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.31/24'}
|
|
|
|
if action == 'add':
|
|
self.device_exists.return_value = False
|
|
|
|
agent._map_internal_interfaces = mock.Mock(return_value=sn_port)
|
|
agent._snat_redirect_add = mock.Mock()
|
|
agent._set_subnet_info = mock.Mock()
|
|
agent._internal_network_added = mock.Mock()
|
|
agent.internal_network_added(ri, port)
|
|
self.assertEqual(agent._snat_redirect_add.call_count, 1)
|
|
self.assertEqual(agent._set_subnet_info.call_count, 1)
|
|
self.assertEqual(agent._internal_network_added.call_count, 2)
|
|
agent._internal_network_added.assert_called_with(
|
|
agent.get_snat_ns_name(ri.router['id']),
|
|
sn_port['network_id'],
|
|
sn_port['id'],
|
|
sn_port['ip_cidr'],
|
|
sn_port['mac_address'],
|
|
agent.get_snat_int_device_name(sn_port['id']),
|
|
l3_agent.SNAT_INT_DEV_PREFIX)
|
|
|
|
def test_agent_add_internal_network(self):
|
|
self._test_internal_network_action('add')
|
|
|
|
def test_agent_add_internal_network_dist(self):
|
|
self._test_internal_network_action_dist('add')
|
|
|
|
def test_agent_remove_internal_network(self):
|
|
self._test_internal_network_action('remove')
|
|
|
|
def _test_external_gateway_action(self, action, router):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router,
|
|
ns_name=agent.get_ns_name(router['id']))
|
|
# Special setup for dvr routers
|
|
if router.get('distributed'):
|
|
agent.conf.agent_mode = 'dvr_snat'
|
|
agent.host = HOSTNAME
|
|
agent._create_dvr_gateway = mock.Mock()
|
|
agent.get_snat_interfaces = mock.Mock(return_value=self.snat_ports)
|
|
|
|
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
interface_name = agent.get_external_device_name(ex_gw_port['id'])
|
|
|
|
if action == 'add':
|
|
self.device_exists.return_value = False
|
|
fake_fip = {'floatingips': [{'id': _uuid(),
|
|
'floating_ip_address': '192.168.1.34',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'port_id': _uuid()}]}
|
|
router[l3_constants.FLOATINGIP_KEY] = fake_fip['floatingips']
|
|
agent.external_gateway_added(ri, ex_gw_port, interface_name)
|
|
if not router.get('distributed'):
|
|
self.assertEqual(self.mock_driver.plug.call_count, 1)
|
|
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
|
|
self.send_arp.assert_called_once_with(ri.ns_name,
|
|
interface_name,
|
|
'20.0.0.30')
|
|
kwargs = {'preserve_ips': ['192.168.1.34/32'],
|
|
'namespace': 'qrouter-' + router['id'],
|
|
'gateway': '20.0.0.1',
|
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}]}
|
|
self.mock_driver.init_l3.assert_called_with(interface_name,
|
|
['20.0.0.30/24'],
|
|
**kwargs)
|
|
else:
|
|
agent._create_dvr_gateway.assert_called_once_with(
|
|
ri, ex_gw_port, interface_name,
|
|
self.snat_ports)
|
|
|
|
elif action == 'remove':
|
|
self.device_exists.return_value = True
|
|
agent.external_gateway_removed(ri, ex_gw_port, interface_name)
|
|
self.assertEqual(self.mock_driver.unplug.call_count, 1)
|
|
else:
|
|
raise Exception("Invalid action %s" % action)
|
|
|
|
def _prepare_ext_gw_test(self, agent):
|
|
ex_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}],
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
interface_name = agent.get_external_device_name(ex_gw_port['id'])
|
|
|
|
self.device_exists.return_value = True
|
|
|
|
return interface_name, ex_gw_port
|
|
|
|
def test_external_gateway_updated(self):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router,
|
|
ns_name=agent.get_ns_name(router['id']))
|
|
interface_name, ex_gw_port = self._prepare_ext_gw_test(agent)
|
|
|
|
fake_fip = {'floatingips': [{'id': _uuid(),
|
|
'floating_ip_address': '192.168.1.34',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'port_id': _uuid()}]}
|
|
router[l3_constants.FLOATINGIP_KEY] = fake_fip['floatingips']
|
|
agent.external_gateway_updated(ri, ex_gw_port,
|
|
interface_name)
|
|
self.assertEqual(self.mock_driver.plug.call_count, 0)
|
|
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
|
|
self.send_arp.assert_called_once_with(ri.ns_name, interface_name,
|
|
'20.0.0.30')
|
|
kwargs = {'preserve_ips': ['192.168.1.34/32'],
|
|
'namespace': 'qrouter-' + router['id'],
|
|
'gateway': '20.0.0.1',
|
|
'extra_subnets': [{'cidr': '172.16.0.0/24'}]}
|
|
self.mock_driver.init_l3.assert_called_with(interface_name,
|
|
['20.0.0.30/24'],
|
|
**kwargs)
|
|
|
|
def _test_ext_gw_updated_dvr_agent_mode(self, host,
|
|
agent_mode, expected_call_count):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
interface_name, ex_gw_port = self._prepare_ext_gw_test(agent)
|
|
agent._external_gateway_added = mock.Mock()
|
|
|
|
# test agent mode = dvr (compute node)
|
|
router['distributed'] = True
|
|
router['gw_port_host'] = host
|
|
agent.conf.agent_mode = agent_mode
|
|
|
|
agent.external_gateway_updated(ri, ex_gw_port,
|
|
interface_name)
|
|
# no gateway should be added on dvr node
|
|
self.assertEqual(expected_call_count,
|
|
agent._external_gateway_added.call_count)
|
|
|
|
def test_ext_gw_updated_dvr_agent_mode(self):
|
|
# no gateway should be added on dvr node
|
|
self._test_ext_gw_updated_dvr_agent_mode('any-foo', 'dvr', 0)
|
|
|
|
def test_ext_gw_updated_dvr_snat_agent_mode_no_host(self):
|
|
# no gateway should be added on dvr_snat node without host match
|
|
self._test_ext_gw_updated_dvr_agent_mode('any-foo', 'dvr_snat', 0)
|
|
|
|
def test_ext_gw_updated_dvr_snat_agent_mode_host(self):
|
|
# gateway should be added on dvr_snat node
|
|
self._test_ext_gw_updated_dvr_agent_mode(self.conf.host,
|
|
'dvr_snat', 1)
|
|
|
|
def test_agent_add_external_gateway(self):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
self._test_external_gateway_action('add', router)
|
|
|
|
def test_agent_add_external_gateway_dist(self):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
router['distributed'] = True
|
|
router['gw_port_host'] = HOSTNAME
|
|
self._test_external_gateway_action('add', router)
|
|
|
|
def _test_arping(self, namespace):
|
|
if not namespace:
|
|
self.conf.set_override('use_namespaces', False)
|
|
|
|
router_id = _uuid()
|
|
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, {})
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
floating_ip = '20.0.0.101'
|
|
interface_name = agent.get_external_device_name(router_id)
|
|
agent._arping(ri, interface_name, floating_ip)
|
|
|
|
arping_cmd = ['arping', '-A',
|
|
'-I', interface_name,
|
|
'-c', self.conf.send_arp_for_ha,
|
|
floating_ip]
|
|
self.mock_ip.netns.execute.assert_any_call(
|
|
arping_cmd, check_exit_code=True)
|
|
|
|
def test_arping_namespace(self):
|
|
self._test_arping(namespace=True)
|
|
|
|
def test_arping_no_namespace(self):
|
|
self._test_arping(namespace=False)
|
|
|
|
def test_agent_remove_external_gateway(self):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
self._test_external_gateway_action('remove', router)
|
|
|
|
def test_agent_remove_external_gateway_dist(self):
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
router['distributed'] = True
|
|
router['gw_port_host'] = HOSTNAME
|
|
self._test_external_gateway_action('remove', router)
|
|
|
|
def _check_agent_method_called(self, agent, calls, namespace):
|
|
self.mock_ip.netns.execute.assert_has_calls(
|
|
[mock.call(call, check_exit_code=False) for call in calls],
|
|
any_order=True)
|
|
|
|
def _test_routing_table_update(self, namespace):
|
|
if not namespace:
|
|
self.conf.set_override('use_namespaces', False)
|
|
|
|
router_id = _uuid()
|
|
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, {})
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
fake_route1 = {'destination': '135.207.0.0/16',
|
|
'nexthop': '1.2.3.4'}
|
|
fake_route2 = {'destination': '135.207.111.111/32',
|
|
'nexthop': '1.2.3.4'}
|
|
|
|
agent._update_routing_table(ri, 'replace', fake_route1)
|
|
expected = [['ip', 'route', 'replace', 'to', '135.207.0.0/16',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
agent._update_routing_table(ri, 'delete', fake_route1)
|
|
expected = [['ip', 'route', 'delete', 'to', '135.207.0.0/16',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
agent._update_routing_table(ri, 'replace', fake_route2)
|
|
expected = [['ip', 'route', 'replace', 'to', '135.207.111.111/32',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
agent._update_routing_table(ri, 'delete', fake_route2)
|
|
expected = [['ip', 'route', 'delete', 'to', '135.207.111.111/32',
|
|
'via', '1.2.3.4']]
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
def test_agent_routing_table_updated(self):
|
|
self._test_routing_table_update(namespace=True)
|
|
|
|
def test_agent_routing_table_updated_no_namespace(self):
|
|
self._test_routing_table_update(namespace=False)
|
|
|
|
def test_routes_updated(self):
|
|
self._test_routes_updated(namespace=True)
|
|
|
|
def test_routes_updated_no_namespace(self):
|
|
self._test_routes_updated(namespace=False)
|
|
|
|
def _test_routes_updated(self, namespace=True):
|
|
if not namespace:
|
|
self.conf.set_override('use_namespaces', False)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
|
|
ri = l3_agent.RouterInfo(router_id, self.conf.root_helper, {})
|
|
ri.router = {}
|
|
|
|
fake_old_routes = []
|
|
fake_new_routes = [{'destination': "110.100.31.0/24",
|
|
'nexthop': "10.100.10.30"},
|
|
{'destination': "110.100.30.0/24",
|
|
'nexthop': "10.100.10.30"}]
|
|
ri.routes = fake_old_routes
|
|
ri.router['routes'] = fake_new_routes
|
|
agent.routes_updated(ri)
|
|
|
|
expected = [['ip', 'route', 'replace', 'to', '110.100.30.0/24',
|
|
'via', '10.100.10.30'],
|
|
['ip', 'route', 'replace', 'to', '110.100.31.0/24',
|
|
'via', '10.100.10.30']]
|
|
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
fake_new_routes = [{'destination': "110.100.30.0/24",
|
|
'nexthop': "10.100.10.30"}]
|
|
ri.router['routes'] = fake_new_routes
|
|
agent.routes_updated(ri)
|
|
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
|
|
'via', '10.100.10.30']]
|
|
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
fake_new_routes = []
|
|
ri.router['routes'] = fake_new_routes
|
|
agent.routes_updated(ri)
|
|
|
|
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
|
|
'via', '10.100.10.30']]
|
|
self._check_agent_method_called(agent, expected, namespace)
|
|
|
|
def _verify_snat_rules(self, rules, router, negate=False):
|
|
interfaces = router[l3_constants.INTERFACE_KEY]
|
|
source_cidrs = []
|
|
for iface in interfaces:
|
|
prefix = iface['subnet']['cidr'].split('/')[1]
|
|
source_cidr = "%s/%s" % (iface['fixed_ips'][0]['ip_address'],
|
|
prefix)
|
|
source_cidrs.append(source_cidr)
|
|
source_nat_ip = router['gw_port']['fixed_ips'][0]['ip_address']
|
|
interface_name = ('qg-%s' % router['gw_port']['id'])[:14]
|
|
expected_rules = [
|
|
'! -i %s ! -o %s -m conntrack ! --ctstate DNAT -j ACCEPT' %
|
|
(interface_name, interface_name)]
|
|
for source_cidr in source_cidrs:
|
|
# Create SNAT rules for IPv4 only
|
|
if (netaddr.IPNetwork(source_cidr).version == 4 and
|
|
netaddr.IPNetwork(source_nat_ip).version == 4):
|
|
value_dict = {'source_cidr': source_cidr,
|
|
'source_nat_ip': source_nat_ip}
|
|
expected_rules.append('-s %(source_cidr)s -j SNAT --to-source '
|
|
'%(source_nat_ip)s' % value_dict)
|
|
for r in rules:
|
|
if negate:
|
|
self.assertNotIn(r.rule, expected_rules)
|
|
else:
|
|
self.assertIn(r.rule, expected_rules)
|
|
|
|
def test__map_internal_interfaces(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=4)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
test_port = {
|
|
'mac_address': '00:12:23:34:45:56',
|
|
'fixed_ips': [{'subnet_id': _get_subnet_id(
|
|
router[l3_constants.INTERFACE_KEY][0]),
|
|
'ip_address': '101.12.13.14'}]}
|
|
internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
|
|
# test valid case
|
|
res_port = agent._map_internal_interfaces(ri,
|
|
internal_ports[0],
|
|
[test_port])
|
|
self.assertEqual(test_port, res_port)
|
|
# test invalid case
|
|
test_port['fixed_ips'][0]['subnet_id'] = 1234
|
|
res_ip = agent._map_internal_interfaces(ri,
|
|
internal_ports[0],
|
|
[test_port])
|
|
self.assertNotEqual(test_port, res_ip)
|
|
self.assertIsNone(res_ip)
|
|
|
|
def test_get_internal_port(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=4)
|
|
subnet_ids = [_get_subnet_id(port) for port in
|
|
router[l3_constants.INTERFACE_KEY]]
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
|
|
# Test Basic cases
|
|
port = agent.get_internal_port(ri, subnet_ids[0])
|
|
fips = port.get('fixed_ips', [])
|
|
subnet_id = fips[0]['subnet_id']
|
|
self.assertEqual(subnet_ids[0], subnet_id)
|
|
port = agent.get_internal_port(ri, subnet_ids[1])
|
|
fips = port.get('fixed_ips', [])
|
|
subnet_id = fips[0]['subnet_id']
|
|
self.assertEqual(subnet_ids[1], subnet_id)
|
|
port = agent.get_internal_port(ri, subnet_ids[3])
|
|
fips = port.get('fixed_ips', [])
|
|
subnet_id = fips[0]['subnet_id']
|
|
self.assertEqual(subnet_ids[3], subnet_id)
|
|
|
|
# Test miss cases
|
|
no_port = agent.get_internal_port(ri, FAKE_ID)
|
|
self.assertIsNone(no_port)
|
|
port = agent.get_internal_port(ri, subnet_ids[0])
|
|
fips = port.get('fixed_ips', [])
|
|
subnet_id = fips[0]['subnet_id']
|
|
self.assertNotEqual(subnet_ids[3], subnet_id)
|
|
|
|
def test__set_subnet_arp_info(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
router['distributed'] = True
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
|
|
test_ports = [{'mac_address': '00:11:22:33:44:55',
|
|
'device_owner': 'network:dhcp',
|
|
'subnet_id': _get_subnet_id(ports[0]),
|
|
'fixed_ips': [{'ip_address': '1.2.3.4'}]}]
|
|
|
|
self.plugin_api.get_ports_by_subnet.return_value = test_ports
|
|
|
|
# Test basic case
|
|
ports[0]['subnet']['id'] = _get_subnet_id(ports[0])
|
|
agent._set_subnet_arp_info(ri, ports[0])
|
|
self.mock_ip_dev.neigh.add.assert_called_once_with(
|
|
4, '1.2.3.4', '00:11:22:33:44:55')
|
|
|
|
# Test negative case
|
|
router['distributed'] = False
|
|
agent._set_subnet_arp_info(ri, ports[0])
|
|
self.mock_ip_dev.neigh.add.never_called()
|
|
|
|
def test_add_arp_entry(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
subnet_id = _get_subnet_id(router[l3_constants.INTERFACE_KEY][0])
|
|
arp_table = {'ip_address': '1.7.23.11',
|
|
'mac_address': '00:11:22:33:44:55',
|
|
'subnet_id': subnet_id}
|
|
|
|
payload = {'arp_table': arp_table, 'router_id': router['id']}
|
|
agent._router_added(router['id'], router)
|
|
agent.add_arp_entry(None, payload)
|
|
agent.router_deleted(None, router['id'])
|
|
self.mock_ip_dev.neigh.add.assert_called_once_with(
|
|
4, '1.7.23.11', '00:11:22:33:44:55')
|
|
|
|
def test_add_arp_entry_no_routerinfo(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
subnet_id = _get_subnet_id(router[l3_constants.INTERFACE_KEY][0])
|
|
arp_table = {'ip_address': '1.7.23.11',
|
|
'mac_address': '00:11:22:33:44:55',
|
|
'subnet_id': subnet_id}
|
|
|
|
payload = {'arp_table': arp_table, 'router_id': router['id']}
|
|
agent._update_arp_entry = mock.Mock()
|
|
agent.add_arp_entry(None, payload)
|
|
self.assertFalse(agent._update_arp_entry.called)
|
|
|
|
def test__update_arp_entry_with_no_subnet(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = l3_agent.RouterInfo(
|
|
'foo_router_id', mock.ANY,
|
|
{'distributed': True, 'gw_port_host': HOSTNAME})
|
|
with mock.patch.object(l3_agent.ip_lib, 'IPDevice') as f:
|
|
agent._update_arp_entry(ri, mock.ANY, mock.ANY,
|
|
'foo_subnet_id', 'add')
|
|
self.assertFalse(f.call_count)
|
|
|
|
def test_del_arp_entry(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
subnet_id = _get_subnet_id(router[l3_constants.INTERFACE_KEY][0])
|
|
arp_table = {'ip_address': '1.5.25.15',
|
|
'mac_address': '00:44:33:22:11:55',
|
|
'subnet_id': subnet_id}
|
|
|
|
payload = {'arp_table': arp_table, 'router_id': router['id']}
|
|
agent._router_added(router['id'], router)
|
|
# first add the entry
|
|
agent.add_arp_entry(None, payload)
|
|
# now delete it
|
|
agent.del_arp_entry(None, payload)
|
|
self.mock_ip_dev.neigh.delete.assert_called_once_with(
|
|
4, '1.5.25.15', '00:44:33:22:11:55')
|
|
agent.router_deleted(None, router['id'])
|
|
|
|
def test_process_cent_router(self):
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
self._test_process_router(ri)
|
|
|
|
def test_process_dist_router(self):
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
subnet_id = _get_subnet_id(router[l3_constants.INTERFACE_KEY][0])
|
|
ri.router['distributed'] = True
|
|
ri.router['_snat_router_interfaces'] = [{
|
|
'fixed_ips': [{'subnet_id': subnet_id,
|
|
'ip_address': '1.2.3.4'}]}]
|
|
ri.router['gw_port_host'] = None
|
|
self._test_process_router(ri)
|
|
|
|
def _test_process_router(self, ri):
|
|
router = ri.router
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
fake_fip_id = 'fake_fip_id'
|
|
agent.process_router_floating_ip_addresses = mock.Mock()
|
|
agent.process_router_floating_ip_nat_rules = mock.Mock()
|
|
agent.process_router_floating_ip_addresses.return_value = {
|
|
fake_fip_id: 'ACTIVE'}
|
|
agent.external_gateway_added = mock.Mock()
|
|
agent.external_gateway_updated = mock.Mock()
|
|
fake_floatingips1 = {'floatingips': [
|
|
{'id': fake_fip_id,
|
|
'floating_ip_address': '8.8.8.8',
|
|
'fixed_ip_address': '7.7.7.7',
|
|
'port_id': _uuid()}]}
|
|
agent.process_router(ri)
|
|
ex_gw_port = agent._get_ex_gw_port(ri)
|
|
agent.process_router_floating_ip_addresses.assert_called_with(
|
|
ri, ex_gw_port)
|
|
agent.process_router_floating_ip_addresses.reset_mock()
|
|
agent.process_router_floating_ip_nat_rules.assert_called_with(ri)
|
|
agent.process_router_floating_ip_nat_rules.reset_mock()
|
|
agent.external_gateway_added.reset_mock()
|
|
|
|
# remap floating IP to a new fixed ip
|
|
fake_floatingips2 = copy.deepcopy(fake_floatingips1)
|
|
fake_floatingips2['floatingips'][0]['fixed_ip_address'] = '7.7.7.8'
|
|
|
|
router[l3_constants.FLOATINGIP_KEY] = fake_floatingips2['floatingips']
|
|
agent.process_router(ri)
|
|
ex_gw_port = agent._get_ex_gw_port(ri)
|
|
agent.process_router_floating_ip_addresses.assert_called_with(
|
|
ri, ex_gw_port)
|
|
agent.process_router_floating_ip_addresses.reset_mock()
|
|
agent.process_router_floating_ip_nat_rules.assert_called_with(ri)
|
|
agent.process_router_floating_ip_nat_rules.reset_mock()
|
|
self.assertEqual(agent.external_gateway_added.call_count, 0)
|
|
self.assertEqual(agent.external_gateway_updated.call_count, 0)
|
|
agent.external_gateway_added.reset_mock()
|
|
agent.external_gateway_updated.reset_mock()
|
|
|
|
# change the ex_gw_port a bit to test gateway update
|
|
new_gw_port = copy.deepcopy(ri.router['gw_port'])
|
|
ri.router['gw_port'] = new_gw_port
|
|
old_ip = (netaddr.IPAddress(ri.router['gw_port']
|
|
['fixed_ips'][0]['ip_address']))
|
|
ri.router['gw_port']['fixed_ips'][0]['ip_address'] = str(old_ip + 1)
|
|
|
|
agent.process_router(ri)
|
|
ex_gw_port = agent._get_ex_gw_port(ri)
|
|
agent.process_router_floating_ip_addresses.reset_mock()
|
|
agent.process_router_floating_ip_nat_rules.reset_mock()
|
|
self.assertEqual(agent.external_gateway_added.call_count, 0)
|
|
self.assertEqual(agent.external_gateway_updated.call_count, 1)
|
|
|
|
# remove just the floating ips
|
|
del router[l3_constants.FLOATINGIP_KEY]
|
|
agent.process_router(ri)
|
|
ex_gw_port = agent._get_ex_gw_port(ri)
|
|
agent.process_router_floating_ip_addresses.assert_called_with(
|
|
ri, ex_gw_port)
|
|
agent.process_router_floating_ip_addresses.reset_mock()
|
|
agent.process_router_floating_ip_nat_rules.assert_called_with(ri)
|
|
agent.process_router_floating_ip_nat_rules.reset_mock()
|
|
|
|
# now no ports so state is torn down
|
|
del router[l3_constants.INTERFACE_KEY]
|
|
del router['gw_port']
|
|
agent.process_router(ri)
|
|
self.assertEqual(self.send_arp.call_count, 1)
|
|
self.assertFalse(agent.process_router_floating_ip_addresses.called)
|
|
self.assertFalse(agent.process_router_floating_ip_nat_rules.called)
|
|
|
|
def test_ha_router_keepalived_config(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(enable_ha=True)
|
|
router['routes'] = [
|
|
{'destination': '8.8.8.8/32', 'nexthop': '35.4.0.10'},
|
|
{'destination': '8.8.4.4/32', 'nexthop': '35.4.0.11'}]
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
ri.router = router
|
|
with contextlib.nested(mock.patch.object(agent,
|
|
'_spawn_metadata_proxy'),
|
|
mock.patch('neutron.agent.linux.'
|
|
'utils.replace_file'),
|
|
mock.patch('neutron.agent.linux.'
|
|
'utils.execute'),
|
|
mock.patch('os.makedirs')):
|
|
agent.process_ha_router_added(ri)
|
|
agent.process_router(ri)
|
|
config = ri.keepalived_manager.config
|
|
ha_iface = agent.get_ha_device_name(ri.ha_port['id'])
|
|
ex_iface = agent.get_external_device_name(ri.ex_gw_port['id'])
|
|
int_iface = agent.get_internal_device_name(
|
|
ri.internal_ports[0]['id'])
|
|
|
|
expected = """vrrp_sync_group VG_1 {
|
|
group {
|
|
VR_1
|
|
}
|
|
}
|
|
vrrp_instance VR_1 {
|
|
state BACKUP
|
|
interface %(ha_iface)s
|
|
virtual_router_id 1
|
|
priority 50
|
|
nopreempt
|
|
advert_int 2
|
|
track_interface {
|
|
%(ha_iface)s
|
|
}
|
|
virtual_ipaddress {
|
|
19.4.4.4/24 dev %(ex_iface)s
|
|
}
|
|
virtual_ipaddress_excluded {
|
|
35.4.0.4/24 dev %(int_iface)s
|
|
}
|
|
virtual_routes {
|
|
0.0.0.0/0 via 19.4.4.1 dev %(ex_iface)s
|
|
8.8.8.8/32 via 35.4.0.10
|
|
8.8.4.4/32 via 35.4.0.11
|
|
}
|
|
}""" % {'ha_iface': ha_iface, 'ex_iface': ex_iface, 'int_iface': int_iface}
|
|
|
|
self.assertEqual(expected, config.get_config_str())
|
|
|
|
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
|
|
def _test_process_router_floating_ip_addresses_add(self, ri,
|
|
agent, IPDevice):
|
|
floating_ips = ri.router.get(l3_constants.FLOATINGIP_KEY, [])
|
|
fip_id = floating_ips[0]['id']
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = []
|
|
ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
|
|
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write'):
|
|
fip_statuses = agent.process_router_floating_ip_addresses(
|
|
ri, {'id': _uuid()})
|
|
self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE},
|
|
fip_statuses)
|
|
device.addr.add.assert_called_once_with(4, '15.1.2.3/32', '15.1.2.3')
|
|
|
|
def test_process_router_floating_ip_nat_rules_add(self):
|
|
fip = {
|
|
'id': _uuid(), 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.1'
|
|
}
|
|
|
|
ri = mock.MagicMock()
|
|
ri.router.get.return_value = [fip]
|
|
ri.router['distributed'].__nonzero__ = lambda self: False
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
agent.process_router_floating_ip_nat_rules(ri)
|
|
|
|
nat = ri.iptables_manager.ipv4['nat']
|
|
nat.clear_rules_by_tag.assert_called_once_with('floating_ip')
|
|
rules = agent.floating_forward_rules('15.1.2.3', '192.168.0.1')
|
|
for chain, rule in rules:
|
|
nat.add_rule.assert_any_call(chain, rule, tag='floating_ip')
|
|
|
|
def test_process_router_cent_floating_ip_add(self):
|
|
fake_floatingips = {'floatingips': [
|
|
{'id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'port_id': _uuid()}]}
|
|
|
|
router = prepare_router_data(enable_snat=True)
|
|
router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self._test_process_router_floating_ip_addresses_add(ri, agent)
|
|
|
|
def test_process_router_dist_floating_ip_add(self):
|
|
fake_floatingips = {'floatingips': [
|
|
{'id': _uuid(),
|
|
'host': HOSTNAME,
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'floating_network_id': _uuid(),
|
|
'port_id': _uuid()},
|
|
{'id': _uuid(),
|
|
'host': 'some-other-host',
|
|
'floating_ip_address': '15.1.2.4',
|
|
'fixed_ip_address': '192.168.0.10',
|
|
'floating_network_id': _uuid(),
|
|
'port_id': _uuid()}]}
|
|
|
|
router = prepare_router_data(enable_snat=True)
|
|
router[l3_constants.FLOATINGIP_KEY] = fake_floatingips['floatingips']
|
|
router['distributed'] = True
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
ri.iptables_manager.ipv4['nat'] = mock.MagicMock()
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent.host = HOSTNAME
|
|
agent.agent_gateway_port = (
|
|
{'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
)
|
|
self._test_process_router_floating_ip_addresses_add(ri, agent)
|
|
|
|
# TODO(mrsmith): refactor for DVR cases
|
|
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
|
|
def test_process_router_floating_ip_addresses_remove(self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
|
|
|
|
ri = mock.MagicMock()
|
|
ri.router.get.return_value = []
|
|
type(ri).is_ha = mock.PropertyMock(return_value=False)
|
|
ri.router['distributed'].__nonzero__ = lambda self: False
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
fip_statuses = agent.process_router_floating_ip_addresses(
|
|
ri, {'id': _uuid()})
|
|
self.assertEqual({}, fip_statuses)
|
|
device.addr.delete.assert_called_once_with(4, '15.1.2.3/32')
|
|
self.mock_driver.delete_conntrack_state.assert_called_once_with(
|
|
root_helper=self.conf.root_helper,
|
|
namespace=ri.ns_name,
|
|
ip='15.1.2.3/32')
|
|
|
|
def test_process_router_floating_ip_nat_rules_remove(self):
|
|
ri = mock.MagicMock()
|
|
ri.router.get.return_value = []
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
agent.process_router_floating_ip_nat_rules(ri)
|
|
|
|
nat = ri.iptables_manager.ipv4['nat']
|
|
nat = ri.iptables_manager.ipv4['nat`']
|
|
nat.clear_rules_by_tag.assert_called_once_with('floating_ip')
|
|
|
|
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
|
|
def test_process_router_floating_ip_addresses_remap(self, IPDevice):
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2'
|
|
}
|
|
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
|
|
ri = mock.MagicMock()
|
|
ri.router['distributed'].__nonzero__ = lambda self: False
|
|
type(ri).is_ha = mock.PropertyMock(return_value=False)
|
|
ri.router.get.return_value = [fip]
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
fip_statuses = agent.process_router_floating_ip_addresses(
|
|
ri, {'id': _uuid()})
|
|
self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE},
|
|
fip_statuses)
|
|
|
|
self.assertFalse(device.addr.add.called)
|
|
self.assertFalse(device.addr.delete.called)
|
|
|
|
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
|
|
def test_process_router_with_disabled_floating_ip(self, IPDevice):
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2'
|
|
}
|
|
|
|
ri = mock.MagicMock()
|
|
ri.floating_ips = [fip]
|
|
ri.router.get.return_value = []
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
fip_statuses = agent.process_router_floating_ip_addresses(
|
|
ri, {'id': _uuid()})
|
|
|
|
self.assertIsNone(fip_statuses.get(fip_id))
|
|
|
|
@mock.patch('neutron.agent.linux.ip_lib.IPDevice')
|
|
def test_process_router_floating_ip_with_device_add_error(self, IPDevice):
|
|
IPDevice.return_value = device = mock.Mock()
|
|
device.addr.add.side_effect = processutils.ProcessExecutionError
|
|
device.addr.list.return_value = []
|
|
fip_id = _uuid()
|
|
fip = {
|
|
'id': fip_id, 'port_id': _uuid(),
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.2'
|
|
}
|
|
ri = mock.MagicMock()
|
|
type(ri).is_ha = mock.PropertyMock(return_value=False)
|
|
ri.router.get.return_value = [fip]
|
|
ri.router['distributed'].__nonzero__ = lambda self: False
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
fip_statuses = agent.process_router_floating_ip_addresses(
|
|
ri, {'id': _uuid()})
|
|
|
|
self.assertEqual({fip_id: l3_constants.FLOATINGIP_STATUS_ERROR},
|
|
fip_statuses)
|
|
|
|
def test_process_router_snat_disabled(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(enable_snat=True)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process with NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Reprocess without NAT
|
|
router['enable_snat'] = False
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# For some reason set logic does not work well with
|
|
# IpTablesRule instances
|
|
nat_rules_delta = [r for r in orig_nat_rules
|
|
if r not in ri.iptables_manager.ipv4['nat'].rules]
|
|
self.assertEqual(len(nat_rules_delta), 2)
|
|
self._verify_snat_rules(nat_rules_delta, router)
|
|
self.assertEqual(self.send_arp.call_count, 1)
|
|
|
|
def test_process_router_snat_enabled(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(enable_snat=False)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process without NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Reprocess with NAT
|
|
router['enable_snat'] = True
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# For some reason set logic does not work well with
|
|
# IpTablesRule instances
|
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
|
if r not in orig_nat_rules]
|
|
self.assertEqual(len(nat_rules_delta), 2)
|
|
self._verify_snat_rules(nat_rules_delta, router)
|
|
self.assertEqual(self.send_arp.call_count, 1)
|
|
|
|
def test_process_router_interface_added(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process with NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Add an interface and reprocess
|
|
router_append_interface(router)
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# For some reason set logic does not work well with
|
|
# IpTablesRule instances
|
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
|
if r not in orig_nat_rules]
|
|
self.assertEqual(len(nat_rules_delta), 1)
|
|
self._verify_snat_rules(nat_rules_delta, router)
|
|
# send_arp is called both times process_router is called
|
|
self.assertEqual(self.send_arp.call_count, 2)
|
|
|
|
def test_process_ipv6_only_gw(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(ip_version=6)
|
|
# Get NAT rules without the gw_port
|
|
gw_port = router['gw_port']
|
|
router['gw_port'] = None
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
|
|
# Get NAT rules with the gw_port
|
|
router['gw_port'] = gw_port
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
with mock.patch.object(
|
|
agent,
|
|
'external_gateway_nat_rules') as external_gateway_nat_rules:
|
|
agent.process_router(ri)
|
|
new_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
|
|
# There should be no change with the NAT rules
|
|
self.assertFalse(external_gateway_nat_rules.called)
|
|
self.assertEqual(orig_nat_rules, new_nat_rules)
|
|
|
|
def _process_router_ipv6_interface_added(
|
|
self, router, ra_mode=None, addr_mode=None):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process with NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Add an IPv6 interface and reprocess
|
|
router_append_interface(router, count=1, ip_version=6, ra_mode=ra_mode,
|
|
addr_mode=addr_mode)
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# IPv4 NAT rules should not be changed by adding an IPv6 interface
|
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
|
if r not in orig_nat_rules]
|
|
self.assertFalse(nat_rules_delta)
|
|
return ri
|
|
|
|
def _expected_call_lookup_ri_process(self, ri, process):
|
|
"""Expected call if a process is looked up in a router instance."""
|
|
return [mock.call(cfg.CONF,
|
|
ri.router['id'],
|
|
self.conf.root_helper,
|
|
ri.ns_name,
|
|
process)]
|
|
|
|
def _assert_ri_process_enabled(self, ri, process):
|
|
"""Verify that process was enabled for a router instance."""
|
|
expected_calls = self._expected_call_lookup_ri_process(ri, process)
|
|
expected_calls.append(mock.call().enable(mock.ANY, True))
|
|
self.assertEqual(expected_calls, self.external_process.mock_calls)
|
|
|
|
def _assert_ri_process_disabled(self, ri, process):
|
|
"""Verify that process was disabled for a router instance."""
|
|
expected_calls = self._expected_call_lookup_ri_process(ri, process)
|
|
expected_calls.append(mock.call().disable())
|
|
self.assertEqual(expected_calls, self.external_process.mock_calls)
|
|
|
|
def test_process_router_ipv6_interface_added(self):
|
|
router = prepare_router_data()
|
|
ri = self._process_router_ipv6_interface_added(router)
|
|
self._assert_ri_process_enabled(ri, 'radvd')
|
|
# Expect radvd configured without prefix
|
|
self.assertNotIn('prefix',
|
|
self.utils_replace_file.call_args[0][1].split())
|
|
|
|
def test_process_router_ipv6_slaac_interface_added(self):
|
|
router = prepare_router_data()
|
|
ri = self._process_router_ipv6_interface_added(
|
|
router, ra_mode=l3_constants.IPV6_SLAAC)
|
|
self._assert_ri_process_enabled(ri, 'radvd')
|
|
# Expect radvd configured with prefix
|
|
self.assertIn('prefix',
|
|
self.utils_replace_file.call_args[0][1].split())
|
|
|
|
def test_process_router_ipv6v4_interface_added(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process with NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Add an IPv4 and IPv6 interface and reprocess
|
|
router_append_interface(router, count=1, ip_version=4)
|
|
router_append_interface(router, count=1, ip_version=6)
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
self._assert_ri_process_enabled(ri, 'radvd')
|
|
# For some reason set logic does not work well with
|
|
# IpTablesRule instances
|
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
|
if r not in orig_nat_rules]
|
|
self.assertEqual(1, len(nat_rules_delta))
|
|
self._verify_snat_rules(nat_rules_delta, router)
|
|
|
|
def test_process_router_interface_removed(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data(num_internal_ports=2)
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# Process with NAT
|
|
agent.process_router(ri)
|
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
|
# Add an interface and reprocess
|
|
del router[l3_constants.INTERFACE_KEY][1]
|
|
# Reassign the router object to RouterInfo
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# For some reason set logic does not work well with
|
|
# IpTablesRule instances
|
|
nat_rules_delta = [r for r in orig_nat_rules
|
|
if r not in ri.iptables_manager.ipv4['nat'].rules]
|
|
self.assertEqual(len(nat_rules_delta), 1)
|
|
self._verify_snat_rules(nat_rules_delta, router, negate=True)
|
|
# send_arp is called both times process_router is called
|
|
self.assertEqual(self.send_arp.call_count, 2)
|
|
|
|
def test_process_router_ipv6_interface_removed(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# Add an IPv6 interface and reprocess
|
|
router_append_interface(router, count=1, ip_version=6)
|
|
agent.process_router(ri)
|
|
self._assert_ri_process_enabled(ri, 'radvd')
|
|
# Reset the calls so we can check for disable radvd
|
|
self.external_process.reset_mock()
|
|
# Remove the IPv6 interface and reprocess
|
|
del router[l3_constants.INTERFACE_KEY][1]
|
|
agent.process_router(ri)
|
|
self._assert_ri_process_disabled(ri, 'radvd')
|
|
|
|
def test_process_router_internal_network_added_unexpected_error(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
with mock.patch.object(
|
|
l3_agent.L3NATAgent,
|
|
'internal_network_added') as internal_network_added:
|
|
# raise RuntimeError to simulate that an unexpected exception
|
|
# occurs
|
|
internal_network_added.side_effect = RuntimeError
|
|
self.assertRaises(RuntimeError, agent.process_router, ri)
|
|
self.assertNotIn(
|
|
router[l3_constants.INTERFACE_KEY][0], ri.internal_ports)
|
|
|
|
# The unexpected exception has been fixed manually
|
|
internal_network_added.side_effect = None
|
|
|
|
# _sync_routers_task finds out that _rpc_loop failed to process the
|
|
# router last time, it will retry in the next run.
|
|
agent.process_router(ri)
|
|
# We were able to add the port to ri.internal_ports
|
|
self.assertIn(
|
|
router[l3_constants.INTERFACE_KEY][0], ri.internal_ports)
|
|
|
|
def test_process_router_internal_network_removed_unexpected_error(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
# add an internal port
|
|
agent.process_router(ri)
|
|
|
|
with mock.patch.object(
|
|
l3_agent.L3NATAgent,
|
|
'internal_network_removed') as internal_net_removed:
|
|
# raise RuntimeError to simulate that an unexpected exception
|
|
# occurs
|
|
internal_net_removed.side_effect = RuntimeError
|
|
ri.internal_ports[0]['admin_state_up'] = False
|
|
# The above port is set to down state, remove it.
|
|
self.assertRaises(RuntimeError, agent.process_router, ri)
|
|
self.assertIn(
|
|
router[l3_constants.INTERFACE_KEY][0], ri.internal_ports)
|
|
|
|
# The unexpected exception has been fixed manually
|
|
internal_net_removed.side_effect = None
|
|
|
|
# _sync_routers_task finds out that _rpc_loop failed to process the
|
|
# router last time, it will retry in the next run.
|
|
agent.process_router(ri)
|
|
# We were able to remove the port from ri.internal_ports
|
|
self.assertNotIn(
|
|
router[l3_constants.INTERFACE_KEY][0], ri.internal_ports)
|
|
|
|
def test_process_router_floatingip_disabled(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
with mock.patch.object(
|
|
agent.plugin_rpc,
|
|
'update_floatingip_statuses') as mock_update_fip_status:
|
|
fip_id = _uuid()
|
|
router = prepare_router_data(num_internal_ports=1)
|
|
router[l3_constants.FLOATINGIP_KEY] = [
|
|
{'id': fip_id,
|
|
'floating_ip_address': '8.8.8.8',
|
|
'fixed_ip_address': '7.7.7.7',
|
|
'port_id': router[l3_constants.INTERFACE_KEY][0]['id']}]
|
|
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
agent.process_router(ri)
|
|
# Assess the call for putting the floating IP up was performed
|
|
mock_update_fip_status.assert_called_once_with(
|
|
mock.ANY, ri.router_id,
|
|
{fip_id: l3_constants.FLOATINGIP_STATUS_ACTIVE})
|
|
mock_update_fip_status.reset_mock()
|
|
# Process the router again, this time without floating IPs
|
|
router[l3_constants.FLOATINGIP_KEY] = []
|
|
ri.router = router
|
|
agent.process_router(ri)
|
|
# Assess the call for putting the floating IP up was performed
|
|
mock_update_fip_status.assert_called_once_with(
|
|
mock.ANY, ri.router_id,
|
|
{fip_id: l3_constants.FLOATINGIP_STATUS_DOWN})
|
|
|
|
def test_process_router_floatingip_exception(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent.process_router_floating_ip_addresses = mock.Mock()
|
|
agent.process_router_floating_ip_addresses.side_effect = RuntimeError
|
|
with mock.patch.object(
|
|
agent.plugin_rpc,
|
|
'update_floatingip_statuses') as mock_update_fip_status:
|
|
fip_id = _uuid()
|
|
router = prepare_router_data(num_internal_ports=1)
|
|
router[l3_constants.FLOATINGIP_KEY] = [
|
|
{'id': fip_id,
|
|
'floating_ip_address': '8.8.8.8',
|
|
'fixed_ip_address': '7.7.7.7',
|
|
'port_id': router[l3_constants.INTERFACE_KEY][0]['id']}]
|
|
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent.external_gateway_added = mock.Mock()
|
|
agent.process_router(ri)
|
|
# Assess the call for putting the floating IP into Error
|
|
# was performed
|
|
mock_update_fip_status.assert_called_once_with(
|
|
mock.ANY, ri.router_id,
|
|
{fip_id: l3_constants.FLOATINGIP_STATUS_ERROR})
|
|
|
|
def test_handle_router_snat_rules_distributed_without_snat_manager(self):
|
|
ri = l3_agent.RouterInfo(
|
|
'foo_router_id', mock.ANY, {'distributed': True})
|
|
ri.iptables_manager = mock.Mock()
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
with mock.patch.object(l3_agent.LOG, 'debug') as log_debug:
|
|
agent._handle_router_snat_rules(
|
|
ri, mock.ANY, mock.ANY, mock.ANY, mock.ANY)
|
|
self.assertIsNone(ri.snat_iptables_manager)
|
|
self.assertFalse(ri.iptables_manager.called)
|
|
self.assertTrue(log_debug.called)
|
|
|
|
def test_handle_router_snat_rules_add_back_jump(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = mock.MagicMock()
|
|
port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
|
|
ri.router = {'distributed': False}
|
|
|
|
agent._handle_router_snat_rules(ri, port, [], "iface", "add_rules")
|
|
|
|
nat = ri.iptables_manager.ipv4['nat']
|
|
nat.empty_chain.assert_any_call('snat')
|
|
nat.add_rule.assert_any_call('snat', '-j $float-snat')
|
|
for call in nat.mock_calls:
|
|
name, args, kwargs = call
|
|
if name == 'add_rule':
|
|
self.assertEqual(args, ('snat', '-j $float-snat'))
|
|
self.assertEqual(kwargs, {})
|
|
break
|
|
|
|
def test_handle_router_snat_rules_add_rules(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
ri = l3_agent.RouterInfo(_uuid(), self.conf.root_helper, {})
|
|
ex_gw_port = {'fixed_ips': [{'ip_address': '192.168.1.4'}]}
|
|
internal_cidrs = ['10.0.0.0/24']
|
|
ri.router = {'distributed': False}
|
|
agent._handle_router_snat_rules(ri, ex_gw_port, internal_cidrs,
|
|
"iface", "add_rules")
|
|
|
|
nat_rules = map(str, ri.iptables_manager.ipv4['nat'].rules)
|
|
wrap_name = ri.iptables_manager.wrap_name
|
|
|
|
jump_float_rule = "-A %s-snat -j %s-float-snat" % (wrap_name,
|
|
wrap_name)
|
|
internal_net_rule = ("-A %s-snat -s %s -j SNAT --to-source %s") % (
|
|
wrap_name, internal_cidrs[0],
|
|
ex_gw_port['fixed_ips'][0]['ip_address'])
|
|
|
|
self.assertIn(jump_float_rule, nat_rules)
|
|
|
|
self.assertIn(internal_net_rule, nat_rules)
|
|
self.assertThat(nat_rules.index(jump_float_rule),
|
|
matchers.LessThan(nat_rules.index(internal_net_rule)))
|
|
|
|
def test_process_router_delete_stale_internal_devices(self):
|
|
class FakeDev(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
stale_devlist = [FakeDev('qr-a1b2c3d4-e5'),
|
|
FakeDev('qr-b2c3d4e5-f6')]
|
|
stale_devnames = [dev.name for dev in stale_devlist]
|
|
|
|
get_devices_return = []
|
|
get_devices_return.extend(stale_devlist)
|
|
self.mock_ip.get_devices.return_value = get_devices_return
|
|
|
|
router = prepare_router_data(enable_snat=True, num_internal_ports=1)
|
|
ri = l3_agent.RouterInfo(router['id'],
|
|
self.conf.root_helper,
|
|
router=router)
|
|
|
|
internal_ports = ri.router.get(l3_constants.INTERFACE_KEY, [])
|
|
self.assertEqual(len(internal_ports), 1)
|
|
internal_port = internal_ports[0]
|
|
|
|
with contextlib.nested(mock.patch.object(l3_agent.L3NATAgent,
|
|
'internal_network_removed'),
|
|
mock.patch.object(l3_agent.L3NATAgent,
|
|
'internal_network_added'),
|
|
mock.patch.object(l3_agent.L3NATAgent,
|
|
'external_gateway_removed'),
|
|
mock.patch.object(l3_agent.L3NATAgent,
|
|
'external_gateway_added')
|
|
) as (internal_network_removed,
|
|
internal_network_added,
|
|
external_gateway_removed,
|
|
external_gateway_added):
|
|
|
|
agent.process_router(ri)
|
|
|
|
self.assertEqual(external_gateway_added.call_count, 1)
|
|
self.assertFalse(external_gateway_removed.called)
|
|
self.assertFalse(internal_network_removed.called)
|
|
internal_network_added.assert_called_once_with(
|
|
ri, internal_port)
|
|
self.assertEqual(self.mock_driver.unplug.call_count,
|
|
len(stale_devnames))
|
|
calls = [mock.call(stale_devname,
|
|
namespace=ri.ns_name,
|
|
prefix=l3_agent.INTERNAL_DEV_PREFIX)
|
|
for stale_devname in stale_devnames]
|
|
self.mock_driver.unplug.assert_has_calls(calls, any_order=True)
|
|
|
|
def test_process_router_delete_stale_external_devices(self):
|
|
class FakeDev(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
stale_devlist = [FakeDev('qg-a1b2c3d4-e5')]
|
|
stale_devnames = [dev.name for dev in stale_devlist]
|
|
|
|
router = prepare_router_data(enable_snat=True, num_internal_ports=1)
|
|
del router['gw_port']
|
|
ri = l3_agent.RouterInfo(router['id'],
|
|
self.conf.root_helper,
|
|
router=router)
|
|
|
|
self.mock_ip.get_devices.return_value = stale_devlist
|
|
|
|
agent.process_router(ri)
|
|
|
|
self.mock_driver.unplug.assert_called_with(
|
|
stale_devnames[0],
|
|
bridge="br-ex",
|
|
namespace=ri.ns_name,
|
|
prefix=l3_agent.EXTERNAL_DEV_PREFIX)
|
|
|
|
def test_router_deleted(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._queue = mock.Mock()
|
|
agent.router_deleted(None, FAKE_ID)
|
|
self.assertEqual(1, agent._queue.add.call_count)
|
|
|
|
def test_routers_updated(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._queue = mock.Mock()
|
|
agent.routers_updated(None, [FAKE_ID])
|
|
self.assertEqual(1, agent._queue.add.call_count)
|
|
|
|
def test_removed_from_agent(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._queue = mock.Mock()
|
|
agent.router_removed_from_agent(None, {'router_id': FAKE_ID})
|
|
self.assertEqual(1, agent._queue.add.call_count)
|
|
|
|
def test_added_to_agent(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._queue = mock.Mock()
|
|
agent.router_added_to_agent(None, [FAKE_ID])
|
|
self.assertEqual(1, agent._queue.add.call_count)
|
|
|
|
def test_destroy_fip_namespace(self):
|
|
class FakeDev(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
namespaces = ['qrouter-foo', 'qrouter-bar']
|
|
|
|
self.mock_ip.get_namespaces.return_value = namespaces
|
|
self.mock_ip.get_devices.return_value = [FakeDev('fpr-aaaa'),
|
|
FakeDev('fg-aaaa')]
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
agent._destroy_fip_namespace(namespaces[0])
|
|
self.mock_driver.unplug.assert_called_once_with('fg-aaaa',
|
|
bridge='br-ex',
|
|
prefix='fg-',
|
|
namespace='qrouter'
|
|
'-foo')
|
|
self.mock_ip.del_veth.assert_called_once_with('fpr-aaaa')
|
|
|
|
def test_destroy_namespace(self):
|
|
class FakeDev(object):
|
|
def __init__(self, name):
|
|
self.name = name
|
|
|
|
namespace = 'qrouter-bar'
|
|
|
|
self.mock_ip.get_namespaces.return_value = [namespace]
|
|
self.mock_ip.get_devices.return_value = [FakeDev('qr-aaaa'),
|
|
FakeDev('rfp-aaaa')]
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
agent._destroy_namespace(namespace)
|
|
self.mock_driver.unplug.assert_called_once_with('qr-aaaa',
|
|
prefix='qr-',
|
|
namespace='qrouter'
|
|
'-bar')
|
|
self.mock_ip.del_veth.assert_called_once_with('rfp-aaaa')
|
|
|
|
def test_destroy_router_namespace_skips_ns_removal(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._destroy_router_namespace("fakens")
|
|
self.assertEqual(self.mock_ip.netns.delete.call_count, 0)
|
|
|
|
def test_destroy_router_namespace_removes_ns(self):
|
|
self.conf.set_override('router_delete_namespaces', True)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
agent._destroy_router_namespace("fakens")
|
|
self.mock_ip.netns.delete.assert_called_once_with("fakens")
|
|
|
|
def _configure_metadata_proxy(self, enableflag=True):
|
|
if not enableflag:
|
|
self.conf.set_override('enable_metadata_proxy', False)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
router = {'id': _uuid(),
|
|
'external_gateway_info': {},
|
|
'routes': [],
|
|
'distributed': False}
|
|
with mock.patch.object(
|
|
agent, '_destroy_metadata_proxy') as destroy_proxy:
|
|
with mock.patch.object(
|
|
agent, '_spawn_metadata_proxy') as spawn_proxy:
|
|
agent._router_added(router_id, router)
|
|
if enableflag:
|
|
spawn_proxy.assert_called_with(router_id, mock.ANY)
|
|
else:
|
|
self.assertFalse(spawn_proxy.call_count)
|
|
agent._router_removed(router_id)
|
|
if enableflag:
|
|
destroy_proxy.assert_called_with(mock.ANY, mock.ANY)
|
|
else:
|
|
self.assertFalse(destroy_proxy.call_count)
|
|
|
|
def test_enable_metadata_proxy(self):
|
|
self._configure_metadata_proxy()
|
|
|
|
def test_disable_metadata_proxy_spawn(self):
|
|
self._configure_metadata_proxy(enableflag=False)
|
|
|
|
def test_metadata_nat_rules(self):
|
|
self.conf.set_override('enable_metadata_proxy', False)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.assertEqual([], agent.metadata_nat_rules())
|
|
|
|
self.conf.set_override('metadata_port', '8775')
|
|
self.conf.set_override('enable_metadata_proxy', True)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
rules = ('PREROUTING', '-s 0.0.0.0/0 -d 169.254.169.254/32 '
|
|
'-p tcp -m tcp --dport 80 -j REDIRECT --to-port 8775')
|
|
self.assertEqual([rules], agent.metadata_nat_rules())
|
|
|
|
def test_router_id_specified_in_conf(self):
|
|
self.conf.set_override('use_namespaces', False)
|
|
self.conf.set_override('router_id', '')
|
|
self.assertRaises(SystemExit, l3_agent.L3NATAgent,
|
|
HOSTNAME, self.conf)
|
|
|
|
self.conf.set_override('router_id', '1234')
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.assertEqual(['1234'], agent._router_ids())
|
|
self.assertFalse(agent._clean_stale_namespaces)
|
|
|
|
def test_process_router_if_compatible_with_no_ext_net_in_conf(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_external_network_id.return_value = 'aaa'
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'aaa'}}
|
|
|
|
agent._process_router_if_compatible(router)
|
|
self.assertIn(router['id'], agent.router_info)
|
|
self.plugin_api.get_external_network_id.assert_called_with(
|
|
agent.context)
|
|
|
|
def test_process_router_if_compatible_with_cached_ext_net(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_external_network_id.return_value = 'aaa'
|
|
agent.target_ex_net_id = 'aaa'
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'aaa'}}
|
|
|
|
agent._process_router_if_compatible(router)
|
|
self.assertIn(router['id'], agent.router_info)
|
|
self.assertFalse(self.plugin_api.get_external_network_id.called)
|
|
|
|
def test_process_router_if_compatible_with_stale_cached_ext_net(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_external_network_id.return_value = 'aaa'
|
|
agent.target_ex_net_id = 'bbb'
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'aaa'}}
|
|
|
|
agent._process_router_if_compatible(router)
|
|
self.assertIn(router['id'], agent.router_info)
|
|
self.plugin_api.get_external_network_id.assert_called_with(
|
|
agent.context)
|
|
|
|
def test_process_router_if_compatible_w_no_ext_net_and_2_net_plugin(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'aaa'}}
|
|
|
|
agent.router_info = {}
|
|
self.plugin_api.get_external_network_id.side_effect = (
|
|
n_exc.TooManyExternalNetworks())
|
|
self.assertRaises(n_exc.TooManyExternalNetworks,
|
|
agent._process_router_if_compatible,
|
|
router)
|
|
self.assertNotIn(router['id'], agent.router_info)
|
|
|
|
def test_process_router_if_compatible_with_ext_net_in_conf(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_external_network_id.return_value = 'aaa'
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'bbb'}}
|
|
|
|
agent.router_info = {}
|
|
self.conf.set_override('gateway_external_network_id', 'aaa')
|
|
self.assertRaises(n_exc.RouterNotCompatibleWithAgent,
|
|
agent._process_router_if_compatible,
|
|
router)
|
|
self.assertNotIn(router['id'], agent.router_info)
|
|
|
|
def test_process_router_if_compatible_with_no_bridge_no_ext_net(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.plugin_api.get_external_network_id.return_value = 'aaa'
|
|
|
|
router = {'id': _uuid(),
|
|
'routes': [],
|
|
'admin_state_up': True,
|
|
'external_gateway_info': {'network_id': 'aaa'}}
|
|
|
|
agent.router_info = {}
|
|
self.conf.set_override('external_network_bridge', '')
|
|
agent._process_router_if_compatible(router)
|
|
self.assertIn(router['id'], agent.router_info)
|
|
|
|
def test_nonexistent_interface_driver(self):
|
|
self.conf.set_override('interface_driver', None)
|
|
with mock.patch.object(l3_agent, 'LOG') as log:
|
|
self.assertRaises(SystemExit, l3_agent.L3NATAgent,
|
|
HOSTNAME, self.conf)
|
|
msg = 'An interface driver must be specified'
|
|
log.error.assert_called_once_with(msg)
|
|
|
|
self.conf.set_override('interface_driver', 'wrong_driver')
|
|
with mock.patch.object(l3_agent, 'LOG') as log:
|
|
self.assertRaises(SystemExit, l3_agent.L3NATAgent,
|
|
HOSTNAME, self.conf)
|
|
msg = "Error importing interface driver 'wrong_driver'"
|
|
log.error.assert_called_once_with(msg)
|
|
|
|
def test_metadata_filter_rules(self):
|
|
self.conf.set_override('enable_metadata_proxy', False)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.assertEqual([], agent.metadata_filter_rules())
|
|
|
|
self.conf.set_override('metadata_port', '8775')
|
|
self.conf.set_override('enable_metadata_proxy', True)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
rules = ('INPUT', '-s 0.0.0.0/0 -d 127.0.0.1 '
|
|
'-p tcp -m tcp --dport 8775 -j ACCEPT')
|
|
self.assertEqual([rules], agent.metadata_filter_rules())
|
|
|
|
def _cleanup_namespace_test(self,
|
|
stale_namespace_list,
|
|
router_list,
|
|
other_namespaces):
|
|
self.conf.set_override('router_delete_namespaces', True)
|
|
|
|
good_namespace_list = [l3_agent.NS_PREFIX + r['id']
|
|
for r in router_list]
|
|
good_namespace_list += [l3_agent.SNAT_NS_PREFIX + r['id']
|
|
for r in router_list]
|
|
self.mock_ip.get_namespaces.return_value = (stale_namespace_list +
|
|
good_namespace_list +
|
|
other_namespaces)
|
|
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
self.assertTrue(agent._clean_stale_namespaces)
|
|
|
|
pm = self.external_process.return_value
|
|
pm.reset_mock()
|
|
|
|
agent._destroy_router_namespace = mock.MagicMock()
|
|
agent._destroy_snat_namespace = mock.MagicMock()
|
|
ns_list = agent._list_namespaces()
|
|
agent._cleanup_namespaces(ns_list, [r['id'] for r in router_list])
|
|
|
|
# Expect process manager to disable metadata proxy per qrouter ns
|
|
qrouters = [n for n in stale_namespace_list
|
|
if n.startswith(l3_agent.NS_PREFIX)]
|
|
|
|
self.assertEqual(agent._destroy_router_namespace.call_count,
|
|
len(qrouters))
|
|
self.assertEqual(agent._destroy_snat_namespace.call_count,
|
|
len(stale_namespace_list) - len(qrouters))
|
|
expected_args = [mock.call(ns) for ns in qrouters]
|
|
agent._destroy_router_namespace.assert_has_calls(expected_args,
|
|
any_order=True)
|
|
self.assertFalse(agent._clean_stale_namespaces)
|
|
|
|
def test_cleanup_namespace(self):
|
|
self.conf.set_override('router_id', None)
|
|
stale_namespaces = [l3_agent.NS_PREFIX + 'foo',
|
|
l3_agent.NS_PREFIX + 'bar',
|
|
l3_agent.SNAT_NS_PREFIX + 'foo']
|
|
other_namespaces = ['unknown']
|
|
|
|
self._cleanup_namespace_test(stale_namespaces,
|
|
[],
|
|
other_namespaces)
|
|
|
|
def test_cleanup_namespace_with_registered_router_ids(self):
|
|
self.conf.set_override('router_id', None)
|
|
stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
|
|
l3_agent.NS_PREFIX + 'eeeee',
|
|
l3_agent.SNAT_NS_PREFIX + 'fffff']
|
|
router_list = [{'id': 'foo', 'distributed': False},
|
|
{'id': 'aaaa', 'distributed': False}]
|
|
other_namespaces = ['qdhcp-aabbcc', 'unknown']
|
|
|
|
self._cleanup_namespace_test(stale_namespaces,
|
|
router_list,
|
|
other_namespaces)
|
|
|
|
def test_cleanup_namespace_with_conf_router_id(self):
|
|
self.conf.set_override('router_id', 'bbbbb')
|
|
stale_namespaces = [l3_agent.NS_PREFIX + 'cccc',
|
|
l3_agent.NS_PREFIX + 'eeeee',
|
|
l3_agent.NS_PREFIX + self.conf.router_id]
|
|
router_list = [{'id': 'foo', 'distributed': False},
|
|
{'id': 'aaaa', 'distributed': False}]
|
|
other_namespaces = ['qdhcp-aabbcc', 'unknown']
|
|
|
|
self._cleanup_namespace_test(stale_namespaces,
|
|
router_list,
|
|
other_namespaces)
|
|
|
|
def test_create_dvr_gateway(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
|
|
port_id = _uuid()
|
|
dvr_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'id': port_id,
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
|
|
interface_name = agent.get_snat_int_device_name(port_id)
|
|
self.device_exists.return_value = False
|
|
|
|
agent._create_dvr_gateway(ri, dvr_gw_port, interface_name,
|
|
self.snat_ports)
|
|
|
|
# check 2 internal ports are plugged
|
|
# check 1 ext-gw-port is plugged
|
|
self.assertEqual(self.mock_driver.plug.call_count, 3)
|
|
self.assertEqual(self.mock_driver.init_l3.call_count, 3)
|
|
|
|
def test_agent_gateway_added(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
network_id = _uuid()
|
|
port_id = _uuid()
|
|
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'id': port_id,
|
|
'network_id': network_id,
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
fip_ns_name = (
|
|
agent.get_fip_ns_name(str(network_id)))
|
|
interface_name = (
|
|
agent.get_fip_ext_device_name(port_id))
|
|
|
|
self.device_exists.return_value = False
|
|
agent.agent_gateway_added(fip_ns_name, agent_gw_port,
|
|
interface_name)
|
|
self.assertEqual(self.mock_driver.plug.call_count, 1)
|
|
self.assertEqual(self.mock_driver.init_l3.call_count, 1)
|
|
if self.conf.use_namespaces:
|
|
self.send_arp.assert_called_once_with(fip_ns_name, interface_name,
|
|
'20.0.0.30')
|
|
else:
|
|
self.utils_exec.assert_any_call(
|
|
check_exit_code=True, root_helper=self.conf.root_helper)
|
|
|
|
def test_create_rtr_2_fip_link(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
fip = {'id': _uuid(),
|
|
'host': HOSTNAME,
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'floating_network_id': _uuid(),
|
|
'port_id': _uuid()}
|
|
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
|
|
rtr_2_fip_name = agent.get_rtr_int_device_name(ri.router_id)
|
|
fip_2_rtr_name = agent.get_fip_int_device_name(ri.router_id)
|
|
fip_ns_name = agent.get_fip_ns_name(str(fip['floating_network_id']))
|
|
|
|
with mock.patch.object(l3_agent.LinkLocalAllocator, '_write'):
|
|
agent.create_rtr_2_fip_link(ri, fip['floating_network_id'])
|
|
self.mock_ip.add_veth.assert_called_with(rtr_2_fip_name,
|
|
fip_2_rtr_name, fip_ns_name)
|
|
# TODO(mrsmith): add more aasserts -
|
|
self.mock_ip_dev.route.add_gateway.assert_called_once_with(
|
|
'169.254.31.29', table=16)
|
|
|
|
# TODO(mrsmith): test _create_agent_gateway_port
|
|
|
|
def test_floating_ip_added_dist(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
|
|
fip = {'id': _uuid(),
|
|
'host': HOSTNAME,
|
|
'floating_ip_address': '15.1.2.3',
|
|
'fixed_ip_address': '192.168.0.1',
|
|
'floating_network_id': _uuid(),
|
|
'port_id': _uuid()}
|
|
agent.agent_gateway_port = agent_gw_port
|
|
ri.rtr_fip_subnet = l3_agent.LinkLocalAddressPair('169.254.30.42/31')
|
|
agent.floating_ip_added_dist(ri, fip)
|
|
self.mock_rule.add_rule_from.assert_called_with('192.168.0.1',
|
|
16, FIP_PRI)
|
|
# TODO(mrsmith): add more asserts
|
|
|
|
@mock.patch.object(l3_agent.L3NATAgent, '_fip_ns_unsubscribe')
|
|
@mock.patch.object(l3_agent.LinkLocalAllocator, '_write')
|
|
def test_floating_ip_removed_dist(self, write, unsubscribe):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router = prepare_router_data()
|
|
agent_gw_port = {'fixed_ips': [{'ip_address': '20.0.0.30',
|
|
'subnet_id': _uuid()}],
|
|
'subnet': {'gateway_ip': '20.0.0.1'},
|
|
'id': _uuid(),
|
|
'network_id': _uuid(),
|
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
'ip_cidr': '20.0.0.30/24'}
|
|
fip_cidr = '11.22.33.44/24'
|
|
|
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
|
router=router)
|
|
ri.dist_fip_count = 2
|
|
agent.fip_ns_subscribers.add(ri.router_id)
|
|
ri.floating_ips_dict['11.22.33.44'] = FIP_PRI
|
|
ri.fip_2_rtr = '11.22.33.42'
|
|
ri.rtr_2_fip = '11.22.33.40'
|
|
agent.agent_gateway_port = agent_gw_port
|
|
s = l3_agent.LinkLocalAddressPair('169.254.30.42/31')
|
|
ri.rtr_fip_subnet = s
|
|
agent.floating_ip_removed_dist(ri, fip_cidr)
|
|
self.mock_rule.delete_rule_priority.assert_called_with(FIP_PRI)
|
|
self.mock_ip_dev.route.delete_route.assert_called_with(fip_cidr,
|
|
str(s.ip))
|
|
self.assertFalse(unsubscribe.called, '_fip_ns_unsubscribe called!')
|
|
|
|
with mock.patch.object(agent, '_destroy_fip_namespace') as f:
|
|
ri.dist_fip_count = 1
|
|
fip_ns_name = agent.get_fip_ns_name(
|
|
str(agent._fetch_external_net_id()))
|
|
ri.rtr_fip_subnet = agent.local_subnets.allocate(ri.router_id)
|
|
_, fip_to_rtr = ri.rtr_fip_subnet.get_pair()
|
|
agent.floating_ip_removed_dist(ri, fip_cidr)
|
|
self.mock_ip.del_veth.assert_called_once_with(
|
|
agent.get_fip_int_device_name(router['id']))
|
|
self.mock_ip_dev.route.delete_gateway.assert_called_once_with(
|
|
str(fip_to_rtr.ip), table=16)
|
|
f.assert_called_once_with(fip_ns_name)
|
|
unsubscribe.assert_called_once_with(ri.router_id)
|
|
|
|
def test_get_service_plugin_list(self):
|
|
service_plugins = [p_const.L3_ROUTER_NAT]
|
|
self.plugin_api.get_service_plugin_list.return_value = service_plugins
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.assertEqual(service_plugins, agent.neutron_service_plugins)
|
|
self.assertTrue(self.plugin_api.get_service_plugin_list.called)
|
|
|
|
def test_get_service_plugin_list_failed(self):
|
|
raise_rpc = n_rpc.RemoteError()
|
|
self.plugin_api.get_service_plugin_list.side_effect = raise_rpc
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
self.assertIsNone(agent.neutron_service_plugins)
|
|
self.assertTrue(self.plugin_api.get_service_plugin_list.called)
|
|
|
|
def test_get_service_plugin_list_retried(self):
|
|
raise_timeout = messaging.MessagingTimeout()
|
|
# Raise a timeout the first 2 times it calls
|
|
# get_service_plugin_list then return a empty tuple
|
|
self.plugin_api.get_service_plugin_list.side_effect = (
|
|
raise_timeout, raise_timeout, tuple()
|
|
)
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
|
|
self.assertEqual(agent.neutron_service_plugins, tuple())
|
|
|
|
def test_get_service_plugin_list_retried_max(self):
|
|
raise_timeout = messaging.MessagingTimeout()
|
|
# Raise a timeout 5 times
|
|
self.plugin_api.get_service_plugin_list.side_effect = (
|
|
(raise_timeout, ) * 5
|
|
)
|
|
self.assertRaises(messaging.MessagingTimeout, l3_agent.L3NATAgent,
|
|
HOSTNAME, self.conf)
|
|
|
|
def test__fip_ns_subscribe_is_first_true(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
is_first = agent._fip_ns_subscribe(router_id)
|
|
self.assertTrue(is_first)
|
|
self.assertEqual(len(agent.fip_ns_subscribers), 1)
|
|
|
|
def test__fip_ns_subscribe_is_first_false(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
router2_id = _uuid()
|
|
agent._fip_ns_subscribe(router_id)
|
|
is_first = agent._fip_ns_subscribe(router2_id)
|
|
self.assertFalse(is_first)
|
|
self.assertEqual(len(agent.fip_ns_subscribers), 2)
|
|
|
|
def test__fip_ns_unsubscribe_is_last_true(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
agent.fip_ns_subscribers.add(router_id)
|
|
is_last = agent._fip_ns_unsubscribe(router_id)
|
|
self.assertTrue(is_last)
|
|
self.assertEqual(len(agent.fip_ns_subscribers), 0)
|
|
|
|
def test__fip_ns_unsubscribe_is_last_false(self):
|
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
|
router_id = _uuid()
|
|
router2_id = _uuid()
|
|
agent.fip_ns_subscribers.add(router_id)
|
|
agent.fip_ns_subscribers.add(router2_id)
|
|
is_last = agent._fip_ns_unsubscribe(router_id)
|
|
self.assertFalse(is_last)
|
|
self.assertEqual(len(agent.fip_ns_subscribers), 1)
|
|
|
|
|
|
class TestL3AgentEventHandler(base.BaseTestCase):
|
|
|
|
def setUp(self):
|
|
super(TestL3AgentEventHandler, self).setUp()
|
|
cfg.CONF.register_opts(l3_agent.L3NATAgent.OPTS)
|
|
cfg.CONF.register_opts(l3_ha_agent.OPTS)
|
|
agent_config.register_interface_driver_opts_helper(cfg.CONF)
|
|
agent_config.register_use_namespaces_opts_helper(cfg.CONF)
|
|
cfg.CONF.set_override(
|
|
'interface_driver', 'neutron.agent.linux.interface.NullDriver'
|
|
)
|
|
cfg.CONF.set_override('use_namespaces', True)
|
|
cfg.CONF.set_override('verbose', False)
|
|
agent_config.register_root_helper(cfg.CONF)
|
|
|
|
device_exists_p = mock.patch(
|
|
'neutron.agent.linux.ip_lib.device_exists')
|
|
device_exists_p.start()
|
|
|
|
utils_exec_p = mock.patch(
|
|
'neutron.agent.linux.utils.execute')
|
|
utils_exec_p.start()
|
|
|
|
drv_cls_p = mock.patch('neutron.agent.linux.interface.NullDriver')
|
|
driver_cls = drv_cls_p.start()
|
|
mock_driver = mock.MagicMock()
|
|
mock_driver.DEV_NAME_LEN = (
|
|
interface.LinuxInterfaceDriver.DEV_NAME_LEN)
|
|
driver_cls.return_value = mock_driver
|
|
|
|
l3_plugin_p = mock.patch(
|
|
'neutron.agent.l3_agent.L3PluginApi')
|
|
l3_plugin_cls = l3_plugin_p.start()
|
|
l3_plugin_cls.return_value = mock.MagicMock()
|
|
|
|
self.external_process_p = mock.patch(
|
|
'neutron.agent.linux.external_process.ProcessManager'
|
|
)
|
|
self.external_process_p.start()
|
|
looping_call_p = mock.patch(
|
|
'neutron.openstack.common.loopingcall.FixedIntervalLoopingCall')
|
|
looping_call_p.start()
|
|
self.agent = l3_agent.L3NATAgent(HOSTNAME)
|
|
|
|
def test_spawn_metadata_proxy(self):
|
|
router_id = _uuid()
|
|
metadata_port = 8080
|
|
ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
|
|
|
|
cfg.CONF.set_override('metadata_port', metadata_port)
|
|
cfg.CONF.set_override('log_file', 'test.log')
|
|
cfg.CONF.set_override('debug', True)
|
|
|
|
self.external_process_p.stop()
|
|
ri = l3_agent.RouterInfo(router_id, None, None)
|
|
try:
|
|
with mock.patch(ip_class_path) as ip_mock:
|
|
self.agent._spawn_metadata_proxy(ri.router_id, ri.ns_name)
|
|
ip_mock.assert_has_calls([
|
|
mock.call('sudo', ri.ns_name),
|
|
mock.call().netns.execute([
|
|
'neutron-ns-metadata-proxy',
|
|
mock.ANY,
|
|
mock.ANY,
|
|
'--router_id=%s' % router_id,
|
|
mock.ANY,
|
|
'--metadata_port=%s' % metadata_port,
|
|
'--debug',
|
|
'--log-file=neutron-ns-metadata-proxy-%s.log' %
|
|
router_id
|
|
], addl_env=None)
|
|
])
|
|
finally:
|
|
self.external_process_p.start()
|