if namespace is None:
device = ip_lib.IPDevice(interface_name,
self.root_helper)
- device.route.pullup_route(interface_name)
+ device.route.pullup_route()
if self.conf.enable_metadata_network:
meta_cidr = netaddr.IPNetwork(METADATA_DEFAULT_IP)
gw_ip = ex_gw_port['subnet']['gateway_ip']
if ex_gw_port['subnet']['gateway_ip']:
- cmd = ['route', 'add', 'default', 'gw', gw_ip]
- if self.conf.use_namespaces:
- ip_wrapper = ip_lib.IPWrapper(self.root_helper,
- namespace=ri.ns_name())
- ip_wrapper.netns.execute(cmd, check_exit_code=False)
- else:
- utils.execute(cmd, check_exit_code=False,
- root_helper=self.root_helper)
+ ip = ip_lib.IPWrapper(self.root_helper, ri.ns_name())
+ ip.route.add_gateway(gw_ip)
for (c, r) in self.external_gateway_nat_rules(ex_gw_ip,
internal_cidrs,
def after_start(self):
LOG.info(_("L3 agent started"))
- def _update_routing_table(self, ri, operation, route):
- cmd = ['ip', 'route', operation, 'to', route['destination'],
- 'via', route['nexthop']]
- #TODO(nati) move this code to iplib
- if self.conf.use_namespaces:
- ip_wrapper = ip_lib.IPWrapper(self.conf.root_helper,
- namespace=ri.ns_name())
- ip_wrapper.netns.execute(cmd, check_exit_code=False)
- else:
- utils.execute(cmd, check_exit_code=False,
- root_helper=self.conf.root_helper)
-
def routes_updated(self, ri):
+ ip = ip_lib.IPWrapper(self.conf.root_helper, ri.ns_name())
+
new_routes = ri.router['routes']
old_routes = ri.routes
adds, removes = common_utils.diff_list_of_dict(old_routes,
for del_route in removes:
if route['destination'] == del_route['destination']:
removes.remove(del_route)
- #replace success even if there is no existing route
- self._update_routing_table(ri, 'replace', route)
+
+ ip.route.add(route['destination'], route['nexthop'])
for route in removes:
LOG.debug(_("Removed route entry is '%s'"), route)
- self._update_routing_table(ri, 'delete', route)
+ ip.route.delete(route['destination'], route['nexthop'])
ri.routes = new_routes
super(IPWrapper, self).__init__(root_helper=root_helper,
namespace=namespace)
self.netns = IpNetnsCommand(self)
+ self.route = IpRouteCommand(self)
def device(self, name):
return IPDevice(name, self.root_helper, self.namespace)
self.name = name
self.link = IpLinkCommand(self)
self.addr = IpAddrCommand(self)
- self.route = IpRouteCommand(self)
+ self.route = IpRouteDeviceCommand(self)
def __eq__(self, other):
return (other is not None and self.name == other.name
return retval
-class IpRouteCommand(IpDeviceCommandBase):
+class IpRouteCommand(IpCommandBase):
COMMAND = 'route'
- def add_gateway(self, gateway, metric=None):
- args = ['replace', 'default', 'via', gateway]
+ def add_gateway(self, gw, metric=None, dev=None):
+ """Adds a default gateway or replaces an existing one."""
+
+ args = ['replace', 'default', 'via', gw]
if metric:
args += ['metric', metric]
- args += ['dev', self.name]
- self._as_root(*args)
+ if dev:
+ args += ['dev', dev]
- def delete_gateway(self, gateway):
- self._as_root('del',
- 'default',
- 'via',
- gateway,
- 'dev',
- self.name)
+ self._as_root(*args)
- def get_gateway(self, scope=None, filters=None):
- if filters is None:
- filters = []
+ def delete_gateway(self, gw, dev=None):
+ args = ['delete', 'default', 'via', gw]
+ if dev:
+ args += ['dev', dev]
- retval = None
+ self._as_root(*args)
+ def get_gateway(self, scope=None, filters=None, dev=None):
+ args = ['list']
+ if dev:
+ args += ['dev', self.name]
if scope:
- filters += ['scope', scope]
+ args += ['scope', scope]
+ if filters:
+ args += filters
- route_list_lines = self._run('list', 'dev', self.name,
- *filters).split('\n')
+ route_list_lines = self._run(*args).split('\n')
default_route_line = next((x.strip() for x in
route_list_lines if
x.strip().startswith('default')), None)
if parts_has_metric:
retval.update(metric=int(parts[metric_index]))
- return retval
+ return retval
def pullup_route(self, interface_name):
"""
self._as_root('append', subnet, 'proto', 'kernel',
'dev', device)
+ def add(self, destination, nexthop, dev=None):
+ """Adds a new route or replaces an existing one."""
+
+ args = ['replace', 'to', destination, 'via', nexthop]
+ if dev:
+ args += ['dev', dev]
+
+ self._as_root(*args)
+
+ def delete(self, destination, nexthop, dev=None):
+ args = ['delete', 'to', destination, 'via', nexthop]
+ if dev:
+ args += ['dev', dev]
+
+ self._as_root(*args)
+
+
+class IpRouteDeviceCommand(IpDeviceCommandBase):
+ """Wrapper for ip route actions which are bound to a specific device"""
+
+ def __init__(self, parent):
+ super(IpRouteDeviceCommand, self).__init__(parent)
+
+ self._route = IpRouteCommand(parent)
+
+ def add_gateway(self, gw, metric=None):
+ return self._route.add_gateway(gw, metric, self.name)
+
+ def delete_gateway(self, gw):
+ return self._route.delete_gateway(gw, self.name)
+
+ def get_gateway(self, scope=None, filters=None):
+ return self._route.get_gateway(scope, filters, self.name)
+
+ def pullup_route(self):
+ return self._route.pullup_route(self.name)
+
+ def add(self, destination, nexthop):
+ return self._route.add(destination, nexthop, self.name)
+
+ def delete(self, destination, nexthop):
+ return self._route.delete(destination, nexthop, self.name)
+
class IpNetnsCommand(IpCommandBase):
COMMAND = 'netns'
driver_cls.return_value = self.mock_driver
self.ip_cls_p = mock.patch('quantum.agent.linux.ip_lib.IPWrapper')
- ip_cls = self.ip_cls_p.start()
+ self.ip_cls = self.ip_cls_p.start()
self.mock_ip = mock.MagicMock()
- ip_cls.return_value = self.mock_ip
+ self.ip_cls.return_value = self.mock_ip
self.l3pluginApi_cls_p = mock.patch(
'quantum.agent.l3_agent.L3PluginApi')
def testAgentRemoveFloatingIP(self):
self._test_floating_ip_action('remove')
- def _check_agent_method_called(self, agent, calls, namespace):
- if namespace:
- self.mock_ip.netns.execute.assert_has_calls(
- [mock.call(call, check_exit_code=False) for call in calls],
- any_order=True)
- else:
- self.utils_exec.assert_has_calls([
- mock.call(call, root_helper='sudo',
- 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,
- self.conf.use_namespaces,
- None)
- 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 testAgentRoutingTableUpdated(self):
- self._test_routing_table_update(namespace=True)
+ def _check_route_calls(self, action, calls, namespace):
+ mocks = {
+ 'replace': self.mock_ip.route.add,
+ 'delete': self.mock_ip.route.delete
+ }
- def testAgentRoutingTableUpdatedNoNameSpace(self):
- self._test_routing_table_update(namespace=False)
+ m = mocks[action]
+ m.assert_has_calls([mock.call(*call) for call in calls],
+ any_order=True)
def testRoutesUpdated(self):
self._test_routes_updated(namespace=True)
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']]
+ expected = [['110.100.30.0/24', '10.100.10.30'],
+ ['110.100.31.0/24', '10.100.10.30']]
- self._check_agent_method_called(agent, expected, namespace)
+ self._check_route_calls('replace', expected, ri.ns_name())
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']]
+ expected = [['110.100.31.0/24', '10.100.10.30']]
- self._check_agent_method_called(agent, expected, namespace)
+ self._check_route_calls('delete', expected, ri.ns_name())
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)
+ expected = [['110.100.30.0/24', '10.100.10.30']]
+ self._check_route_calls('delete', expected, ri.ns_name())
def testProcessRouter(self):
class TestIpRouteCommand(TestIPCmdBase):
def setUp(self):
super(TestIpRouteCommand, self).setUp()
- self.parent.name = 'eth0'
self.command = 'route'
self.route_cmd = ip_lib.IpRouteCommand(self.parent)
+ def test_add(self):
+ dst = '10.0.1.0/24'
+ nexthop = '192.168.1.1'
+
+ self.route_cmd.add(dst, nexthop)
+ self._assert_sudo([], ('replace', 'to', dst, 'via', nexthop))
+
+ def test_delete(self):
+ dst = '10.0.1.0/24'
+ nexthop = '192.168.1.1'
+
+ self.route_cmd.delete(dst, nexthop)
+ self._assert_sudo([], ('delete', 'to', dst, 'via', nexthop))
+
def test_add_gateway(self):
+ gw = '10.0.1.1'
+
+ self.route_cmd.add_gateway(gw)
+ self._assert_sudo([], ('replace', 'default', 'via', gw))
+
+ def test_delete_gateway(self):
+ gw = '10.0.1.1'
+
+ self.route_cmd.delete_gateway(gw)
+ self._assert_sudo([], ('delete', 'default', 'via', gw))
+
+ def test_add_gateway_with_metric_dev(self):
gateway = '192.168.45.100'
metric = 100
- self.route_cmd.add_gateway(gateway, metric)
+ dev = 'eth0'
+
+ self.route_cmd.add_gateway(gateway, metric, dev)
self._assert_sudo([],
('replace', 'default', 'via', gateway,
'metric', metric,
- 'dev', self.parent.name))
+ 'dev', dev))
- def test_del_gateway(self):
+ def test_delete_gateway_with_dev(self):
gateway = '192.168.45.100'
- self.route_cmd.delete_gateway(gateway)
+ dev = 'eth0'
+
+ self.route_cmd.delete_gateway(gateway, dev)
self._assert_sudo([],
- ('del', 'default', 'via', gateway,
- 'dev', self.parent.name))
+ ('delete', 'default', 'via', gateway,
+ 'dev', dev))
def test_get_gateway(self):
test_cases = [{'sample': GATEWAY_SAMPLE1,
self.assertEqual(len(self.parent._run.mock_calls), 2)
+class TestIpRouteDeviceCommand(TestIPCmdBase):
+ def setUp(self):
+ super(TestIpRouteDeviceCommand, self).setUp()
+ self.parent.name = 'eth0'
+ self.command = 'route'
+ self.route_cmd = ip_lib.IpRouteDeviceCommand(self.parent)
+
+ route_p = mock.patch.object(self.route_cmd, '_route')
+ self._route = route_p.start()
+
+ def test_add_gateway(self):
+ gw = '10.0.0.1'
+ metric = 100
+
+ self.route_cmd.add_gateway(gw, metric)
+ self._route.add_gateway.assert_called_once_with(gw, metric,
+ self.route_cmd.name)
+
+ def test_delete_gateway(self):
+ gw = '10.0.0.1'
+
+ self.route_cmd.delete_gateway(gw)
+ self._route.delete_gateway.assert_called_once_with(gw,
+ self.route_cmd.name)
+
+ def test_get_gateway(self):
+ scope = 'scope'
+ filters = []
+
+ self.route_cmd.get_gateway(scope, filters)
+ self._route.get_gateway.assert_called_once_with(scope, filters,
+ self.route_cmd.name)
+
+ def test_add(self):
+ dst = '8.8.8.8'
+ via = '10.0.1.4'
+
+ self.route_cmd.add(dst, via)
+ self._route.add.assert_called_once_with(dst, via, self.route_cmd.name)
+
+ def test_delete(self):
+ dst = '8.8.8.8'
+ via = '10.0.1.4'
+
+ self.route_cmd.delete(dst, via)
+ self._route.delete.assert_called_once_with(dst, via,
+ self.route_cmd.name)
+
+ def test_pullup_route(self):
+ self.route_cmd.pullup_route()
+ self._route.pullup_route.assert_called_once_with(self.route_cmd.name)
+
+
class TestIpNetnsCommand(TestIPCmdBase):
def setUp(self):
super(TestIpNetnsCommand, self).setUp()