ip_devs = ip_wrapper.get_devices(exclude_loopback=True)
return [ip_dev.name for ip_dev in ip_devs]
+ @staticmethod
+ def _get_updated_ports(existing_ports, current_ports):
+ updated_ports = dict()
+ current_ports_dict = {p['id']: p for p in current_ports}
+ for existing_port in existing_ports:
+ current_port = current_ports_dict.get(existing_port['id'])
+ if current_port:
+ if sorted(existing_port['fixed_ips']) != (
+ sorted(current_port['fixed_ips'])):
+ updated_ports[current_port['id']] = current_port
+ return updated_ports
+
+ @staticmethod
+ def _port_has_ipv6_subnet(port):
+ if 'subnets' in port:
+ for subnet in port['subnets']:
+ if netaddr.IPNetwork(subnet['cidr']).version == 6:
+ return True
+
def _process_internal_ports(self):
existing_port_ids = set(p['id'] for p in self.internal_ports)
new_ports = [p for p in internal_ports if p['id'] in new_port_ids]
old_ports = [p for p in self.internal_ports
if p['id'] not in current_port_ids]
+ updated_ports = self._get_updated_ports(self.internal_ports,
+ internal_ports)
- new_ipv6_port = False
- old_ipv6_port = False
+ enable_ra = False
for p in new_ports:
self.internal_network_added(p)
self.internal_ports.append(p)
- if not new_ipv6_port:
- for subnet in p['subnets']:
- if netaddr.IPNetwork(subnet['cidr']).version == 6:
- new_ipv6_port = True
- break
+ enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
for p in old_ports:
self.internal_network_removed(p)
self.internal_ports.remove(p)
- if not old_ipv6_port:
- for subnet in p['subnets']:
- if netaddr.IPNetwork(subnet['cidr']).version == 6:
- old_ipv6_port = True
- break
+ enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
+
+ if updated_ports:
+ for index, p in enumerate(internal_ports):
+ if not updated_ports.get(p['id']):
+ continue
+ self.internal_ports[index] = updated_ports[p['id']]
+ interface_name = self.get_internal_device_name(p['id'])
+ ip_cidrs = common_utils.fixed_ip_cidrs(p['fixed_ips'])
+ self.driver.init_l3(interface_name, ip_cidrs=ip_cidrs,
+ namespace=self.ns_name)
+ enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
# Enable RA
- if new_ipv6_port or old_ipv6_port:
+ if enable_ra:
self.radvd.enable(internal_ports)
existing_devices = self._get_existing_devices()
MinRtrAdvInterval 3;
MaxRtrAdvInterval 10;
- {% if ra_mode == constants.DHCPV6_STATELESS %}
+ {% if constants.DHCPV6_STATELESS in ra_modes %}
AdvOtherConfigFlag on;
{% endif %}
- {% if ra_mode == constants.DHCPV6_STATEFUL %}
+ {% if constants.DHCPV6_STATEFUL in ra_modes %}
AdvManagedFlag on;
{% endif %}
- {% if ra_mode in (constants.IPV6_SLAAC, constants.DHCPV6_STATELESS) %}
+ {% for prefix in prefixes %}
prefix {{ prefix }}
{
AdvOnLink on;
AdvAutonomous on;
};
- {% endif %}
+ {% endfor %}
};
""")
buf = six.StringIO()
for p in router_ports:
subnets = p.get('subnets', [])
- for subnet in subnets:
- prefix = subnet['cidr']
- if netaddr.IPNetwork(prefix).version == 6:
- interface_name = self._dev_name_helper(p['id'])
- ra_mode = subnet['ipv6_ra_mode']
- buf.write('%s' % CONFIG_TEMPLATE.render(
- ra_mode=ra_mode,
- interface_name=interface_name,
- prefix=prefix,
- constants=constants))
+ v6_subnets = [subnet for subnet in subnets if
+ netaddr.IPNetwork(subnet['cidr']).version == 6]
+ if not v6_subnets:
+ continue
+ ra_modes = {subnet['ipv6_ra_mode'] for subnet in v6_subnets}
+ auto_config_prefixes = [subnet['cidr'] for subnet in v6_subnets if
+ subnet['ipv6_ra_mode'] == constants.IPV6_SLAAC or
+ subnet['ipv6_ra_mode'] == constants.DHCPV6_STATELESS]
+ interface_name = self._dev_name_helper(p['id'])
+ buf.write('%s' % CONFIG_TEMPLATE.render(
+ ra_modes=list(ra_modes),
+ interface_name=interface_name,
+ prefixes=auto_config_prefixes,
+ constants=constants))
utils.replace_file(radvd_conf, buf.getvalue())
return radvd_conf
from neutron.db import models_v2
from neutron.extensions import external_net
from neutron.extensions import l3
-from neutron.i18n import _LI
+from neutron.i18n import _LI, _LE
from neutron import manager
from neutron.openstack.common import uuidutils
from neutron.plugins.common import constants
raise n_exc.PortInUse(net_id=port['network_id'],
port_id=port['id'],
device_id=port['device_id'])
+
+ # Only allow one router port with IPv6 subnets per network id
+ if self._port_has_ipv6_address(port):
+ for existing_port in (rp.port for rp in router.attached_ports):
+ if (existing_port['network_id'] == port['network_id'] and
+ self._port_has_ipv6_address(existing_port)):
+ msg = _("Cannot have multiple router ports with the "
+ "same network id if both contain IPv6 "
+ "subnets. Existing port %(p)s has IPv6 "
+ "subnet(s) and network id %(nid)s")
+ raise n_exc.BadRequest(resource='router', msg=msg % {
+ 'p': existing_port['id'],
+ 'nid': existing_port['network_id']})
+
fixed_ips = [ip for ip in port['fixed_ips']]
- if len(fixed_ips) != 1:
- msg = _('Router port must have exactly one fixed IP')
+ subnets = []
+ for fixed_ip in fixed_ips:
+ subnet = self._core_plugin._get_subnet(context,
+ fixed_ip['subnet_id'])
+ subnets.append(subnet)
+ self._check_for_dup_router_subnet(context, router,
+ port['network_id'],
+ subnet['id'],
+ subnet['cidr'])
+
+ # Keep the restriction against multiple IPv4 subnets
+ if len([s for s in subnets if s['ip_version'] == 4]) > 1:
+ msg = _LE("Cannot have multiple "
+ "IPv4 subnets on router port")
raise n_exc.BadRequest(resource='router', msg=msg)
- subnet_id = fixed_ips[0]['subnet_id']
- subnet = self._core_plugin._get_subnet(context, subnet_id)
- self._check_for_dup_router_subnet(context, router,
- port['network_id'],
- subnet['id'],
- subnet['cidr'])
+
port.update({'device_id': router.id, 'device_owner': owner})
- return port
+ return port, subnets
+
+ def _port_has_ipv6_address(self, port):
+ for fixed_ip in port['fixed_ips']:
+ if netaddr.IPNetwork(fixed_ip['ip_address']).version == 6:
+ return True
+
+ def _find_ipv6_router_port_by_network(self, router, net_id):
+ for port in router.attached_ports:
+ p = port['port']
+ if p['network_id'] == net_id and self._port_has_ipv6_address(p):
+ return port
def _add_interface_by_subnet(self, context, router, subnet_id, owner):
subnet = self._core_plugin._get_subnet(context, subnet_id)
fixed_ip = {'ip_address': subnet['gateway_ip'],
'subnet_id': subnet['id']}
+ if subnet['ip_version'] == 6:
+ # Add new prefix to an existing ipv6 port with the same network id
+ # if one exists
+ port = self._find_ipv6_router_port_by_network(router,
+ subnet['network_id'])
+ if port:
+ fixed_ips = list(port['port']['fixed_ips'])
+ fixed_ips.append(fixed_ip)
+ return self._core_plugin.update_port(context,
+ port['port_id'], {'port':
+ {'fixed_ips': fixed_ips}}), [subnet], False
+
return self._core_plugin.create_port(context, {
'port':
{'tenant_id': subnet['tenant_id'],
'admin_state_up': True,
'device_id': router.id,
'device_owner': owner,
- 'name': ''}})
+ 'name': ''}}), [subnet], True
@staticmethod
def _make_router_interface_info(
- router_id, tenant_id, port_id, subnet_id):
+ router_id, tenant_id, port_id, subnet_id, subnet_ids):
return {
'id': router_id,
'tenant_id': tenant_id,
'port_id': port_id,
- 'subnet_id': subnet_id
+ 'subnet_id': subnet_id, # deprecated by IPv6 multi-prefix
+ 'subnet_ids': subnet_ids
}
def add_router_interface(self, context, router_id, interface_info):
add_by_port, add_by_sub = self._validate_interface_info(interface_info)
device_owner = self._get_device_owner(context, router_id)
+ # This should be True unless adding an IPv6 prefix to an existing port
+ new_port = True
+
if add_by_port:
- port = self._add_interface_by_port(
- context, router, interface_info['port_id'], device_owner)
+ port, subnets = self._add_interface_by_port(
+ context, router, interface_info['port_id'], device_owner)
# add_by_subnet is not used here, because the validation logic of
# _validate_interface_info ensures that either of add_by_* is True.
else:
- port = self._add_interface_by_subnet(
- context, router, interface_info['subnet_id'], device_owner)
-
- with context.session.begin(subtransactions=True):
- router_port = RouterPort(
- port_id=port['id'],
- router_id=router.id,
- port_type=device_owner
- )
- context.session.add(router_port)
+ port, subnets, new_port = self._add_interface_by_subnet(
+ context, router, interface_info['subnet_id'], device_owner)
+
+ if new_port:
+ with context.session.begin(subtransactions=True):
+ router_port = RouterPort(
+ port_id=port['id'],
+ router_id=router.id,
+ port_type=device_owner
+ )
+ context.session.add(router_port)
return self._make_router_interface_info(
- router.id, port['tenant_id'], port['id'],
- port['fixed_ips'][0]['subnet_id'])
+ router.id, port['tenant_id'], port['id'], subnets[-1]['id'],
+ [subnet['id'] for subnet in subnets])
def _confirm_router_interface_not_in_use(self, context, router_id,
subnet_id):
except exc.NoResultFound:
raise l3.RouterInterfaceNotFound(router_id=router_id,
port_id=port_id)
- port_subnet_id = port_db['fixed_ips'][0]['subnet_id']
- if subnet_id and port_subnet_id != subnet_id:
+ port_subnet_ids = [fixed_ip['subnet_id']
+ for fixed_ip in port_db['fixed_ips']]
+ if subnet_id and subnet_id not in port_subnet_ids:
raise n_exc.SubnetMismatchForPort(
port_id=port_id, subnet_id=subnet_id)
- subnet = self._core_plugin._get_subnet(context, port_subnet_id)
- self._confirm_router_interface_not_in_use(
- context, router_id, port_subnet_id)
+ subnets = [self._core_plugin._get_subnet(context, port_subnet_id)
+ for port_subnet_id in port_subnet_ids]
+ for port_subnet_id in port_subnet_ids:
+ self._confirm_router_interface_not_in_use(
+ context, router_id, port_subnet_id)
self._core_plugin.delete_port(context, port_db['id'],
l3_port_check=False)
- return (port_db, subnet)
+ return (port_db, subnets)
def _remove_interface_by_subnet(self, context,
router_id, subnet_id, owner):
)
for p in ports:
- if p['fixed_ips'][0]['subnet_id'] == subnet_id:
+ port_subnets = [fip['subnet_id'] for fip in p['fixed_ips']]
+ if subnet_id in port_subnets and len(port_subnets) > 1:
+ # multiple prefix port - delete prefix from port
+ fixed_ips = [fip for fip in p['fixed_ips'] if
+ fip['subnet_id'] != subnet_id]
+ self._core_plugin.update_port(context, p['id'],
+ {'port':
+ {'fixed_ips': fixed_ips}})
+ return (p, [subnet])
+ elif subnet_id in port_subnets:
+ # only one subnet on port - delete the port
self._core_plugin.delete_port(context, p['id'],
l3_port_check=False)
- return (p, subnet)
+ return (p, [subnet])
except exc.NoResultFound:
pass
raise l3.RouterInterfaceNotFoundForSubnet(router_id=router_id,
subnet_id = interface_info.get('subnet_id')
device_owner = self._get_device_owner(context, router_id)
if remove_by_port:
- port, subnet = self._remove_interface_by_port(context, router_id,
- port_id, subnet_id,
- device_owner)
+ port, subnets = self._remove_interface_by_port(context, router_id,
+ port_id, subnet_id,
+ device_owner)
# remove_by_subnet is not used here, because the validation logic of
# _validate_interface_info ensures that at least one of remote_by_*
# is True.
else:
- port, subnet = self._remove_interface_by_subnet(
- context, router_id, subnet_id, device_owner)
+ port, subnets = self._remove_interface_by_subnet(
+ context, router_id, subnet_id, device_owner)
return self._make_router_interface_info(router_id, port['tenant_id'],
- port['id'], subnet['id'])
+ port['id'], subnets[0]['id'],
+ [subnet['id'] for subnet in
+ subnets])
def _get_floatingip(self, context, id):
try:
router = self._get_router(context, router_id)
device_owner = self._get_device_owner(context, router)
+ # This should be True unless adding an IPv6 prefix to an existing port
+ new_port = True
+
if add_by_port:
- port = self._add_interface_by_port(
- context, router, interface_info['port_id'], device_owner)
+ port, subnets = self._add_interface_by_port(
+ context, router, interface_info['port_id'], device_owner)
elif add_by_sub:
- port = self._add_interface_by_subnet(
- context, router, interface_info['subnet_id'], device_owner)
-
- with context.session.begin(subtransactions=True):
- router_port = l3_db.RouterPort(
- port_id=port['id'],
- router_id=router.id,
- port_type=device_owner
- )
- context.session.add(router_port)
-
- if router.extra_attributes.distributed and router.gw_port:
- self.add_csnat_router_interface_port(
- context.elevated(), router, port['network_id'],
- port['fixed_ips'][0]['subnet_id'])
+ port, subnets, new_port = self._add_interface_by_subnet(
+ context, router, interface_info['subnet_id'], device_owner)
+
+ if new_port:
+ with context.session.begin(subtransactions=True):
+ router_port = l3_db.RouterPort(
+ port_id=port['id'],
+ router_id=router.id,
+ port_type=device_owner
+ )
+ context.session.add(router_port)
+
+ if router.extra_attributes.distributed and router.gw_port:
+ self.add_csnat_router_interface_port(
+ context.elevated(), router, port['network_id'],
+ port['fixed_ips'][-1]['subnet_id'])
router_interface_info = self._make_router_interface_info(
- router_id, port['tenant_id'], port['id'],
- port['fixed_ips'][0]['subnet_id'])
+ router_id, port['tenant_id'], port['id'], subnets[-1]['id'],
+ [subnet['id'] for subnet in subnets])
self.notify_router_interface_action(
context, router_interface_info, 'add')
return router_interface_info
device_owner = self._get_device_owner(context, router)
if remove_by_port:
- port, subnet = self._remove_interface_by_port(
- context, router_id, port_id, subnet_id, device_owner)
+ port, subnets = self._remove_interface_by_port(
+ context, router_id, port_id, subnet_id, device_owner)
# remove_by_subnet is not used here, because the validation logic of
# _validate_interface_info ensures that at least one of remote_by_*
# is True.
else:
- port, subnet = self._remove_interface_by_subnet(
- context, router_id, subnet_id, device_owner)
+ port, subnets = self._remove_interface_by_subnet(
+ context, router_id, subnet_id, device_owner)
if router.extra_attributes.distributed:
if router.gw_port:
context, l3_agent['id'], router_id)
router_interface_info = self._make_router_interface_info(
- router_id, port['tenant_id'], port['id'],
- port['fixed_ips'][0]['subnet_id'])
+ router_id, port['tenant_id'], port['id'], subnets[0]['id'],
+ [subnet['id'] for subnet in subnets])
self.notify_router_interface_action(
context, router_interface_info, 'remove')
return router_interface_info
"failed to add the interface in the roll back."
" of a remove_router_interface operation"))
+ def _find_router_port_by_subnet_id(self, ports, subnet_id):
+ for p in ports:
+ subnet_ids = [fip['subnet_id'] for fip in p['fixed_ips']]
+ if subnet_id in subnet_ids:
+ return p['id']
+
@_ha
def remove_router_interface(self, context, router_id, interface_info):
LOG.debug("Remove router interface in progress: "
'network_id': [subnet['network_id']]}
ports = self.get_ports(context, filters=df)
if ports:
- pid = ports[0]['id']
+ pid = self._find_router_port_by_subnet_id(ports, subnet_id)
+ if not pid:
+ raise sdnve_exc.SdnveException(
+ msg=(_('Update router-remove-interface '
+ 'failed SDN-VE: subnet %(sid) is not '
+ 'associated with any ports on router '
+ '%(rid)'), {'sid': subnet_id,
+ 'rid': router_id}))
interface_info['port_id'] = pid
msg = ("SdnvePluginV2.remove_router_interface "
"subnet_id: %(sid)s port_id: %(pid)s")
session = context.session
with session.begin(subtransactions=True):
try:
+ if not port_id:
+ # port_id was not originally given in interface_info,
+ # so we want to remove the interface by subnet instead
+ # of port
+ del interface_info['port_id']
info = super(SdnvePluginV2, self).remove_router_interface(
context, router_id, interface_info)
except Exception:
'host': host}
router.router[l3_constants.FLOATINGIP_KEY].append(fip)
+ def _add_internal_interface_by_subnet(self, router, count=1,
+ ip_version=4,
+ ipv6_subnet_modes=None,
+ interface_id=None):
+ return test_l3_agent.router_append_subnet(router, count,
+ ip_version, ipv6_subnet_modes, interface_id)
+
def _namespace_exists(self, namespace):
ip = ip_lib.IPWrapper(namespace=namespace)
return ip.netns.exists(namespace)
v6_ext_gw_with_sub))
router = self.manage_router(self.agent, router_info)
+ # Add multiple-IPv6-prefix internal router port
+ slaac = l3_constants.IPV6_SLAAC
+ slaac_mode = {'ra_mode': slaac, 'address_mode': slaac}
+ subnet_modes = [slaac_mode] * 2
+ self._add_internal_interface_by_subnet(router.router, count=2,
+ ip_version=6, ipv6_subnet_modes=subnet_modes)
+ router.process(self.agent)
+
if enable_ha:
port = router.get_ex_gw_port()
interface_name = router.get_external_device_name(port['id'])
mkintf, notify):
grtr.return_value = router
gdev.return_value = mock.Mock()
- rmintf.return_value = (mock.MagicMock(), mock.Mock())
+ rmintf.return_value = (mock.MagicMock(), mock.MagicMock())
mkintf.return_value = mock.Mock()
gplugin.return_value = {plugin_const.L3_ROUTER_NAT: plugin}
delintf.return_value = None
import copy
import eventlet
+from itertools import chain as iter_chain
+from itertools import combinations as iter_combinations
import mock
import netaddr
from oslo_log import log
mac_address.value += 1
+def router_append_subnet(router, count=1, ip_version=4,
+ ipv6_subnet_modes=None, interface_id=None):
+ if ip_version == 6:
+ subnet_mode_none = {'ra_mode': None, 'address_mode': None}
+ if not ipv6_subnet_modes:
+ ipv6_subnet_modes = [subnet_mode_none] * count
+ elif len(ipv6_subnet_modes) != count:
+ ipv6_subnet_modes.extend([subnet_mode_none for i in
+ xrange(len(ipv6_subnet_modes), count)])
+
+ if ip_version == 4:
+ ip_pool = '35.4.%i.4'
+ cidr_pool = '35.4.%i.0/24'
+ prefixlen = 24
+ gw_pool = '35.4.%i.1'
+ elif ip_version == 6:
+ ip_pool = 'fd01:%x::6'
+ cidr_pool = 'fd01:%x::/64'
+ prefixlen = 64
+ gw_pool = 'fd01:%x::1'
+ else:
+ raise ValueError("Invalid ip_version: %s" % ip_version)
+
+ interfaces = copy.deepcopy(router.get(l3_constants.INTERFACE_KEY, []))
+ if interface_id:
+ try:
+ interface = (i for i in interfaces
+ if i['id'] == interface_id).next()
+ except StopIteration:
+ raise ValueError("interface_id not found")
+
+ fixed_ips, subnets = interface['fixed_ips'], interface['subnets']
+ else:
+ interface = None
+ fixed_ips, subnets = [], []
+
+ num_existing_subnets = len(subnets)
+ for i in xrange(count):
+ subnet_id = _uuid()
+ fixed_ips.append(
+ {'ip_address': ip_pool % (i + num_existing_subnets),
+ 'subnet_id': subnet_id,
+ 'prefixlen': prefixlen})
+ subnets.append(
+ {'id': subnet_id,
+ 'cidr': cidr_pool % (i + num_existing_subnets),
+ 'gateway_ip': gw_pool % (i + num_existing_subnets),
+ 'ipv6_ra_mode': ipv6_subnet_modes[i]['ra_mode'],
+ 'ipv6_address_mode': ipv6_subnet_modes[i]['address_mode']})
+
+ if interface:
+ # Update old interface
+ index = interfaces.index(interface)
+ interfaces[index].update({'fixed_ips': fixed_ips, 'subnets': subnets})
+ else:
+ # New interface appended to interfaces list
+ mac_address = netaddr.EUI('ca:fe:de:ad:be:ef')
+ mac_address.dialect = netaddr.mac_unix
+ interfaces.append(
+ {'id': _uuid(),
+ 'network_id': _uuid(),
+ 'admin_state_up': True,
+ 'mac_address': str(mac_address),
+ 'fixed_ips': fixed_ips,
+ 'subnets': subnets})
+
+ router[l3_constants.INTERFACE_KEY] = interfaces
+
+
def prepare_router_data(ip_version=4, enable_snat=None, num_internal_ports=1,
enable_floating_ip=False, enable_ha=False,
extra_routes=False, dual_stack=False,
namespace=ri.ns_name,
conf=mock.ANY)]
+ def _process_router_ipv6_subnet_added(
+ self, router, ipv6_subnet_modes=None):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
+ agent.external_gateway_added = mock.Mock()
+ self._process_router_instance_for_agent(agent, ri, router)
+ # Add an IPv6 interface with len(ipv6_subnet_modes) subnets
+ # and reprocess
+ router_append_subnet(router, count=len(ipv6_subnet_modes),
+ ip_version=6, ipv6_subnet_modes=ipv6_subnet_modes)
+ # Reassign the router object to RouterInfo
+ self._process_router_instance_for_agent(agent, ri, router)
+ return ri
+
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(
self.assertIn('prefix',
self.utils_replace_file.call_args[0][1].split())
+ def test_process_router_ipv6_subnets_added(self):
+ router = prepare_router_data()
+ ri = self._process_router_ipv6_subnet_added(router, ipv6_subnet_modes=[
+ {'ra_mode': l3_constants.IPV6_SLAAC,
+ 'address_mode': l3_constants.IPV6_SLAAC},
+ {'ra_mode': l3_constants.DHCPV6_STATELESS,
+ 'address_mode': l3_constants.DHCPV6_STATELESS},
+ {'ra_mode': l3_constants.DHCPV6_STATEFUL,
+ 'address_mode': l3_constants.DHCPV6_STATEFUL}])
+ self._assert_ri_process_enabled(ri, 'radvd')
+ radvd_config = self.utils_replace_file.call_args[0][1].split()
+ # Assert we have a prefix from IPV6_SLAAC and a prefix from
+ # DHCPV6_STATELESS on one interface
+ self.assertEqual(2, radvd_config.count("prefix"))
+ self.assertEqual(1, radvd_config.count("interface"))
+
+ def test_process_router_ipv6_subnets_added_to_existing_port(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = prepare_router_data()
+ ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
+ agent.external_gateway_added = mock.Mock()
+ self._process_router_instance_for_agent(agent, ri, router)
+ # Add the first subnet on a new interface
+ router_append_subnet(router, count=1, ip_version=6, ipv6_subnet_modes=[
+ {'ra_mode': l3_constants.IPV6_SLAAC,
+ 'address_mode': l3_constants.IPV6_SLAAC}])
+ self._process_router_instance_for_agent(agent, ri, router)
+ self._assert_ri_process_enabled(ri, 'radvd')
+ radvd_config = self.utils_replace_file.call_args[0][1].split()
+ self.assertEqual(1, len(ri.internal_ports[1]['subnets']))
+ self.assertEqual(1, len(ri.internal_ports[1]['fixed_ips']))
+ self.assertEqual(1, radvd_config.count("prefix"))
+ self.assertEqual(1, radvd_config.count("interface"))
+ # Reset mocks to verify radvd enabled and configured correctly
+ # after second subnet added to interface
+ self.external_process.reset_mock()
+ self.utils_replace_file.reset_mock()
+ # Add the second subnet on the same interface
+ interface_id = router[l3_constants.INTERFACE_KEY][1]['id']
+ router_append_subnet(router, count=1, ip_version=6, ipv6_subnet_modes=[
+ {'ra_mode': l3_constants.IPV6_SLAAC,
+ 'address_mode': l3_constants.IPV6_SLAAC}],
+ interface_id=interface_id)
+ self._process_router_instance_for_agent(agent, ri, router)
+ # radvd should have been enabled again and the interface
+ # should have two prefixes
+ self._assert_ri_process_enabled(ri, 'radvd')
+ radvd_config = self.utils_replace_file.call_args[0][1].split()
+ self.assertEqual(2, len(ri.internal_ports[1]['subnets']))
+ self.assertEqual(2, len(ri.internal_ports[1]['fixed_ips']))
+ self.assertEqual(2, radvd_config.count("prefix"))
+ self.assertEqual(1, radvd_config.count("interface"))
+
def test_process_router_ipv6v4_interface_added(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
self._process_router_instance_for_agent(agent, ri, router)
self._assert_ri_process_disabled(ri, 'radvd')
+ def test_process_router_ipv6_subnet_removed(self):
+ agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
+ router = prepare_router_data()
+ ri = l3router.RouterInfo(router['id'], router, **self.ri_kwargs)
+ agent.external_gateway_added = mock.Mock()
+ self._process_router_instance_for_agent(agent, ri, router)
+ # Add an IPv6 interface with two subnets and reprocess
+ router_append_subnet(router, count=2, ip_version=6,
+ ipv6_subnet_modes=([
+ {'ra_mode': l3_constants.IPV6_SLAAC,
+ 'address_mode': l3_constants.IPV6_SLAAC}
+ ] * 2))
+ self._process_router_instance_for_agent(agent, ri, router)
+ self._assert_ri_process_enabled(ri, 'radvd')
+ # Reset mocks to check for modified radvd config
+ self.utils_replace_file.reset_mock()
+ self.external_process.reset_mock()
+ # Remove one subnet from the interface and reprocess
+ interfaces = copy.deepcopy(router[l3_constants.INTERFACE_KEY])
+ del interfaces[1]['subnets'][0]
+ del interfaces[1]['fixed_ips'][0]
+ router[l3_constants.INTERFACE_KEY] = interfaces
+ self._process_router_instance_for_agent(agent, ri, router)
+ # Assert radvd was enabled again and that we only have one
+ # prefix on the interface
+ self._assert_ri_process_enabled(ri, 'radvd')
+ radvd_config = self.utils_replace_file.call_args[0][1].split()
+ self.assertEqual(1, len(ri.internal_ports[1]['subnets']))
+ self.assertEqual(1, len(ri.internal_ports[1]['fixed_ips']))
+ self.assertEqual(1, radvd_config.count("interface"))
+ self.assertEqual(1, radvd_config.count("prefix"))
+
def test_process_router_internal_network_added_unexpected_error(self):
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
router = prepare_router_data()
self.assertIn(_join('-m', 'syslog'), cmd)
def test_generate_radvd_conf_other_and_managed_flag(self):
- _skip_check = object()
- skip = lambda flag: True if flag is _skip_check else False
-
+ # expected = {ra_mode: (AdvOtherConfigFlag, AdvManagedFlag), ...}
expected = {l3_constants.IPV6_SLAAC: (False, False),
l3_constants.DHCPV6_STATELESS: (True, False),
- # we don't check other flag for stateful since it's redundant
- # for this mode and can be ignored by clients, as per RFC4861
- l3_constants.DHCPV6_STATEFUL: (_skip_check, True)}
+ l3_constants.DHCPV6_STATEFUL: (False, True)}
+
+ modes = [l3_constants.IPV6_SLAAC, l3_constants.DHCPV6_STATELESS,
+ l3_constants.DHCPV6_STATEFUL]
+ mode_combos = list(iter_chain(*[[list(combo) for combo in
+ iter_combinations(modes, i)] for i in range(1, len(modes) + 1)]))
- for ra_mode, flags_set in expected.iteritems():
+ for mode_list in mode_combos:
+ ipv6_subnet_modes = [{'ra_mode': mode, 'address_mode': mode}
+ for mode in mode_list]
router = prepare_router_data()
- ri = self._process_router_ipv6_interface_added(router,
- ra_mode=ra_mode)
+ ri = self._process_router_ipv6_subnet_added(router,
+ ipv6_subnet_modes)
ri.radvd._generate_radvd_conf(router[l3_constants.INTERFACE_KEY])
def assertFlag(flag):
return (self.assertIn if flag else self.assertNotIn)
- other_flag, managed_flag = flags_set
- if not skip(other_flag):
- assertFlag(other_flag)('AdvOtherConfigFlag on;',
- self.utils_replace_file.call_args[0][1])
+ other_flag, managed_flag = (
+ any(expected[mode][0] for mode in mode_list),
+ any(expected[mode][1] for mode in mode_list))
- if not skip(managed_flag):
- assertFlag(managed_flag)('AdvManagedFlag on',
- self.utils_replace_file.call_args[0][1])
+ assertFlag(other_flag)('AdvOtherConfigFlag on;',
+ self.utils_replace_file.call_args[0][1])
+ assertFlag(managed_flag)('AdvManagedFlag on;',
+ self.utils_replace_file.call_args[0][1])
ipv6_address_mode=uc['address_mode']) as s:
self._test_router_add_interface_subnet(r, s, uc['msg'])
+ def test_router_add_interface_multiple_ipv4_subnets(self):
+ """Test router-interface-add for multiple ipv4 subnets.
+
+ Verify that adding multiple ipv4 subnets from the same network
+ to a router places them all on different router interfaces.
+ """
+ with self.router() as r, self.network() as n:
+ with self.subnet(network=n, cidr='10.0.0.0/24') as s1, (
+ self.subnet(network=n, cidr='10.0.1.0/24')) as s2:
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s1['subnet']['id'],
+ None)
+ pid1 = body['port_id']
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s2['subnet']['id'],
+ None)
+ pid2 = body['port_id']
+ self.assertNotEqual(pid1, pid2)
+ self._router_interface_action('remove', r['router']['id'],
+ s1['subnet']['id'], None)
+ self._router_interface_action('remove', r['router']['id'],
+ s2['subnet']['id'], None)
+
+ def test_router_add_interface_multiple_ipv6_subnets_same_net(self):
+ """Test router-interface-add for multiple ipv6 subnets on a network.
+
+ Verify that adding multiple ipv6 subnets from the same network
+ to a router places them all on the same router interface.
+ """
+ with self.router() as r, self.network() as n:
+ with (self.subnet(network=n, cidr='fd00::1/64', ip_version=6)
+ ) as s1, self.subnet(network=n, cidr='fd01::1/64',
+ ip_version=6) as s2:
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s1['subnet']['id'],
+ None)
+ pid1 = body['port_id']
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s2['subnet']['id'],
+ None)
+ pid2 = body['port_id']
+ self.assertEqual(pid1, pid2)
+ port = self._show('ports', pid1)
+ self.assertEqual(2, len(port['port']['fixed_ips']))
+ port_subnet_ids = [fip['subnet_id'] for fip in
+ port['port']['fixed_ips']]
+ self.assertIn(s1['subnet']['id'], port_subnet_ids)
+ self.assertIn(s2['subnet']['id'], port_subnet_ids)
+ self._router_interface_action('remove', r['router']['id'],
+ s1['subnet']['id'], None)
+ self._router_interface_action('remove', r['router']['id'],
+ s2['subnet']['id'], None)
+
+ def test_router_add_interface_multiple_ipv6_subnets_different_net(self):
+ """Test router-interface-add for ipv6 subnets on different networks.
+
+ Verify that adding multiple ipv6 subnets from different networks
+ to a router places them on different router interfaces.
+ """
+ with self.router() as r, self.network() as n1, self.network() as n2:
+ with (self.subnet(network=n1, cidr='fd00::1/64', ip_version=6)
+ ) as s1, self.subnet(network=n2, cidr='fd01::1/64',
+ ip_version=6) as s2:
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s1['subnet']['id'],
+ None)
+ pid1 = body['port_id']
+ body = self._router_interface_action('add',
+ r['router']['id'],
+ s2['subnet']['id'],
+ None)
+ pid2 = body['port_id']
+ self.assertNotEqual(pid1, pid2)
+ self._router_interface_action('remove', r['router']['id'],
+ s1['subnet']['id'], None)
+ self._router_interface_action('remove', r['router']['id'],
+ s2['subnet']['id'], None)
+
def test_router_add_iface_ipv6_ext_ra_subnet_returns_400(self):
"""Test router-interface-add for in-valid ipv6 subnets.
body = self._show('ports', p['port']['id'])
self.assertEqual(body['port']['device_id'], r['router']['id'])
+ # clean-up
+ self._router_interface_action('remove',
+ r['router']['id'],
+ None,
+ p['port']['id'])
+
+ def test_router_add_interface_multiple_ipv4_subnet_port_returns_400(self):
+ """Test adding router port with multiple IPv4 subnets fails.
+
+ Multiple IPv4 subnets are not allowed on a single router port.
+ Ensure that adding a port with multiple IPv4 subnets to a router fails.
+ """
+ with self.network() as n, self.router() as r:
+ with self.subnet(network=n, cidr='10.0.0.0/24') as s1, (
+ self.subnet(network=n, cidr='10.0.1.0/24')) as s2:
+ fixed_ips = [{'subnet_id': s1['subnet']['id']},
+ {'subnet_id': s2['subnet']['id']}]
+ with self.port(subnet=s1, fixed_ips=fixed_ips) as p:
+ exp_code = exc.HTTPBadRequest.code
+ self._router_interface_action('add',
+ r['router']['id'],
+ None,
+ p['port']['id'],
+ expected_code=exp_code)
+
+ def test_router_add_interface_ipv6_port_existing_network_returns_400(self):
+ """Ensure unique IPv6 router ports per network id.
+
+ Adding a router port containing one or more IPv6 subnets with the same
+ network id as an existing router port should fail. This is so
+ there is no ambiguity regarding on which port to add an IPv6 subnet
+ when executing router-interface-add with a subnet and no port.
+ """
+ with self.network() as n, self.router() as r:
+ with self.subnet(network=n, cidr='fd00::/64',
+ ip_version=6) as s1, (
+ self.subnet(network=n, cidr='fd01::/64',
+ ip_version=6)) as s2:
+ with self.port(subnet=s1) as p:
+ self._router_interface_action('add',
+ r['router']['id'],
+ s2['subnet']['id'],
+ None)
+ exp_code = exc.HTTPBadRequest.code
+ self._router_interface_action('add',
+ r['router']['id'],
+ None,
+ p['port']['id'],
+ expected_code=exp_code)
+ self._router_interface_action('remove',
+ r['router']['id'],
+ s2['subnet']['id'],
+ None)
+
+ def test_router_add_interface_multiple_ipv6_subnet_port(self):
+ """A port with multiple IPv6 subnets can be added to a router
+
+ Create a port with multiple associated IPv6 subnets and attach
+ it to a router. The action should succeed.
+ """
+ with self.network() as n, self.router() as r:
+ with self.subnet(network=n, cidr='fd00::/64',
+ ip_version=6) as s1, (
+ self.subnet(network=n, cidr='fd01::/64',
+ ip_version=6)) as s2:
+ fixed_ips = [{'subnet_id': s1['subnet']['id']},
+ {'subnet_id': s2['subnet']['id']}]
+ with self.port(subnet=s1, fixed_ips=fixed_ips) as p:
+ self._router_interface_action('add',
+ r['router']['id'],
+ None,
+ p['port']['id'])
+ self._router_interface_action('remove',
+ r['router']['id'],
+ None,
+ p['port']['id'])
+
def test_router_add_interface_empty_port_and_subnet_ids(self):
with self.router() as r:
self._router_interface_action('add', r['router']['id'],
p2['port']['id'],
exc.HTTPNotFound.code)
+ def test_router_remove_ipv6_subnet_from_interface(self):
+ """Delete a subnet from a router interface
+
+ Verify that deleting a subnet with router-interface-delete removes
+ that subnet when there are multiple subnets on the interface and
+ removes the interface when it is the last subnet on the interface.
+ """
+ with self.router() as r, self.network() as n:
+ with (self.subnet(network=n, cidr='fd00::1/64', ip_version=6)
+ ) as s1, self.subnet(network=n, cidr='fd01::1/64',
+ ip_version=6) as s2:
+ body = self._router_interface_action('add', r['router']['id'],
+ s1['subnet']['id'],
+ None)
+ self._router_interface_action('add', r['router']['id'],
+ s2['subnet']['id'], None)
+ port = self._show('ports', body['port_id'])
+ self.assertEqual(2, len(port['port']['fixed_ips']))
+ self._router_interface_action('remove', r['router']['id'],
+ s1['subnet']['id'], None)
+ port = self._show('ports', body['port_id'])
+ self.assertEqual(1, len(port['port']['fixed_ips']))
+ self._router_interface_action('remove', r['router']['id'],
+ s2['subnet']['id'], None)
+ exp_code = exc.HTTPNotFound.code
+ port = self._show('ports', body['port_id'],
+ expected_code=exp_code)
+
def test_router_delete(self):
with self.router() as router:
router_id = router['router']['id']