if enable_ra_on_gw:
self.driver.configure_ipv6_ra(self.ns_name, interface_name)
+ def _add_extra_subnet_onlink_routes(self, ex_gw_port, interface_name):
+ extra_subnets = ex_gw_port.get('extra_subnets', [])
+ instance = self._get_keepalived_instance()
+ onlink_route_cidrs = set(s['cidr'] for s in extra_subnets)
+ instance.virtual_routes.extra_subnets = [
+ keepalived.KeepalivedVirtualRoute(
+ onlink_route_cidr, None, interface_name, scope='link') for
+ onlink_route_cidr in onlink_route_cidrs]
+
def _should_delete_ipv6_lladdr(self, ipv6_lladdr):
"""Only the master should have any IP addresses configured.
Let keepalived manage IPv6 link local addresses, the same way we let
for ip_cidr in common_utils.fixed_ip_cidrs(ex_gw_port['fixed_ips']):
self._add_vip(ip_cidr, interface_name)
self._add_default_gw_virtual_route(ex_gw_port, interface_name)
+ self._add_extra_subnet_onlink_routes(ex_gw_port, interface_name)
def add_floating_ip(self, fip, interface_name, device):
fip_ip = fip['floating_ip_address']
return True
-def get_routing_table(namespace=None):
+def get_routing_table(ip_version, namespace=None):
"""Return a list of dictionaries, each representing a route.
+ @param ip_version: the routes of version to return, for example 4
+ @param namespace
+ @return: a list of dictionaries, each representing a route.
The dictionary format is: {'destination': cidr,
'nexthop': ip,
- 'device': device_name}
+ 'device': device_name,
+ 'scope': scope}
"""
ip_wrapper = IPWrapper(namespace=namespace)
- table = ip_wrapper.netns.execute(['ip', 'route'], check_exit_code=True)
+ table = ip_wrapper.netns.execute(
+ ['ip', '-%s' % ip_version, 'route'],
+ check_exit_code=True)
routes = []
# Example for route_lines:
data = dict(route[i:i + 2] for i in range(1, len(route), 2))
routes.append({'destination': network,
'nexthop': data.get('via'),
- 'device': data.get('dev')})
+ 'device': data.get('dev'),
+ 'scope': data.get('scope')})
return routes
class KeepalivedVirtualRoute(object):
"""A virtual route entry of a keepalived configuration."""
- def __init__(self, destination, nexthop, interface_name=None):
+ def __init__(self, destination, nexthop, interface_name=None,
+ scope=None):
self.destination = destination
self.nexthop = nexthop
self.interface_name = interface_name
+ self.scope = scope
def build_config(self):
- output = '%s via %s' % (self.destination, self.nexthop)
+ output = self.destination
+ if self.nexthop:
+ output += ' via %s' % self.nexthop
if self.interface_name:
output += ' dev %s' % self.interface_name
+ if self.scope:
+ output += ' scope %s' % self.scope
return output
def __init__(self):
self.gateway_routes = []
self.extra_routes = []
+ self.extra_subnets = []
def remove_routes_on_interface(self, interface_name):
self.gateway_routes = [gw_rt for gw_rt in self.gateway_routes
# NOTE(amuller): extra_routes are initialized from the router's
# 'routes' attribute. These routes do not have an interface
# parameter and so cannot be removed via an interface_name lookup.
+ self.extra_subnets = [route for route in self.extra_subnets if
+ route.interface_name != interface_name]
@property
def routes(self):
- return self.gateway_routes + self.extra_routes
+ return self.gateway_routes + self.extra_routes + self.extra_subnets
def __len__(self):
return len(self.routes)
fixed_ips = []
subnets = []
gateway_mac = kwargs.get('gateway_mac', 'ca:fe:de:ad:be:ee')
+ extra_subnets = []
for loop_version in (4, 6):
if loop_version == 4 and (ip_version == 4 or dual_stack):
ip_address = kwargs.get('ip_address', '19.4.4.4')
prefixlen = 24
subnet_cidr = kwargs.get('subnet_cidr', '19.4.4.0/24')
gateway_ip = kwargs.get('gateway_ip', '19.4.4.1')
+ _extra_subnet = {'cidr': '9.4.5.0/24'}
elif (loop_version == 6 and (ip_version == 6 or dual_stack) and
v6_ext_gw_with_sub):
ip_address = kwargs.get('ip_address', 'fd00::4')
prefixlen = 64
subnet_cidr = kwargs.get('subnet_cidr', 'fd00::/64')
gateway_ip = kwargs.get('gateway_ip', 'fd00::1')
+ _extra_subnet = {'cidr': 'fd01::/64'}
else:
continue
subnet_id = _uuid()
subnets.append({'id': subnet_id,
'cidr': subnet_cidr,
'gateway_ip': gateway_ip})
+ extra_subnets.append(_extra_subnet)
if not fixed_ips and v6_ext_gw_with_sub:
raise ValueError("Invalid ip_version: %s" % ip_version)
'mac_address': gateway_mac,
'network_id': _uuid(),
'fixed_ips': fixed_ips,
- 'subnets': subnets}
+ 'subnets': subnets,
+ 'extra_subnets': extra_subnets}
routes = []
if extra_routes:
expected_routes = [{'nexthop': device_ip,
'device': attr.name,
- 'destination': destination},
+ 'destination': destination,
+ 'scope': None},
{'nexthop': None,
'device': attr.name,
'destination': str(
- netaddr.IPNetwork(attr.ip_cidrs[0]).cidr)}]
+ netaddr.IPNetwork(attr.ip_cidrs[0]).cidr),
+ 'scope': 'link'}]
- routes = ip_lib.get_routing_table(namespace=attr.namespace)
+ routes = ip_lib.get_routing_table(4, namespace=attr.namespace)
self.assertEqual(expected_routes, routes)
floating_ip_cidr = common_utils.ip_to_cidr(
router.get_floating_ips()[0]['floating_ip_address'])
default_gateway_ip = external_port['subnets'][0].get('gateway_ip')
-
+ extra_subnet_cidr = external_port['extra_subnets'][0].get('cidr')
return """vrrp_instance VR_1 {
state BACKUP
interface %(ha_device_name)s
virtual_routes {
0.0.0.0/0 via %(default_gateway_ip)s dev %(external_device_name)s
8.8.8.0/24 via 19.4.4.4
+ %(extra_subnet_cidr)s dev %(external_device_name)s scope link
}
}""" % {
'ha_device_name': ha_device_name,
'floating_ip_cidr': floating_ip_cidr,
'default_gateway_ip': default_gateway_ip,
'int_port_ipv6': int_port_ipv6,
- 'ex_port_ipv6': ex_port_ipv6
+ 'ex_port_ipv6': ex_port_ipv6,
+ 'extra_subnet_cidr': extra_subnet_cidr,
}
def _get_rule(self, iptables_manager, table, chain, predicate):
device, router.get_internal_device_name, router.ns_name))
def _assert_extra_routes(self, router):
- routes = ip_lib.get_routing_table(namespace=router.ns_name)
+ routes = ip_lib.get_routing_table(4, namespace=router.ns_name)
routes = [{'nexthop': route['nexthop'],
'destination': route['destination']} for route in routes]
for extra_route in router.router['routes']:
self.assertIn(extra_route, routes)
+ def _assert_onlink_subnet_routes(self, router, ip_versions):
+ routes = []
+ for ip_version in ip_versions:
+ _routes = ip_lib.get_routing_table(ip_version,
+ namespace=router.ns_name)
+ routes.extend(_routes)
+ routes = set(route['destination'] for route in routes)
+ extra_subnets = router.get_ex_gw_port()['extra_subnets']
+ for extra_subnet in (route['cidr'] for route in extra_subnets):
+ self.assertIn(extra_subnet, routes)
+
def _assert_interfaces_deleted_from_ovs(self):
def assert_ovs_bridge_empty(bridge_name):
bridge = ovs_lib.OVSBridge(bridge_name)
self._assert_snat_chains(router)
self._assert_floating_ip_chains(router)
self._assert_extra_routes(router)
+ ip_versions = [4, 6] if (ip_version == 6 or dual_stack) else [4]
+ self._assert_onlink_subnet_routes(router, ip_versions)
self._assert_metadata_chains(router)
# Verify router gateway interface is configured to receive Router Advts
keepalived.KeepalivedVirtualRoute('10.0.0.0/8', '1.0.0.1'),
keepalived.KeepalivedVirtualRoute('20.0.0.0/8', '2.0.0.2')]
routes.extra_routes = extra_routes
+ extra_subnets = [
+ keepalived.KeepalivedVirtualRoute(
+ '30.0.0.0/8', None, 'eth0', scope='link')]
+ routes.extra_subnets = extra_subnets
return routes
def test_routes(self):
routes = self._get_instance_routes()
- self.assertEqual(len(routes.routes), 4)
+ self.assertEqual(len(routes.routes), 5)
def test_remove_routes_on_interface(self):
routes = self._get_instance_routes()
::/0 via fe80::3e97:eff:fe26:3bfa/64 dev eth1
10.0.0.0/8 via 1.0.0.1
20.0.0.0/8 via 2.0.0.2
+ 30.0.0.0/8 dev eth0 scope link
}"""
routes = self._get_instance_routes()
self.assertEqual(expected, '\n'.join(routes.build_config()))