neutron/neutron/tests/unit/agent/l3/test_router_info.py
Slawek Kaplonski 21cddc47b4 Fix update of ports cache in router_info class
RouterInfo class has got internal_ports cache which is updated
in _process_internal_ports() method.
There was an issue in this updates logic because it was
iterating through enumerate local variable "internal_ports"
which represents current router ports and if such current port
was found in updated_ports list it was storred in
RouterInfo().internal_ports variable under same index as was
found in "internal_ports" local variable.
This sometimes leads to an issue because same port can be
stored under different index in internal_ports and
RouterInfo().internal_ports lists thus wrong port in
RouterInfo().internal_ports was overwritten.

Such issue leads to problem with generating radvd config file
because in ports cache list there was duplicate info about same port
so radvd config file contained duplicate interface definitions too.

This should be properly fixed by changing RouterInfo.internal_ports
to be a dict instead of list of ports but such patch would be much
bigger and (possibly) harded to backport to stable branches.

Change-Id: I2e38457942518c8a3e07e606091bb6720317b77e
Closes-Bug: #1813279
2019-02-07 15:49:59 +01:00

532 lines
22 KiB
Python

# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron_lib import constants as lib_constants
from neutron_lib.exceptions import l3 as l3_exc
from oslo_utils import uuidutils
from neutron.agent.l3 import router_info
from neutron.agent.linux import ip_lib
from neutron.conf.agent import common as config
from neutron.conf.agent.l3 import config as l3_config
from neutron.tests import base
_uuid = uuidutils.generate_uuid
class TestRouterInfo(base.BaseTestCase):
def setUp(self):
super(TestRouterInfo, self).setUp()
conf = config.setup_conf()
l3_config.register_l3_agent_config_opts(l3_config.OPTS, conf)
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
self.ri_kwargs = {'agent_conf': conf,
'interface_driver': mock.sentinel.interface_driver}
def _check_agent_method_called(self, calls):
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):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
ri.router = {}
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'}
ri.update_routing_table('replace', fake_route1)
expected = [['ip', 'route', 'replace', 'to', '135.207.0.0/16',
'via', '1.2.3.4']]
self._check_agent_method_called(expected)
ri.update_routing_table('delete', fake_route1)
expected = [['ip', 'route', 'delete', 'to', '135.207.0.0/16',
'via', '1.2.3.4']]
self._check_agent_method_called(expected)
ri.update_routing_table('replace', fake_route2)
expected = [['ip', 'route', 'replace', 'to', '135.207.111.111/32',
'via', '1.2.3.4']]
self._check_agent_method_called(expected)
ri.update_routing_table('delete', fake_route2)
expected = [['ip', 'route', 'delete', 'to', '135.207.111.111/32',
'via', '1.2.3.4']]
self._check_agent_method_called(expected)
def test_update_routing_table(self):
# Just verify the correct namespace was used in the call
uuid = _uuid()
netns = 'qrouter-' + uuid
fake_route1 = {'destination': '135.207.0.0/16',
'nexthop': '1.2.3.4'}
ri = router_info.RouterInfo(mock.Mock(), uuid,
{'id': uuid}, **self.ri_kwargs)
ri._update_routing_table = mock.Mock()
ri.update_routing_table('replace', fake_route1)
ri._update_routing_table.assert_called_once_with('replace',
fake_route1,
netns)
def test_routes_updated(self):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
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
ri.routes_updated(fake_old_routes, fake_new_routes)
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(expected)
ri.routes = fake_new_routes
fake_new_routes = [{'destination': "110.100.30.0/24",
'nexthop': "10.100.10.30"}]
ri.router['routes'] = fake_new_routes
ri.routes_updated(ri.routes, fake_new_routes)
expected = [['ip', 'route', 'delete', 'to', '110.100.31.0/24',
'via', '10.100.10.30']]
self._check_agent_method_called(expected)
fake_new_routes = []
ri.router['routes'] = fake_new_routes
ri.routes_updated(ri.routes, fake_new_routes)
expected = [['ip', 'route', 'delete', 'to', '110.100.30.0/24',
'via', '10.100.10.30']]
self._check_agent_method_called(expected)
def test__process_pd_iptables_rules(self):
subnet_id = _uuid()
ex_gw_port = {'id': _uuid()}
prefix = '2001:db8:cafe::/64'
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
ipv6_mangle = ri.iptables_manager.ipv6['mangle'] = mock.MagicMock()
ri.get_ex_gw_port = mock.Mock(return_value=ex_gw_port)
ri.get_external_device_name = mock.Mock(return_value='fake_device')
ri.get_address_scope_mark_mask = mock.Mock(return_value='fake_mark')
ri._process_pd_iptables_rules(prefix, subnet_id)
mangle_rule = '-d %s ' % prefix
mangle_rule += ri.address_scope_mangle_rule('fake_device', 'fake_mark')
ipv6_mangle.add_rule.assert_called_once_with(
'scope',
mangle_rule,
tag='prefix_delegation_%s' % subnet_id)
def test_add_ports_address_scope_iptables(self):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
port = {
'id': _uuid(),
'fixed_ips': [{'ip_address': '172.9.9.9'}],
'address_scopes': {lib_constants.IP_VERSION_4: '1234'}
}
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
ri.get_address_scope_mark_mask = mock.Mock(return_value='fake_mark')
ri.get_internal_device_name = mock.Mock(return_value='fake_device')
ri.rt_tables_manager = mock.MagicMock()
ri.process_external_port_address_scope_routing = mock.Mock()
ri.process_floating_ip_address_scope_rules = mock.Mock()
ri.iptables_manager._apply = mock.Mock()
ri.router[lib_constants.INTERFACE_KEY] = [port]
ri.process_address_scope()
ipv4_mangle.add_rule.assert_called_once_with(
'scope', ri.address_scope_mangle_rule('fake_device', 'fake_mark'))
def test_address_scope_mark_ids_handling(self):
mark_ids = set(range(router_info.ADDRESS_SCOPE_MARK_ID_MIN,
router_info.ADDRESS_SCOPE_MARK_ID_MAX))
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
# first mark id is used for the default address scope
scope_to_mark_id = {router_info.DEFAULT_ADDRESS_SCOPE: mark_ids.pop()}
self.assertEqual(scope_to_mark_id, ri._address_scope_to_mark_id)
self.assertEqual(mark_ids, ri.available_mark_ids)
# new id should be used for new address scope
ri.get_address_scope_mark_mask('new_scope')
scope_to_mark_id['new_scope'] = mark_ids.pop()
self.assertEqual(scope_to_mark_id, ri._address_scope_to_mark_id)
self.assertEqual(mark_ids, ri.available_mark_ids)
# new router should have it's own mark ids set
new_mark_ids = set(range(router_info.ADDRESS_SCOPE_MARK_ID_MIN,
router_info.ADDRESS_SCOPE_MARK_ID_MAX))
new_ri = router_info.RouterInfo(mock.Mock(), _uuid(),
{}, **self.ri_kwargs)
new_mark_ids.pop()
self.assertEqual(new_mark_ids, new_ri.available_mark_ids)
self.assertNotEqual(ri.available_mark_ids, new_ri.available_mark_ids)
def test_process_delete(self):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
ri.router = {'id': _uuid()}
with mock.patch.object(ri, '_process_internal_ports') as p_i_p,\
mock.patch.object(ri, '_process_external_on_delete') as p_e_o_d:
self.mock_ip.netns.exists.return_value = False
ri.process_delete()
self.assertFalse(p_i_p.called)
self.assertFalse(p_e_o_d.called)
p_i_p.reset_mock()
p_e_o_d.reset_mock()
self.mock_ip.netns.exists.return_value = True
ri.process_delete()
p_i_p.assert_called_once_with()
p_e_o_d.assert_called_once_with()
def test__update_internal_ports_cache(self):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
ri.internal_ports = [
{'id': 'port-id-1', 'mtu': 1500},
{'id': 'port-id-2', 'mtu': 2000}]
initial_internal_ports = ri.internal_ports[:]
# Test add new element to the cache
new_port = {'id': 'new-port-id', 'mtu': 1500}
ri._update_internal_ports_cache(new_port)
self.assertEqual(
initial_internal_ports + [new_port],
ri.internal_ports)
# Test update existing port in cache
updated_port = new_port.copy()
updated_port['mtu'] = 2500
ri._update_internal_ports_cache(updated_port)
self.assertEqual(
initial_internal_ports + [updated_port],
ri.internal_ports)
class BasicRouterTestCaseFramework(base.BaseTestCase):
def _create_router(self, router=None, **kwargs):
if not router:
router = mock.MagicMock()
self.agent_conf = mock.Mock()
self.router_id = _uuid()
return router_info.RouterInfo(mock.Mock(),
self.router_id,
router,
self.agent_conf,
mock.sentinel.interface_driver,
**kwargs)
class TestBasicRouterOperations(BasicRouterTestCaseFramework):
def test_get_floating_ips(self):
router = mock.MagicMock()
router.get.return_value = [mock.sentinel.floating_ip]
ri = self._create_router(router)
fips = ri.get_floating_ips()
self.assertEqual([mock.sentinel.floating_ip], fips)
def test_process_floating_ip_nat_rules(self):
ri = self._create_router()
fips = [{'fixed_ip_address': mock.sentinel.ip,
'floating_ip_address': mock.sentinel.fip}]
ri.get_floating_ips = mock.Mock(return_value=fips)
ri.iptables_manager = mock.MagicMock()
ipv4_nat = ri.iptables_manager.ipv4['nat']
ri.floating_forward_rules = mock.Mock(
return_value=[(mock.sentinel.chain, mock.sentinel.rule)])
ri.process_floating_ip_nat_rules()
# Be sure that the rules are cleared first and apply is called last
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
ipv4_nat.mock_calls[0])
self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1])
# Be sure that add_rule is called somewhere in the middle
ipv4_nat.add_rule.assert_called_once_with(mock.sentinel.chain,
mock.sentinel.rule,
tag='floating_ip')
def test_process_floating_ip_nat_rules_removed(self):
ri = self._create_router()
ri.get_floating_ips = mock.Mock(return_value=[])
ri.iptables_manager = mock.MagicMock()
ipv4_nat = ri.iptables_manager.ipv4['nat']
ri.process_floating_ip_nat_rules()
# Be sure that the rules are cleared first and apply is called last
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
ipv4_nat.mock_calls[0])
self.assertEqual(mock.call.apply(), ri.iptables_manager.mock_calls[-1])
# Be sure that add_rule is called somewhere in the middle
self.assertFalse(ipv4_nat.add_rule.called)
def test_process_floating_ip_address_scope_rules_diff_scopes(self):
ri = self._create_router()
fips = [{'fixed_ip_address': mock.sentinel.ip,
'floating_ip_address': mock.sentinel.fip,
'fixed_ip_address_scope': 'scope1'}]
ri.get_floating_ips = mock.Mock(return_value=fips)
ri._get_external_address_scope = mock.Mock(return_value='scope2')
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
ri.floating_mangle_rules = mock.Mock(
return_value=[(mock.sentinel.chain1, mock.sentinel.rule1)])
ri.get_external_device_name = mock.Mock()
ri.process_floating_ip_address_scope_rules()
# Be sure that the rules are cleared first
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
ipv4_mangle.mock_calls[0])
# Be sure that add_rule is called somewhere in the middle
self.assertEqual(1, ipv4_mangle.add_rule.call_count)
self.assertEqual(mock.call.add_rule(mock.sentinel.chain1,
mock.sentinel.rule1,
tag='floating_ip'),
ipv4_mangle.mock_calls[1])
def test_process_floating_ip_address_scope_rules_same_scopes(self):
ri = self._create_router()
fips = [{'fixed_ip_address': mock.sentinel.ip,
'floating_ip_address': mock.sentinel.fip,
'fixed_ip_address_scope': 'scope1'}]
ri.get_floating_ips = mock.Mock(return_value=fips)
ri._get_external_address_scope = mock.Mock(return_value='scope1')
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
ri.process_floating_ip_address_scope_rules()
# Be sure that the rules are cleared first
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
ipv4_mangle.mock_calls[0])
# Be sure that add_rule is not called somewhere in the middle
self.assertFalse(ipv4_mangle.add_rule.called)
def test_process_floating_ip_mangle_rules_removed(self):
ri = self._create_router()
ri.get_floating_ips = mock.Mock(return_value=[])
ipv4_mangle = ri.iptables_manager.ipv4['mangle'] = mock.MagicMock()
ri.process_floating_ip_address_scope_rules()
# Be sure that the rules are cleared first
self.assertEqual(mock.call.clear_rules_by_tag('floating_ip'),
ipv4_mangle.mock_calls[0])
# Be sure that add_rule is not called somewhere in the middle
self.assertFalse(ipv4_mangle.add_rule.called)
def _test_add_fip_addr_to_device_error(self, device):
ri = self._create_router()
ip = '15.1.2.3'
result = ri._add_fip_addr_to_device(
{'id': mock.sentinel.id, 'floating_ip_address': ip}, device)
device.addr.add.assert_called_with(ip + '/32')
return result
def test__add_fip_addr_to_device(self):
result = self._test_add_fip_addr_to_device_error(mock.Mock())
self.assertTrue(result)
def test__add_fip_addr_to_device_error(self):
device = mock.Mock()
device.addr.add.side_effect = RuntimeError
result = self._test_add_fip_addr_to_device_error(device)
self.assertFalse(result)
def test_process_snat_dnat_for_fip(self):
ri = self._create_router()
ri.process_floating_ip_nat_rules = mock.Mock(side_effect=Exception)
self.assertRaises(l3_exc.FloatingIpSetupException,
ri.process_snat_dnat_for_fip)
ri.process_floating_ip_nat_rules.assert_called_once_with()
def test_put_fips_in_error_state(self):
ri = self._create_router()
ri.router = mock.Mock()
ri.router.get.return_value = [{'id': mock.sentinel.id1},
{'id': mock.sentinel.id2}]
statuses = ri.put_fips_in_error_state()
expected = [{mock.sentinel.id1: lib_constants.FLOATINGIP_STATUS_ERROR,
mock.sentinel.id2: lib_constants.FLOATINGIP_STATUS_ERROR}]
self.assertNotEqual(expected, statuses)
def test_configure_fip_addresses(self):
ri = self._create_router()
ri.process_floating_ip_addresses = mock.Mock(
side_effect=Exception)
self.assertRaises(l3_exc.FloatingIpSetupException,
ri.configure_fip_addresses,
mock.sentinel.interface_name)
ri.process_floating_ip_addresses.assert_called_once_with(
mock.sentinel.interface_name)
def test_get_router_cidrs_returns_cidrs(self):
ri = self._create_router()
addresses = ['15.1.2.2/24', '15.1.2.3/32']
device = mock.MagicMock()
device.addr.list.return_value = [{'cidr': addresses[0]},
{'cidr': addresses[1]}]
self.assertEqual(set(addresses), ri.get_router_cidrs(device))
@mock.patch.object(ip_lib, 'IPDevice')
class TestFloatingIpWithMockDevice(BasicRouterTestCaseFramework):
def test_process_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',
'status': lib_constants.FLOATINGIP_STATUS_DOWN
}
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
ri = self._create_router()
ri.get_floating_ips = mock.Mock(return_value=[fip])
fip_statuses = ri.process_floating_ip_addresses(
mock.sentinel.interface_name)
self.assertEqual({fip_id: lib_constants.FLOATINGIP_STATUS_ACTIVE},
fip_statuses)
self.assertFalse(device.addr.add.called)
self.assertFalse(device.addr.delete.called)
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 = self._create_router()
ri.floating_ips = [fip]
ri.get_floating_ips = mock.Mock(return_value=[])
fip_statuses = ri.process_floating_ip_addresses(
mock.sentinel.interface_name)
self.assertIsNone(fip_statuses.get(fip_id))
def test_process_router_floating_ip_with_device_add_error(self, IPDevice):
IPDevice.return_value = device = mock.Mock(side_effect=RuntimeError)
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',
'status': 'DOWN'
}
ri = self._create_router()
ri.add_floating_ip = mock.Mock(
return_value=lib_constants.FLOATINGIP_STATUS_ERROR)
ri.get_floating_ips = mock.Mock(return_value=[fip])
fip_statuses = ri.process_floating_ip_addresses(
mock.sentinel.interface_name)
self.assertEqual({fip_id: lib_constants.FLOATINGIP_STATUS_ERROR},
fip_statuses)
# TODO(mrsmith): refactor for DVR cases
def test_process_floating_ip_addresses_remove(self, IPDevice):
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
ri = self._create_router()
ri.remove_floating_ip = mock.Mock()
ri.router.get = mock.Mock(return_value=[])
fip_statuses = ri.process_floating_ip_addresses(
mock.sentinel.interface_name)
self.assertEqual({}, fip_statuses)
ri.remove_floating_ip.assert_called_once_with(device, '15.1.2.3/32')
def test_process_floating_ip_reassignment(self, IPDevice):
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = [{'cidr': '15.1.2.3/32'}]
fip_id = _uuid()
fip = {
'id': fip_id, 'port_id': _uuid(),
'floating_ip_address': '15.1.2.3',
'fixed_ip_address': '192.168.0.3',
'status': 'DOWN'
}
ri = self._create_router()
ri.get_floating_ips = mock.Mock(return_value=[fip])
ri.move_floating_ip = mock.Mock()
ri.fip_map = {'15.1.2.3': '192.168.0.2'}
ri.process_floating_ip_addresses(mock.sentinel.interface_name)
ri.move_floating_ip.assert_called_once_with(fip)
def test_process_floating_ip_addresses_gw_secondary_ip_not_removed(
self, IPDevice):
IPDevice.return_value = device = mock.Mock()
device.addr.list.return_value = [{'cidr': '1.1.1.1/16'},
{'cidr': '2.2.2.2/32'},
{'cidr': '3.3.3.3/32'},
{'cidr': '4.4.4.4/32'}]
ri = self._create_router()
ri.get_floating_ips = mock.Mock(return_value=[
{'id': _uuid(),
'floating_ip_address': '3.3.3.3',
'status': 'DOWN'}])
ri.add_floating_ip = mock.Mock()
ri.get_ex_gw_port = mock.Mock(return_value={
"fixed_ips": [{"ip_address": "1.1.1.1"},
{"ip_address": "2.2.2.2"}]})
ri.remove_floating_ip = mock.Mock()
ri.process_floating_ip_addresses("qg-fake-device")
ri.remove_floating_ip.assert_called_once_with(device, '4.4.4.4/32')