Merge pull request #73 from ryanpetrello/linux
Convert route management from BSD `route` to Linux `/sbin/ip`
This commit is contained in:
commit
195c0fc038
|
@ -16,6 +16,7 @@
|
||||||
|
|
||||||
|
|
||||||
import functools
|
import functools
|
||||||
|
import logging
|
||||||
import re
|
import re
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
|
@ -23,6 +24,8 @@ import netaddr
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
from akanda.router.drivers import base
|
from akanda.router.drivers import base
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
GENERIC_IFNAME = 'ge'
|
GENERIC_IFNAME = 'ge'
|
||||||
PHYSICAL_INTERFACES = ['lo', 'eth', 'em', 're', 'en', 'vio', 'vtnet']
|
PHYSICAL_INTERFACES = ['lo', 'eth', 'em', 're', 'en', 'vio', 'vtnet']
|
||||||
|
@ -158,6 +161,141 @@ class IPManager(base.Manager):
|
||||||
self.update_interface(primary)
|
self.update_interface(primary)
|
||||||
return ip_str
|
return ip_str
|
||||||
|
|
||||||
|
def update_default_gateway(self, config):
|
||||||
|
# Track whether we have set the default gateways, by IP
|
||||||
|
# version.
|
||||||
|
gw_set = {
|
||||||
|
4: False,
|
||||||
|
6: False,
|
||||||
|
}
|
||||||
|
|
||||||
|
ifname = None
|
||||||
|
for net in config.networks:
|
||||||
|
if not net.is_external_network:
|
||||||
|
continue
|
||||||
|
ifname = net.interface.ifname
|
||||||
|
|
||||||
|
# The default v4 gateway is pulled out as a special case
|
||||||
|
# because we only want one but we might have multiple v4
|
||||||
|
# subnets on the external network. However, sometimes the RUG
|
||||||
|
# can't figure out what that value is, because it thinks we
|
||||||
|
# don't have any external IP addresses, yet. In that case, it
|
||||||
|
# doesn't give us a default.
|
||||||
|
if config.default_v4_gateway:
|
||||||
|
self._set_default_gateway(config.default_v4_gateway, ifname)
|
||||||
|
gw_set[4] = True
|
||||||
|
|
||||||
|
# Look through our networks and make sure we have a default
|
||||||
|
# gateway set for each IP version, if we have an IP for that
|
||||||
|
# version on the external net. If we haven't already set the
|
||||||
|
# v4 gateway, this picks the gateway for the first subnet we
|
||||||
|
# find, which might be wrong.
|
||||||
|
for net in config.networks:
|
||||||
|
if not net.is_external_network:
|
||||||
|
continue
|
||||||
|
|
||||||
|
for subnet in net.subnets:
|
||||||
|
if subnet.gateway_ip and not gw_set[subnet.gateway_ip.version]:
|
||||||
|
self._set_default_gateway(
|
||||||
|
subnet.gateway_ip,
|
||||||
|
net.interface.ifname
|
||||||
|
)
|
||||||
|
gw_set[subnet.gateway_ip.version] = True
|
||||||
|
|
||||||
|
def update_host_routes(self, config, cache):
|
||||||
|
db = cache.get_or_create('host_routes', lambda: {})
|
||||||
|
for net in config.networks:
|
||||||
|
|
||||||
|
# For each subnet...
|
||||||
|
for subnet in net.subnets:
|
||||||
|
cidr = str(subnet.cidr)
|
||||||
|
|
||||||
|
# determine the set of previously written routes for this cidr
|
||||||
|
if cidr not in db:
|
||||||
|
db[cidr] = set()
|
||||||
|
|
||||||
|
current = db[cidr]
|
||||||
|
|
||||||
|
# build a set of new routes for this cidr
|
||||||
|
latest = set()
|
||||||
|
for r in subnet.host_routes:
|
||||||
|
latest.add((r.destination, r.next_hop))
|
||||||
|
|
||||||
|
# If the set of previously written routes contains routes that
|
||||||
|
# aren't defined in the new config, run commands to delete them
|
||||||
|
for x in current - latest:
|
||||||
|
if self._alter_route(net.interface.ifname, 'del', *x):
|
||||||
|
current.remove(x)
|
||||||
|
|
||||||
|
# If the new config contains routes that aren't defined in the
|
||||||
|
# set of previously written routes, run commands to add them
|
||||||
|
for x in latest - current:
|
||||||
|
if self._alter_route(net.interface.ifname, 'add', *x):
|
||||||
|
current.add(x)
|
||||||
|
|
||||||
|
if not current:
|
||||||
|
del db[cidr]
|
||||||
|
|
||||||
|
cache.set('host_routes', db)
|
||||||
|
|
||||||
|
def _get_default_gateway(self, version):
|
||||||
|
current = None
|
||||||
|
try:
|
||||||
|
cmd_out = self.sudo('-%s' % version, 'route', 'show')
|
||||||
|
except:
|
||||||
|
# assume the route is missing and use defaults
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
for l in cmd_out.splitlines():
|
||||||
|
l = l.strip()
|
||||||
|
if l.startswith('default'):
|
||||||
|
match = re.search('via (?P<gateway>[^ ]+)', l)
|
||||||
|
if match:
|
||||||
|
return match.group('gateway')
|
||||||
|
return current
|
||||||
|
|
||||||
|
def _set_default_gateway(self, gateway_ip, ifname):
|
||||||
|
version = 4
|
||||||
|
if gateway_ip.version == 6:
|
||||||
|
version = 6
|
||||||
|
current = self._get_default_gateway(version)
|
||||||
|
desired = str(gateway_ip)
|
||||||
|
ifname = self.generic_to_host(ifname)
|
||||||
|
|
||||||
|
if current and current != desired:
|
||||||
|
# Remove the current gateway and add the desired one
|
||||||
|
self.sudo(
|
||||||
|
'-%s' % version, 'route', 'del', 'default', 'via', current,
|
||||||
|
'dev', ifname
|
||||||
|
)
|
||||||
|
return self.sudo(
|
||||||
|
'-%s' % version, 'route', 'add', 'default', 'via', desired,
|
||||||
|
'dev', ifname
|
||||||
|
)
|
||||||
|
if not current:
|
||||||
|
# Add the desired gateway
|
||||||
|
return self.sudo(
|
||||||
|
'-%s' % version, 'route', 'add', 'default', 'via', desired,
|
||||||
|
'dev', ifname
|
||||||
|
)
|
||||||
|
|
||||||
|
def _alter_route(self, ifname, action, destination, next_hop):
|
||||||
|
version = destination.version
|
||||||
|
ifname = self.generic_to_host(ifname)
|
||||||
|
try:
|
||||||
|
LOG.debug(self.sudo(
|
||||||
|
'-%s' % version, 'route', action, str(destination), 'via',
|
||||||
|
str(next_hop), 'dev', ifname
|
||||||
|
))
|
||||||
|
return True
|
||||||
|
except RuntimeError as e:
|
||||||
|
# Since these are user-supplied custom routes, it's very possible
|
||||||
|
# that adding/removing them will fail. A failure to apply one of
|
||||||
|
# these custom rules, however, should *not* cause an overall router
|
||||||
|
# failure.
|
||||||
|
LOG.warn('Route could not be %sed: %s' % (action, unicode(e)))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def get_rug_address():
|
def get_rug_address():
|
||||||
""" Return the RUG address """
|
""" Return the RUG address """
|
||||||
|
|
|
@ -1,148 +0,0 @@
|
||||||
# Copyright 2014 DreamHost, LLC
|
|
||||||
#
|
|
||||||
# Author: DreamHost, LLC
|
|
||||||
#
|
|
||||||
# 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 logging
|
|
||||||
|
|
||||||
from akanda.router.drivers import base
|
|
||||||
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class RouteManager(base.Manager):
|
|
||||||
EXECUTABLE = '/sbin/route'
|
|
||||||
|
|
||||||
def __init__(self, root_helper='sudo'):
|
|
||||||
super(RouteManager, self).__init__(root_helper)
|
|
||||||
|
|
||||||
def update_default(self, config):
|
|
||||||
# Track whether we have set the default gateways, by IP
|
|
||||||
# version.
|
|
||||||
gw_set = {
|
|
||||||
4: False,
|
|
||||||
6: False,
|
|
||||||
}
|
|
||||||
|
|
||||||
# The default v4 gateway is pulled out as a special case
|
|
||||||
# because we only want one but we might have multiple v4
|
|
||||||
# subnets on the external network. However, sometimes the RUG
|
|
||||||
# can't figure out what that value is, because it thinks we
|
|
||||||
# don't have any external IP addresses, yet. In that case, it
|
|
||||||
# doesn't give us a default.
|
|
||||||
if config.default_v4_gateway:
|
|
||||||
self._set_default_gateway(config.default_v4_gateway)
|
|
||||||
gw_set[4] = True
|
|
||||||
|
|
||||||
# Look through our networks and make sure we have a default
|
|
||||||
# gateway set for each IP version, if we have an IP for that
|
|
||||||
# version on the external net. If we haven't already set the
|
|
||||||
# v4 gateway, this picks the gateway for the first subnet we
|
|
||||||
# find, which might be wrong.
|
|
||||||
for net in config.networks:
|
|
||||||
if not net.is_external_network:
|
|
||||||
continue
|
|
||||||
|
|
||||||
for subnet in net.subnets:
|
|
||||||
if subnet.gateway_ip and not gw_set[subnet.gateway_ip.version]:
|
|
||||||
self._set_default_gateway(subnet.gateway_ip)
|
|
||||||
gw_set[subnet.gateway_ip.version] = True
|
|
||||||
|
|
||||||
def update_host_routes(self, config, cache):
|
|
||||||
db = cache.get_or_create('host_routes', lambda: {})
|
|
||||||
for net in config.networks:
|
|
||||||
|
|
||||||
# For each subnet...
|
|
||||||
for subnet in net.subnets:
|
|
||||||
cidr = str(subnet.cidr)
|
|
||||||
|
|
||||||
# determine the set of previously written routes for this cidr
|
|
||||||
if cidr not in db:
|
|
||||||
db[cidr] = set()
|
|
||||||
|
|
||||||
current = db[cidr]
|
|
||||||
|
|
||||||
# build a set of new routes for this cidr
|
|
||||||
latest = set()
|
|
||||||
for r in subnet.host_routes:
|
|
||||||
latest.add((r.destination, r.next_hop))
|
|
||||||
|
|
||||||
# If the set of previously written routes contains routes that
|
|
||||||
# aren't defined in the new config, run commands to delete them
|
|
||||||
for x in current - latest:
|
|
||||||
if self._alter_route('delete', *x):
|
|
||||||
current.remove(x)
|
|
||||||
|
|
||||||
# If the new config contains routes that aren't defined in the
|
|
||||||
# set of previously written routes, run commands to add them
|
|
||||||
for x in latest - current:
|
|
||||||
if self._alter_route('add', *x):
|
|
||||||
current.add(x)
|
|
||||||
|
|
||||||
if not current:
|
|
||||||
del db[cidr]
|
|
||||||
|
|
||||||
cache.set('host_routes', db)
|
|
||||||
|
|
||||||
def _get_default_gateway(self, version):
|
|
||||||
current = None
|
|
||||||
try:
|
|
||||||
cmd_out = self.sudo('-n', 'get', version, 'default')
|
|
||||||
except:
|
|
||||||
# assume the route is missing and use defaults
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if 'no such process' in cmd_out.lower():
|
|
||||||
# There is no gateway
|
|
||||||
return None
|
|
||||||
for l in cmd_out.splitlines():
|
|
||||||
l = l.strip()
|
|
||||||
if l.startswith('gateway:'):
|
|
||||||
return l.partition(':')[-1].strip()
|
|
||||||
return current
|
|
||||||
|
|
||||||
def _set_default_gateway(self, gateway_ip):
|
|
||||||
version = '-inet'
|
|
||||||
if gateway_ip.version == 6:
|
|
||||||
version += '6'
|
|
||||||
current = self._get_default_gateway(version)
|
|
||||||
desired = str(gateway_ip)
|
|
||||||
|
|
||||||
if not current:
|
|
||||||
# Set the gateway
|
|
||||||
return self.sudo('add', version, 'default', desired)
|
|
||||||
if current != desired:
|
|
||||||
# Update the current gateway
|
|
||||||
return self.sudo('change', version, 'default', desired)
|
|
||||||
# Nothing to do
|
|
||||||
return ''
|
|
||||||
|
|
||||||
def _alter_route(self, action, destination, next_hop):
|
|
||||||
version = '-inet'
|
|
||||||
if destination.version == 6:
|
|
||||||
version += '6'
|
|
||||||
try:
|
|
||||||
LOG.debug(
|
|
||||||
self.sudo(action, version, str(destination), str(next_hop))
|
|
||||||
)
|
|
||||||
return True
|
|
||||||
except RuntimeError as e:
|
|
||||||
# Since these are user-supplied custom routes, it's very possible
|
|
||||||
# that adding/removing them will fail. A failure to apply one of
|
|
||||||
# these custom rules, however, should *not* cause an overall router
|
|
||||||
# failure.
|
|
||||||
LOG.warn('Route could not be %sed: %s' % (action, unicode(e)))
|
|
||||||
return False
|
|
|
@ -19,8 +19,7 @@ import os
|
||||||
import re
|
import re
|
||||||
|
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
from akanda.router.drivers import (bird, dnsmasq, ip, metadata, pf,
|
from akanda.router.drivers import (bird, dnsmasq, ip, metadata, pf, arp)
|
||||||
route, arp)
|
|
||||||
|
|
||||||
|
|
||||||
class Manager(object):
|
class Manager(object):
|
||||||
|
@ -88,8 +87,8 @@ class Manager(object):
|
||||||
mgr.update_conf(rule_data)
|
mgr.update_conf(rule_data)
|
||||||
|
|
||||||
def update_routes(self, cache):
|
def update_routes(self, cache):
|
||||||
mgr = route.RouteManager()
|
mgr = ip.IPManager()
|
||||||
mgr.update_default(self.config)
|
mgr.update_default_gateway(self.config)
|
||||||
mgr.update_host_routes(self.config, cache)
|
mgr.update_host_routes(self.config, cache)
|
||||||
|
|
||||||
def update_arp(self):
|
def update_arp(self):
|
||||||
|
|
|
@ -22,67 +22,52 @@ import netaddr
|
||||||
from dogpile.cache import make_region
|
from dogpile.cache import make_region
|
||||||
|
|
||||||
from akanda.router import models
|
from akanda.router import models
|
||||||
from akanda.router.drivers import route
|
from akanda.router.drivers import ip
|
||||||
|
|
||||||
|
|
||||||
class RouteTest(unittest2.TestCase):
|
class RouteTest(unittest2.TestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.mgr = route.RouteManager()
|
super(RouteTest, self).setUp()
|
||||||
|
self.mgr = ip.IPManager()
|
||||||
|
self.host_patch = mock.patch.object(
|
||||||
|
self.mgr, 'generic_to_host', lambda x: x.replace('ge', 'eth')
|
||||||
|
)
|
||||||
|
self.host_patch.start()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
super(RouteTest, self).tearDown()
|
||||||
|
self.host_patch.stop()
|
||||||
|
|
||||||
def test_get_default_gateway_v6_missing(self):
|
def test_get_default_gateway_v6_missing(self):
|
||||||
output = 'route: writing to routing socket: No such process\n'
|
output = ''
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
sudo.return_value = output
|
sudo.return_value = output
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
None,
|
None,
|
||||||
self.mgr._get_default_gateway('-inet6')
|
self.mgr._get_default_gateway(6)
|
||||||
)
|
)
|
||||||
sudo.assert_called_with('-n', 'get', '-inet6', 'default')
|
sudo.assert_called_with('-6', 'route', 'show')
|
||||||
|
|
||||||
def test_get_default_gateway_v6(self):
|
def test_get_default_gateway_v6(self):
|
||||||
output = """
|
output = "default via fe80::f816:3eff:fe33:deac dev eth2 metric 1024"
|
||||||
route to: ::
|
|
||||||
destination: ::
|
|
||||||
mask: default
|
|
||||||
gateway: fdee:9f85:83be::1
|
|
||||||
interface: vio1
|
|
||||||
if address: fdee:9f85:83be:0:f816:3eff:fe7b:6263
|
|
||||||
priority: 8 (static)
|
|
||||||
flags: <UP,GATEWAY,DONE,STATIC>
|
|
||||||
use mtu expire
|
|
||||||
0 0 0
|
|
||||||
"""
|
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
sudo.return_value = output
|
sudo.return_value = output
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'fdee:9f85:83be::1',
|
'fe80::f816:3eff:fe33:deac',
|
||||||
self.mgr._get_default_gateway('-inet6')
|
self.mgr._get_default_gateway(6)
|
||||||
)
|
)
|
||||||
sudo.assert_called_with('-n', 'get', '-inet6', 'default')
|
sudo.assert_called_with('-6', 'route', 'show')
|
||||||
|
|
||||||
def test_get_default_gateway_v4(self):
|
def test_get_default_gateway_v4(self):
|
||||||
output = """
|
output = "default via 192.168.122.1 dev eth0 metric 100"
|
||||||
route to: default
|
|
||||||
destination: default
|
|
||||||
mask: default
|
|
||||||
gateway: 192.168.122.1
|
|
||||||
interface: vio0
|
|
||||||
if address: 192.168.122.240
|
|
||||||
priority: 8 (static)
|
|
||||||
flags: <UP,GATEWAY,DONE,STATIC>
|
|
||||||
label: DHCLIENT 20978
|
|
||||||
use mtu expire
|
|
||||||
73687 0 0
|
|
||||||
sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
|
||||||
"""
|
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
sudo.return_value = output
|
sudo.return_value = output
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
'192.168.122.1',
|
'192.168.122.1',
|
||||||
self.mgr._get_default_gateway('-inet')
|
self.mgr._get_default_gateway(4)
|
||||||
)
|
)
|
||||||
sudo.assert_called_with('-n', 'get', '-inet', 'default')
|
sudo.assert_called_with('-4', 'route', 'show')
|
||||||
|
|
||||||
def test_set_default_v4_matches_current(self):
|
def test_set_default_v4_matches_current(self):
|
||||||
ip_s = '192.168.122.1'
|
ip_s = '192.168.122.1'
|
||||||
|
@ -93,7 +78,7 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
get.return_value = ip_s
|
get.return_value = ip_s
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
sudo.side_effect = AssertionError('should not be called')
|
sudo.side_effect = AssertionError('should not be called')
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
|
|
||||||
def test_set_default_v4_changes_current(self):
|
def test_set_default_v4_changes_current(self):
|
||||||
ip_s = '192.168.122.1'
|
ip_s = '192.168.122.1'
|
||||||
|
@ -101,10 +86,19 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
ip.version = 4
|
ip.version = 4
|
||||||
ip.__str__.return_value = ip_s
|
ip.__str__.return_value = ip_s
|
||||||
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
||||||
get.return_value = '192.168.122.254'
|
get.return_value = '192.168.122.254'
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
sudo.assert_called_with('change', '-inet', 'default', ip_s)
|
assert sudo.call_args_list == [
|
||||||
|
mock.call(
|
||||||
|
'-4', 'route', 'del', 'default', 'via',
|
||||||
|
get.return_value, 'dev', 'eth1'
|
||||||
|
),
|
||||||
|
mock.call(
|
||||||
|
'-4', 'route', 'add', 'default', 'via', ip_s,
|
||||||
|
'dev', 'eth1'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
def test_set_default_v4_no_current(self):
|
def test_set_default_v4_no_current(self):
|
||||||
ip_s = '192.168.122.1'
|
ip_s = '192.168.122.1'
|
||||||
|
@ -114,8 +108,11 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
||||||
get.return_value = None
|
get.return_value = None
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
sudo.assert_called_with('add', '-inet', 'default', ip_s)
|
sudo.assert_called_with(
|
||||||
|
'-4', 'route', 'add', 'default', 'via', '192.168.122.1',
|
||||||
|
'dev', 'eth1'
|
||||||
|
)
|
||||||
|
|
||||||
def test_set_default_v6_matches_current(self):
|
def test_set_default_v6_matches_current(self):
|
||||||
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
||||||
|
@ -126,7 +123,7 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
get.return_value = ip_s
|
get.return_value = ip_s
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
sudo.side_effect = AssertionError('should not be called')
|
sudo.side_effect = AssertionError('should not be called')
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
|
|
||||||
def test_set_default_v6_changes_current(self):
|
def test_set_default_v6_changes_current(self):
|
||||||
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
||||||
|
@ -136,19 +133,32 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
||||||
get.return_value = 'fe80::5054:ff:fee2:aaaa'
|
get.return_value = 'fe80::5054:ff:fee2:aaaa'
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
sudo.assert_called_with('change', '-inet6', 'default', ip_s)
|
assert sudo.call_args_list == [
|
||||||
|
mock.call(
|
||||||
|
'-6', 'route', 'del', 'default', 'via',
|
||||||
|
get.return_value, 'dev', 'eth1'
|
||||||
|
),
|
||||||
|
mock.call(
|
||||||
|
'-6', 'route', 'add', 'default', 'via', ip_s,
|
||||||
|
'dev', 'eth1'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
def test_set_default_v6_no_current(self):
|
def test_set_default_v6_no_current(self):
|
||||||
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
ip_s = 'fe80::5054:ff:fee2:1d4f'
|
||||||
ip = mock.MagicMock()
|
ip = mock.MagicMock()
|
||||||
ip.version = 6
|
ip.version = 6
|
||||||
ip.__str__.return_value = ip_s
|
ip.__str__.return_value = ip_s
|
||||||
|
self.mgr.generic_mapping = {'ge1', 'eth1'}
|
||||||
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
with mock.patch.object(self.mgr, '_get_default_gateway') as get:
|
||||||
get.return_value = None
|
get.return_value = None
|
||||||
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
with mock.patch.object(self.mgr, 'sudo') as sudo:
|
||||||
self.mgr._set_default_gateway(ip)
|
self.mgr._set_default_gateway(ip, 'ge1')
|
||||||
sudo.assert_called_with('add', '-inet6', 'default', ip_s)
|
sudo.assert_called_with(
|
||||||
|
'-6', 'route', 'add', 'default', 'via', ip_s,
|
||||||
|
'dev', 'eth1'
|
||||||
|
)
|
||||||
|
|
||||||
def test_update_default_no_inputs(self):
|
def test_update_default_no_inputs(self):
|
||||||
c = models.Configuration({})
|
c = models.Configuration({})
|
||||||
|
@ -156,13 +166,13 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
set.side_effect = AssertionError(
|
set.side_effect = AssertionError(
|
||||||
'should not try to set default gw'
|
'should not try to set default gw'
|
||||||
)
|
)
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
|
|
||||||
def test_update_default_v4_from_gateway(self):
|
def test_update_default_v4_from_gateway(self):
|
||||||
c = models.Configuration({'default_v4_gateway': '172.16.77.1'})
|
c = models.Configuration({'default_v4_gateway': '172.16.77.1'})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
set.assert_called_once_with(c.default_v4_gateway)
|
set.assert_called_once_with(c.default_v4_gateway, None)
|
||||||
|
|
||||||
def test_update_default_v4_from_subnet(self):
|
def test_update_default_v4_from_subnet(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
|
@ -181,10 +191,10 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
snet = net.subnets[0]
|
snet = net.subnets[0]
|
||||||
set.assert_called_once_with(snet.gateway_ip)
|
set.assert_called_once_with(snet.gateway_ip, 'ge0')
|
||||||
|
|
||||||
def test_update_multiple_v4_subnets(self):
|
def test_update_multiple_v4_subnets(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
|
@ -209,10 +219,10 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
snet = net.subnets[0]
|
snet = net.subnets[0]
|
||||||
set.assert_called_once_with(snet.gateway_ip)
|
set.assert_called_once_with(snet.gateway_ip, 'ge0')
|
||||||
|
|
||||||
def test_update_default_v6(self):
|
def test_update_default_v6(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
|
@ -231,10 +241,10 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
snet = net.subnets[0]
|
snet = net.subnets[0]
|
||||||
set.assert_called_once_with(snet.gateway_ip)
|
set.assert_called_once_with(snet.gateway_ip, 'ge0')
|
||||||
|
|
||||||
def test_update_default_multiple_v6(self):
|
def test_update_default_multiple_v6(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
|
@ -259,12 +269,12 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
)
|
)
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
with mock.patch.object(self.mgr, '_set_default_gateway') as set:
|
||||||
self.mgr.update_default(c)
|
self.mgr.update_default_gateway(c)
|
||||||
net = c.networks[0]
|
net = c.networks[0]
|
||||||
snet = net.subnets[0]
|
snet = net.subnets[0]
|
||||||
set.assert_called_once_with(snet.gateway_ip)
|
set.assert_called_once_with(snet.gateway_ip, 'ge0')
|
||||||
|
|
||||||
@mock.patch.object(route.RouteManager, '_set_default_gateway',
|
@mock.patch.object(ip.IPManager, '_set_default_gateway',
|
||||||
lambda *a, **kw: None)
|
lambda *a, **kw: None)
|
||||||
def test_custom_host_routes(self):
|
def test_custom_host_routes(self):
|
||||||
subnet = dict(
|
subnet = dict(
|
||||||
|
@ -290,7 +300,8 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
# ...so let's add one!
|
# ...so let's add one!
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
sudo.assert_called_once_with(
|
sudo.assert_called_once_with(
|
||||||
'add', '-inet', '192.240.128.0/20', '192.168.89.2'
|
'-4', 'route', 'add', '192.240.128.0/20', 'via',
|
||||||
|
'192.168.89.2', 'dev', 'eth0'
|
||||||
)
|
)
|
||||||
|
|
||||||
# db[subnet.cidr] should contain the above route
|
# db[subnet.cidr] should contain the above route
|
||||||
|
@ -311,7 +322,8 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
sudo.assert_called_once_with(
|
sudo.assert_called_once_with(
|
||||||
'delete', '-inet', '192.240.128.0/20', '192.168.89.2'
|
'-4', 'route', 'del', '192.240.128.0/20', 'via',
|
||||||
|
'192.168.89.2', 'dev', 'eth0'
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cache.get('host_routes')), 0)
|
self.assertEqual(len(cache.get('host_routes')), 0)
|
||||||
|
|
||||||
|
@ -327,8 +339,10 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('add', '-inet', '192.240.128.0/20', '192.168.89.2'),
|
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
||||||
mock.call('add', '-inet', '192.220.128.0/20', '192.168.89.3'),
|
'via', '192.168.89.2', 'dev', 'eth0'),
|
||||||
|
mock.call('-4', 'route', 'add', '192.220.128.0/20',
|
||||||
|
'via', '192.168.89.3', 'dev', 'eth0'),
|
||||||
])
|
])
|
||||||
|
|
||||||
# ...let's remove one and add another...
|
# ...let's remove one and add another...
|
||||||
|
@ -343,9 +357,10 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('delete', '-inet', '192.220.128.0/20',
|
mock.call('-4', 'route', 'del', '192.220.128.0/20',
|
||||||
'192.168.89.3'),
|
'via', '192.168.89.3', 'dev', 'eth0'),
|
||||||
mock.call('add', '-inet', '192.185.128.0/20', '192.168.89.4')
|
mock.call('-4', 'route', 'add', '192.185.128.0/20',
|
||||||
|
'via', '192.168.89.4', 'dev', 'eth0')
|
||||||
])
|
])
|
||||||
|
|
||||||
# ...let's add another subnet...
|
# ...let's add another subnet...
|
||||||
|
@ -364,7 +379,8 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('add', '-inet', '192.240.128.0/20', '192.168.90.1')
|
mock.call('-4', 'route', 'add', '192.240.128.0/20',
|
||||||
|
'via', '192.168.90.1', 'dev', 'eth0')
|
||||||
])
|
])
|
||||||
self.assertEqual(len(cache.get('host_routes')), 2)
|
self.assertEqual(len(cache.get('host_routes')), 2)
|
||||||
|
|
||||||
|
@ -375,12 +391,12 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
c = models.Configuration({'networks': [network]})
|
c = models.Configuration({'networks': [network]})
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
self.assertEqual(sudo.call_args_list, [
|
self.assertEqual(sudo.call_args_list, [
|
||||||
mock.call('delete', '-inet', '192.185.128.0/20',
|
mock.call('-4', 'route', 'del', '192.185.128.0/20',
|
||||||
'192.168.89.4'),
|
'via', '192.168.89.4', 'dev', 'eth0'),
|
||||||
mock.call('delete', '-inet', '192.240.128.0/20',
|
mock.call('-4', 'route', 'del', '192.240.128.0/20',
|
||||||
'192.168.89.2'),
|
'via', '192.168.89.2', 'dev', 'eth0'),
|
||||||
mock.call('delete', '-inet', '192.240.128.0/20',
|
mock.call('-4', 'route', 'del', '192.240.128.0/20',
|
||||||
'192.168.90.1'),
|
'via', '192.168.90.1', 'dev', 'eth0'),
|
||||||
])
|
])
|
||||||
self.assertEqual(len(cache.get('host_routes')), 0)
|
self.assertEqual(len(cache.get('host_routes')), 0)
|
||||||
|
|
||||||
|
@ -409,6 +425,7 @@ sockaddrs: <DST,GATEWAY,NETMASK,IFP,IFA,LABEL>
|
||||||
|
|
||||||
self.mgr.update_host_routes(c, cache)
|
self.mgr.update_host_routes(c, cache)
|
||||||
sudo.assert_called_once_with(
|
sudo.assert_called_once_with(
|
||||||
'add', '-inet', '192.240.128.0/20', '192.168.89.2'
|
'-4', 'route', 'add', '192.240.128.0/20', 'via',
|
||||||
|
'192.168.89.2', 'dev', 'eth0'
|
||||||
)
|
)
|
||||||
self.assertEqual(len(cache.get('host_routes')), 0)
|
self.assertEqual(len(cache.get('host_routes')), 0)
|
||||||
|
|
Loading…
Reference in New Issue