Support Router Advertisement Daemon (radvd) for IPv6
Launch radvd from inside l3 agent when any router port has an IPv6 address. If slaac is used for IPv6 addresses, advertise the prefix associated with the port; otherwise, advertise default route only. Change-Id: Ib8b0b3e71f7af9afa769c41357c66f88f4326807 Implements: blueprint neutron-ipv6-radvd-ra Co-Authored-By: Henry Gessau <gessau@cisco.com>
This commit is contained in:
parent
2f7a2ae72b
commit
7f8ae630b8
@ -14,6 +14,7 @@ arping: CommandFilter, arping, root
|
|||||||
# l3_agent
|
# l3_agent
|
||||||
sysctl: CommandFilter, sysctl, root
|
sysctl: CommandFilter, sysctl, root
|
||||||
route: CommandFilter, route, root
|
route: CommandFilter, route, root
|
||||||
|
radvd: CommandFilter, radvd, root
|
||||||
|
|
||||||
# metadata proxy
|
# metadata proxy
|
||||||
metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root
|
metadata_proxy: CommandFilter, neutron-ns-metadata-proxy, root
|
||||||
@ -26,6 +27,8 @@ metadata_proxy_local_quantum: CommandFilter, /usr/local/bin/quantum-ns-metadata-
|
|||||||
kill_metadata: KillFilter, root, /usr/bin/python, -9
|
kill_metadata: KillFilter, root, /usr/bin/python, -9
|
||||||
kill_metadata7: KillFilter, root, /usr/bin/python2.7, -9
|
kill_metadata7: KillFilter, root, /usr/bin/python2.7, -9
|
||||||
kill_metadata6: KillFilter, root, /usr/bin/python2.6, -9
|
kill_metadata6: KillFilter, root, /usr/bin/python2.6, -9
|
||||||
|
kill_radvd_usr: KillFilter, root, /usr/sbin/radvd, -9, -HUP
|
||||||
|
kill_radvd: KillFilter, root, /sbin/radvd, -9, -HUP
|
||||||
|
|
||||||
# ip_lib
|
# ip_lib
|
||||||
ip: IpFilter, ip, root
|
ip: IpFilter, ip, root
|
||||||
|
@ -29,6 +29,7 @@ from neutron.agent.linux import interface
|
|||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import iptables_manager
|
from neutron.agent.linux import iptables_manager
|
||||||
from neutron.agent.linux import ovs_lib # noqa
|
from neutron.agent.linux import ovs_lib # noqa
|
||||||
|
from neutron.agent.linux import ra
|
||||||
from neutron.agent import rpc as agent_rpc
|
from neutron.agent import rpc as agent_rpc
|
||||||
from neutron.common import config as common_config
|
from neutron.common import config as common_config
|
||||||
from neutron.common import constants as l3_constants
|
from neutron.common import constants as l3_constants
|
||||||
@ -427,6 +428,7 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
|||||||
if self.conf.enable_metadata_proxy:
|
if self.conf.enable_metadata_proxy:
|
||||||
self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
|
self._destroy_metadata_proxy(ns[len(NS_PREFIX):], ns)
|
||||||
|
|
||||||
|
ra.disable_ipv6_ra(ns[len(NS_PREFIX):], ns, self.root_helper)
|
||||||
try:
|
try:
|
||||||
self._destroy_router_namespace(ns)
|
self._destroy_router_namespace(ns)
|
||||||
except RuntimeError:
|
except RuntimeError:
|
||||||
@ -579,15 +581,31 @@ class L3NATAgent(firewall_l3_agent.FWaaSL3AgentRpcCallback, manager.Manager):
|
|||||||
p['id'] not in existing_port_ids]
|
p['id'] not in existing_port_ids]
|
||||||
old_ports = [p for p in ri.internal_ports if
|
old_ports = [p for p in ri.internal_ports if
|
||||||
p['id'] not in current_port_ids]
|
p['id'] not in current_port_ids]
|
||||||
|
|
||||||
|
new_ipv6_port = False
|
||||||
|
old_ipv6_port = False
|
||||||
for p in new_ports:
|
for p in new_ports:
|
||||||
self._set_subnet_info(p)
|
self._set_subnet_info(p)
|
||||||
self.internal_network_added(ri, p['network_id'], p['id'],
|
self.internal_network_added(ri, p['network_id'], p['id'],
|
||||||
p['ip_cidr'], p['mac_address'])
|
p['ip_cidr'], p['mac_address'])
|
||||||
ri.internal_ports.append(p)
|
ri.internal_ports.append(p)
|
||||||
|
if (not new_ipv6_port and
|
||||||
|
netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
|
||||||
|
new_ipv6_port = True
|
||||||
|
|
||||||
for p in old_ports:
|
for p in old_ports:
|
||||||
self.internal_network_removed(ri, p['id'], p['ip_cidr'])
|
self.internal_network_removed(ri, p['id'], p['ip_cidr'])
|
||||||
ri.internal_ports.remove(p)
|
ri.internal_ports.remove(p)
|
||||||
|
if (not old_ipv6_port and
|
||||||
|
netaddr.IPNetwork(p['subnet']['cidr']).version == 6):
|
||||||
|
old_ipv6_port = True
|
||||||
|
|
||||||
|
if new_ipv6_port or old_ipv6_port:
|
||||||
|
ra.enable_ipv6_ra(ri.router_id,
|
||||||
|
ri.ns_name,
|
||||||
|
internal_ports,
|
||||||
|
self.get_internal_device_name,
|
||||||
|
self.root_helper)
|
||||||
|
|
||||||
existing_devices = self._get_existing_devices(ri)
|
existing_devices = self._get_existing_devices(ri)
|
||||||
current_internal_devs = set([n for n in existing_devices
|
current_internal_devs = set([n for n in existing_devices
|
||||||
|
@ -14,8 +14,6 @@
|
|||||||
#
|
#
|
||||||
# @author: Mark McClain, DreamHost
|
# @author: Mark McClain, DreamHost
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
@ -38,25 +36,40 @@ class ProcessManager(object):
|
|||||||
|
|
||||||
Note: The manager expects uuid to be in cmdline.
|
Note: The manager expects uuid to be in cmdline.
|
||||||
"""
|
"""
|
||||||
def __init__(self, conf, uuid, root_helper='sudo', namespace=None):
|
def __init__(self, conf, uuid, root_helper='sudo',
|
||||||
|
namespace=None, service=None):
|
||||||
self.conf = conf
|
self.conf = conf
|
||||||
self.uuid = uuid
|
self.uuid = uuid
|
||||||
self.root_helper = root_helper
|
self.root_helper = root_helper
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
if service:
|
||||||
|
self.service_pid_fname = 'pid.' + service
|
||||||
|
else:
|
||||||
|
self.service_pid_fname = 'pid'
|
||||||
|
|
||||||
def enable(self, cmd_callback):
|
def enable(self, cmd_callback, reload_cfg=False):
|
||||||
if not self.active:
|
if not self.active:
|
||||||
cmd = cmd_callback(self.get_pid_file_name(ensure_pids_dir=True))
|
cmd = cmd_callback(self.get_pid_file_name(ensure_pids_dir=True))
|
||||||
|
|
||||||
ip_wrapper = ip_lib.IPWrapper(self.root_helper, self.namespace)
|
ip_wrapper = ip_lib.IPWrapper(self.root_helper, self.namespace)
|
||||||
ip_wrapper.netns.execute(cmd)
|
ip_wrapper.netns.execute(cmd)
|
||||||
|
elif reload_cfg:
|
||||||
|
self.reload_cfg()
|
||||||
|
|
||||||
def disable(self):
|
def reload_cfg(self):
|
||||||
|
self.disable('HUP')
|
||||||
|
|
||||||
|
def disable(self, sig='9'):
|
||||||
pid = self.pid
|
pid = self.pid
|
||||||
|
|
||||||
if self.active:
|
if self.active:
|
||||||
cmd = ['kill', '-9', pid]
|
cmd = ['kill', '-%s' % (sig), pid]
|
||||||
utils.execute(cmd, self.root_helper)
|
utils.execute(cmd, self.root_helper)
|
||||||
|
# In the case of shutting down, remove the pid file
|
||||||
|
if sig == '9':
|
||||||
|
utils.remove_conf_file(self.conf.external_pids,
|
||||||
|
self.uuid,
|
||||||
|
self.service_pid_fname)
|
||||||
elif pid:
|
elif pid:
|
||||||
LOG.debug(_('Process for %(uuid)s pid %(pid)d is stale, ignoring '
|
LOG.debug(_('Process for %(uuid)s pid %(pid)d is stale, ignoring '
|
||||||
'command'), {'uuid': self.uuid, 'pid': pid})
|
'command'), {'uuid': self.uuid, 'pid': pid})
|
||||||
@ -65,28 +78,18 @@ class ProcessManager(object):
|
|||||||
|
|
||||||
def get_pid_file_name(self, ensure_pids_dir=False):
|
def get_pid_file_name(self, ensure_pids_dir=False):
|
||||||
"""Returns the file name for a given kind of config file."""
|
"""Returns the file name for a given kind of config file."""
|
||||||
pids_dir = os.path.abspath(os.path.normpath(self.conf.external_pids))
|
return utils.get_conf_file_name(self.conf.external_pids,
|
||||||
if ensure_pids_dir and not os.path.isdir(pids_dir):
|
self.uuid,
|
||||||
os.makedirs(pids_dir, 0o755)
|
self.service_pid_fname,
|
||||||
|
ensure_pids_dir)
|
||||||
return os.path.join(pids_dir, self.uuid + '.pid')
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pid(self):
|
def pid(self):
|
||||||
"""Last known pid for this external process spawned for this uuid."""
|
"""Last known pid for this external process spawned for this uuid."""
|
||||||
file_name = self.get_pid_file_name()
|
return utils.get_value_from_conf_file(self.conf.external_pids,
|
||||||
msg = _('Error while reading %s')
|
self.uuid,
|
||||||
|
self.service_pid_fname,
|
||||||
try:
|
int)
|
||||||
with open(file_name, 'r') as f:
|
|
||||||
return int(f.read())
|
|
||||||
except IOError:
|
|
||||||
msg = _('Unable to access %s')
|
|
||||||
except ValueError:
|
|
||||||
msg = _('Unable to convert value in %s')
|
|
||||||
|
|
||||||
LOG.debug(msg, file_name)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def active(self):
|
def active(self):
|
||||||
|
122
neutron/agent/linux/ra.py
Normal file
122
neutron/agent/linux/ra.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# Copyright 2014 OpenStack Foundation
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# 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 netaddr
|
||||||
|
from oslo.config import cfg
|
||||||
|
import six
|
||||||
|
|
||||||
|
from neutron.agent.linux import external_process
|
||||||
|
from neutron.agent.linux import utils
|
||||||
|
from neutron.common import constants
|
||||||
|
from neutron.openstack.common import log as logging
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
OPTS = [
|
||||||
|
cfg.StrOpt('ra_confs',
|
||||||
|
default='$state_path/ra',
|
||||||
|
help=_('Location to store IPv6 RA config files')),
|
||||||
|
]
|
||||||
|
|
||||||
|
cfg.CONF.register_opts(OPTS)
|
||||||
|
|
||||||
|
prefix_fmt = """interface %s
|
||||||
|
{
|
||||||
|
AdvSendAdvert on;
|
||||||
|
MinRtrAdvInterval 3;
|
||||||
|
MaxRtrAdvInterval 10;
|
||||||
|
prefix %s
|
||||||
|
{
|
||||||
|
AdvOnLink on;
|
||||||
|
AdvAutonomous on;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
default_fmt = """interface %s
|
||||||
|
{
|
||||||
|
AdvSendAdvert on;
|
||||||
|
MinRtrAdvInterval 3;
|
||||||
|
MaxRtrAdvInterval 10;
|
||||||
|
};
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _is_slaac(ra_mode):
|
||||||
|
return (ra_mode == constants.IPV6_SLAAC or
|
||||||
|
ra_mode == constants.DHCPV6_STATELESS)
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_radvd_conf(router_id, router_ports, dev_name_helper):
|
||||||
|
radvd_conf = utils.get_conf_file_name(cfg.CONF.ra_confs,
|
||||||
|
router_id,
|
||||||
|
'radvd.conf',
|
||||||
|
True)
|
||||||
|
buf = six.StringIO()
|
||||||
|
for p in router_ports:
|
||||||
|
if netaddr.IPNetwork(p['subnet']['cidr']).version == 6:
|
||||||
|
interface_name = dev_name_helper(p['id'])
|
||||||
|
if _is_slaac(p['subnet']['ipv6_ra_mode']):
|
||||||
|
conf_str = prefix_fmt % (interface_name,
|
||||||
|
p['subnet']['cidr'])
|
||||||
|
else:
|
||||||
|
conf_str = default_fmt % interface_name
|
||||||
|
buf.write('%s' % conf_str)
|
||||||
|
|
||||||
|
utils.replace_file(radvd_conf, buf.getvalue())
|
||||||
|
return radvd_conf
|
||||||
|
|
||||||
|
|
||||||
|
def _spawn_radvd(router_id, radvd_conf, router_ns, root_helper):
|
||||||
|
def callback(pid_file):
|
||||||
|
radvd_cmd = ['radvd',
|
||||||
|
'-C', '%s' % radvd_conf,
|
||||||
|
'-p', '%s' % pid_file]
|
||||||
|
return radvd_cmd
|
||||||
|
|
||||||
|
radvd = external_process.ProcessManager(cfg.CONF,
|
||||||
|
router_id,
|
||||||
|
root_helper,
|
||||||
|
router_ns,
|
||||||
|
'radvd')
|
||||||
|
radvd.enable(callback, True)
|
||||||
|
LOG.debug("radvd enabled for router %s", router_id)
|
||||||
|
|
||||||
|
|
||||||
|
def enable_ipv6_ra(router_id, router_ns, router_ports,
|
||||||
|
dev_name_helper, root_helper):
|
||||||
|
for p in router_ports:
|
||||||
|
if netaddr.IPNetwork(p['subnet']['cidr']).version == 6:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
# Kill the daemon if it's running
|
||||||
|
disable_ipv6_ra(router_id, router_ns, root_helper)
|
||||||
|
return
|
||||||
|
|
||||||
|
LOG.debug("Enable IPv6 RA for router %s", router_id)
|
||||||
|
radvd_conf = _generate_radvd_conf(router_id, router_ports, dev_name_helper)
|
||||||
|
_spawn_radvd(router_id, radvd_conf, router_ns, root_helper)
|
||||||
|
|
||||||
|
|
||||||
|
def disable_ipv6_ra(router_id, router_ns, root_helper):
|
||||||
|
radvd = external_process.ProcessManager(cfg.CONF,
|
||||||
|
router_id,
|
||||||
|
root_helper,
|
||||||
|
router_ns,
|
||||||
|
'radvd')
|
||||||
|
radvd.disable()
|
||||||
|
utils.remove_conf_files(cfg.CONF.ra_confs, router_id)
|
||||||
|
LOG.debug("radvd disabled for router %s", router_id)
|
@ -18,6 +18,7 @@
|
|||||||
import fcntl
|
import fcntl
|
||||||
import os
|
import os
|
||||||
import shlex
|
import shlex
|
||||||
|
import shutil
|
||||||
import socket
|
import socket
|
||||||
import struct
|
import struct
|
||||||
import tempfile
|
import tempfile
|
||||||
@ -126,3 +127,51 @@ def find_child_pids(pid):
|
|||||||
ctxt.reraise = False
|
ctxt.reraise = False
|
||||||
return []
|
return []
|
||||||
return [x.strip() for x in raw_pids.split('\n') if x.strip()]
|
return [x.strip() for x in raw_pids.split('\n') if x.strip()]
|
||||||
|
|
||||||
|
|
||||||
|
def _get_conf_dir(cfg_root, uuid, ensure_conf_dir):
|
||||||
|
confs_dir = os.path.abspath(os.path.normpath(cfg_root))
|
||||||
|
conf_dir = os.path.join(confs_dir, uuid)
|
||||||
|
if ensure_conf_dir:
|
||||||
|
if not os.path.isdir(conf_dir):
|
||||||
|
os.makedirs(conf_dir, 0o755)
|
||||||
|
return conf_dir
|
||||||
|
|
||||||
|
|
||||||
|
def get_conf_file_name(cfg_root, uuid, cfg_file, ensure_conf_dir=False):
|
||||||
|
"""Returns the file name for a given kind of config file."""
|
||||||
|
conf_dir = _get_conf_dir(cfg_root, uuid, ensure_conf_dir)
|
||||||
|
return os.path.join(conf_dir, cfg_file)
|
||||||
|
|
||||||
|
|
||||||
|
def get_value_from_conf_file(cfg_root, uuid, cfg_file, converter=None):
|
||||||
|
"""A helper function to read a value from one of a config file."""
|
||||||
|
file_name = get_conf_file_name(cfg_root, uuid, cfg_file)
|
||||||
|
msg = _('Error while reading %s')
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(file_name, 'r') as f:
|
||||||
|
try:
|
||||||
|
return converter and converter(f.read()) or f.read()
|
||||||
|
except ValueError:
|
||||||
|
msg = _('Unable to convert value in %s')
|
||||||
|
except IOError:
|
||||||
|
msg = _('Unable to access %s')
|
||||||
|
|
||||||
|
LOG.debug(msg % file_name)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def remove_conf_files(cfg_root, uuid):
|
||||||
|
conf_dir = _get_conf_dir(cfg_root, uuid, False)
|
||||||
|
shutil.rmtree(conf_dir, ignore_errors=True)
|
||||||
|
|
||||||
|
|
||||||
|
def remove_conf_file(cfg_root, uuid, cfg_file):
|
||||||
|
"""Remove a config file. Remove the directory if this is the last file."""
|
||||||
|
conf_file = get_conf_file_name(cfg_root, uuid, cfg_file)
|
||||||
|
if os.path.exists(conf_file):
|
||||||
|
os.unlink(conf_file)
|
||||||
|
conf_dir = _get_conf_dir(cfg_root, uuid, False)
|
||||||
|
if not os.listdir(conf_dir):
|
||||||
|
shutil.rmtree(conf_dir, ignore_errors=True)
|
||||||
|
@ -1007,7 +1007,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
|
|
||||||
network_ids = set(p['network_id'] for p, _ in each_port_with_ip())
|
network_ids = set(p['network_id'] for p, _ in each_port_with_ip())
|
||||||
filters = {'network_id': [id for id in network_ids]}
|
filters = {'network_id': [id for id in network_ids]}
|
||||||
fields = ['id', 'cidr', 'gateway_ip', 'network_id']
|
fields = ['id', 'cidr', 'gateway_ip',
|
||||||
|
'network_id', 'ipv6_ra_mode']
|
||||||
|
|
||||||
subnets_by_network = dict((id, []) for id in network_ids)
|
subnets_by_network = dict((id, []) for id in network_ids)
|
||||||
for subnet in self._core_plugin.get_subnets(context, filters, fields):
|
for subnet in self._core_plugin.get_subnets(context, filters, fields):
|
||||||
@ -1018,7 +1019,8 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
|
|||||||
for subnet in subnets_by_network[port['network_id']]:
|
for subnet in subnets_by_network[port['network_id']]:
|
||||||
subnet_info = {'id': subnet['id'],
|
subnet_info = {'id': subnet['id'],
|
||||||
'cidr': subnet['cidr'],
|
'cidr': subnet['cidr'],
|
||||||
'gateway_ip': subnet['gateway_ip']}
|
'gateway_ip': subnet['gateway_ip'],
|
||||||
|
'ipv6_ra_mode': subnet['ipv6_ra_mode']}
|
||||||
|
|
||||||
if subnet['id'] == fixed_ip['subnet_id']:
|
if subnet['id'] == fixed_ip['subnet_id']:
|
||||||
port['subnet'] = subnet_info
|
port['subnet'] = subnet_info
|
||||||
|
@ -121,7 +121,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestBasicRouterOperations, self).setUp()
|
super(TestBasicRouterOperations, self).setUp()
|
||||||
self.conf = cfg.ConfigOpts()
|
self.conf = agent_config.setup_conf()
|
||||||
self.conf.register_opts(base_config.core_opts)
|
self.conf.register_opts(base_config.core_opts)
|
||||||
self.conf.register_opts(l3_agent.L3NATAgent.OPTS)
|
self.conf.register_opts(l3_agent.L3NATAgent.OPTS)
|
||||||
agent_config.register_interface_driver_opts_helper(self.conf)
|
agent_config.register_interface_driver_opts_helper(self.conf)
|
||||||
@ -141,6 +141,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
'neutron.agent.linux.utils.execute')
|
'neutron.agent.linux.utils.execute')
|
||||||
self.utils_exec = self.utils_exec_p.start()
|
self.utils_exec = self.utils_exec_p.start()
|
||||||
|
|
||||||
|
self.utils_replace_file_p = mock.patch(
|
||||||
|
'neutron.agent.linux.utils.replace_file')
|
||||||
|
self.utils_replace_file = self.utils_replace_file_p.start()
|
||||||
|
|
||||||
self.external_process_p = mock.patch(
|
self.external_process_p = mock.patch(
|
||||||
'neutron.agent.linux.external_process.ProcessManager')
|
'neutron.agent.linux.external_process.ProcessManager')
|
||||||
self.external_process = self.external_process_p.start()
|
self.external_process = self.external_process_p.start()
|
||||||
@ -441,6 +445,38 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
else:
|
else:
|
||||||
self.assertIn(r.rule, expected_rules)
|
self.assertIn(r.rule, expected_rules)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _router_append_interface(router, count=1, ip_version=4,
|
||||||
|
ra_mode=None, addr_mode=None):
|
||||||
|
if ip_version == 4:
|
||||||
|
ip_pool = '35.4.%i.4'
|
||||||
|
cidr_pool = '35.4.%i.0/24'
|
||||||
|
gw_pool = '35.4.%i.1'
|
||||||
|
elif ip_version == 6:
|
||||||
|
ip_pool = 'fd01:%x::6'
|
||||||
|
cidr_pool = 'fd01:%x::/64'
|
||||||
|
gw_pool = 'fd01:%x::1'
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid ip_version: %s" % ip_version)
|
||||||
|
|
||||||
|
interfaces = router[l3_constants.INTERFACE_KEY]
|
||||||
|
current = sum(
|
||||||
|
[netaddr.IPNetwork(p['subnet']['cidr']).version == ip_version
|
||||||
|
for p in interfaces])
|
||||||
|
|
||||||
|
for i in range(current, current + count):
|
||||||
|
interfaces.append(
|
||||||
|
{'id': _uuid(),
|
||||||
|
'network_id': _uuid(),
|
||||||
|
'admin_state_up': True,
|
||||||
|
'fixed_ips': [{'ip_address': ip_pool % i,
|
||||||
|
'subnet_id': _uuid()}],
|
||||||
|
'mac_address': 'ca:fe:de:ad:be:ef',
|
||||||
|
'subnet': {'cidr': cidr_pool % i,
|
||||||
|
'gateway_ip': gw_pool % i,
|
||||||
|
'ipv6_ra_mode': ra_mode,
|
||||||
|
'ipv6_address_mode': addr_mode}})
|
||||||
|
|
||||||
def _prepare_router_data(self, ip_version=4,
|
def _prepare_router_data(self, ip_version=4,
|
||||||
enable_snat=None, num_internal_ports=1):
|
enable_snat=None, num_internal_ports=1):
|
||||||
if ip_version == 4:
|
if ip_version == 4:
|
||||||
@ -451,6 +487,8 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
ip_addr = 'fd00::4'
|
ip_addr = 'fd00::4'
|
||||||
cidr = 'fd00::/64'
|
cidr = 'fd00::/64'
|
||||||
gateway_ip = 'fd00::1'
|
gateway_ip = 'fd00::1'
|
||||||
|
else:
|
||||||
|
raise ValueError("Invalid ip_version: %s" % ip_version)
|
||||||
|
|
||||||
router_id = _uuid()
|
router_id = _uuid()
|
||||||
ex_gw_port = {'id': _uuid(),
|
ex_gw_port = {'id': _uuid(),
|
||||||
@ -459,22 +497,15 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
'subnet_id': _uuid()}],
|
'subnet_id': _uuid()}],
|
||||||
'subnet': {'cidr': cidr,
|
'subnet': {'cidr': cidr,
|
||||||
'gateway_ip': gateway_ip}}
|
'gateway_ip': gateway_ip}}
|
||||||
int_ports = []
|
|
||||||
for i in range(num_internal_ports):
|
|
||||||
int_ports.append({'id': _uuid(),
|
|
||||||
'network_id': _uuid(),
|
|
||||||
'admin_state_up': True,
|
|
||||||
'fixed_ips': [{'ip_address': '35.4.%s.4' % i,
|
|
||||||
'subnet_id': _uuid()}],
|
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
||||||
'subnet': {'cidr': '35.4.%s.0/24' % i,
|
|
||||||
'gateway_ip': '35.4.%s.1' % i}})
|
|
||||||
|
|
||||||
router = {
|
router = {
|
||||||
'id': router_id,
|
'id': router_id,
|
||||||
l3_constants.INTERFACE_KEY: int_ports,
|
l3_constants.INTERFACE_KEY: [],
|
||||||
'routes': [],
|
'routes': [],
|
||||||
'gw_port': ex_gw_port}
|
'gw_port': ex_gw_port}
|
||||||
|
self._router_append_interface(router, count=num_internal_ports,
|
||||||
|
ip_version=ip_version)
|
||||||
|
|
||||||
if enable_snat is not None:
|
if enable_snat is not None:
|
||||||
router['enable_snat'] = enable_snat
|
router['enable_snat'] = enable_snat
|
||||||
return router
|
return router
|
||||||
@ -725,15 +756,7 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
||||||
# Add an interface and reprocess
|
# Add an interface and reprocess
|
||||||
router[l3_constants.INTERFACE_KEY].append(
|
self._router_append_interface(router)
|
||||||
{'id': _uuid(),
|
|
||||||
'network_id': _uuid(),
|
|
||||||
'admin_state_up': True,
|
|
||||||
'fixed_ips': [{'ip_address': '35.4.1.4',
|
|
||||||
'subnet_id': _uuid()}],
|
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
||||||
'subnet': {'cidr': '35.4.1.0/24',
|
|
||||||
'gateway_ip': '35.4.1.1'}})
|
|
||||||
# Reassign the router object to RouterInfo
|
# Reassign the router object to RouterInfo
|
||||||
ri.router = router
|
ri.router = router
|
||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
@ -772,9 +795,9 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
self.assertFalse(external_gateway_nat_rules.called)
|
self.assertFalse(external_gateway_nat_rules.called)
|
||||||
self.assertEqual(orig_nat_rules, new_nat_rules)
|
self.assertEqual(orig_nat_rules, new_nat_rules)
|
||||||
|
|
||||||
def test_process_router_ipv6_interface_added(self):
|
def _process_router_ipv6_interface_added(
|
||||||
|
self, router, ra_mode=None, addr_mode=None):
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
router = self._prepare_router_data()
|
|
||||||
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
||||||
self.conf.use_namespaces, router=router)
|
self.conf.use_namespaces, router=router)
|
||||||
agent.external_gateway_added = mock.Mock()
|
agent.external_gateway_added = mock.Mock()
|
||||||
@ -782,23 +805,53 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
||||||
# Add an IPv6 interface and reprocess
|
# Add an IPv6 interface and reprocess
|
||||||
router[l3_constants.INTERFACE_KEY].append(
|
self._router_append_interface(router, count=1, ip_version=6,
|
||||||
{'id': _uuid(),
|
ra_mode=ra_mode, addr_mode=addr_mode)
|
||||||
'network_id': _uuid(),
|
|
||||||
'admin_state_up': True,
|
|
||||||
'fixed_ips': [{'ip_address': 'fd00::2',
|
|
||||||
'subnet_id': _uuid()}],
|
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
||||||
'subnet': {'cidr': 'fd00::/64',
|
|
||||||
'gateway_ip': 'fd00::1'}})
|
|
||||||
# Reassign the router object to RouterInfo
|
# Reassign the router object to RouterInfo
|
||||||
ri.router = router
|
ri.router = router
|
||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
# For some reason set logic does not work well with
|
# IPv4 NAT rules should not be changed by adding an IPv6 interface
|
||||||
# IpTablesRule instances
|
|
||||||
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
||||||
if r not in orig_nat_rules]
|
if r not in orig_nat_rules]
|
||||||
self.assertFalse(nat_rules_delta)
|
self.assertFalse(nat_rules_delta)
|
||||||
|
return ri
|
||||||
|
|
||||||
|
def _expected_call_lookup_ri_process(self, ri, process):
|
||||||
|
"""Expected call if a process is looked up in a router instance."""
|
||||||
|
return [mock.call(cfg.CONF,
|
||||||
|
ri.router['id'],
|
||||||
|
self.conf.root_helper,
|
||||||
|
ri.ns_name,
|
||||||
|
process)]
|
||||||
|
|
||||||
|
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(ri, process)
|
||||||
|
expected_calls.append(mock.call().enable(mock.ANY, True))
|
||||||
|
self.assertEqual(expected_calls, self.external_process.mock_calls)
|
||||||
|
|
||||||
|
def _assert_ri_process_disabled(self, ri, process):
|
||||||
|
"""Verify that process was disabled for a router instance."""
|
||||||
|
expected_calls = self._expected_call_lookup_ri_process(ri, process)
|
||||||
|
expected_calls.append(mock.call().disable())
|
||||||
|
self.assertEqual(expected_calls, self.external_process.mock_calls)
|
||||||
|
|
||||||
|
def test_process_router_ipv6_interface_added(self):
|
||||||
|
router = self._prepare_router_data()
|
||||||
|
ri = self._process_router_ipv6_interface_added(router)
|
||||||
|
self._assert_ri_process_enabled(ri, 'radvd')
|
||||||
|
# Expect radvd configured without prefix
|
||||||
|
self.assertNotIn('prefix',
|
||||||
|
self.utils_replace_file.call_args[0][1].split())
|
||||||
|
|
||||||
|
def test_process_router_ipv6_slaac_interface_added(self):
|
||||||
|
router = self._prepare_router_data()
|
||||||
|
ri = self._process_router_ipv6_interface_added(
|
||||||
|
router, ra_mode=l3_constants.IPV6_SLAAC)
|
||||||
|
self._assert_ri_process_enabled(ri, 'radvd')
|
||||||
|
# Expect radvd configured with prefix
|
||||||
|
self.assertIn('prefix',
|
||||||
|
self.utils_replace_file.call_args[0][1].split())
|
||||||
|
|
||||||
def test_process_router_ipv6v4_interface_added(self):
|
def test_process_router_ipv6v4_interface_added(self):
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
@ -810,28 +863,12 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
orig_nat_rules = ri.iptables_manager.ipv4['nat'].rules[:]
|
||||||
# Add an IPv4 and IPv6 interface and reprocess
|
# Add an IPv4 and IPv6 interface and reprocess
|
||||||
router[l3_constants.INTERFACE_KEY].append(
|
self._router_append_interface(router, count=1, ip_version=4)
|
||||||
{'id': _uuid(),
|
self._router_append_interface(router, count=1, ip_version=6)
|
||||||
'network_id': _uuid(),
|
|
||||||
'admin_state_up': True,
|
|
||||||
'fixed_ips': [{'ip_address': '35.4.1.4',
|
|
||||||
'subnet_id': _uuid()}],
|
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
||||||
'subnet': {'cidr': '35.4.1.0/24',
|
|
||||||
'gateway_ip': '35.4.1.1'}})
|
|
||||||
|
|
||||||
router[l3_constants.INTERFACE_KEY].append(
|
|
||||||
{'id': _uuid(),
|
|
||||||
'network_id': _uuid(),
|
|
||||||
'admin_state_up': True,
|
|
||||||
'fixed_ips': [{'ip_address': 'fd00::2',
|
|
||||||
'subnet_id': _uuid()}],
|
|
||||||
'mac_address': 'ca:fe:de:ad:be:ef',
|
|
||||||
'subnet': {'cidr': 'fd00::/64',
|
|
||||||
'gateway_ip': 'fd00::1'}})
|
|
||||||
# Reassign the router object to RouterInfo
|
# Reassign the router object to RouterInfo
|
||||||
ri.router = router
|
ri.router = router
|
||||||
agent.process_router(ri)
|
agent.process_router(ri)
|
||||||
|
self._assert_ri_process_enabled(ri, 'radvd')
|
||||||
# For some reason set logic does not work well with
|
# For some reason set logic does not work well with
|
||||||
# IpTablesRule instances
|
# IpTablesRule instances
|
||||||
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
nat_rules_delta = [r for r in ri.iptables_manager.ipv4['nat'].rules
|
||||||
@ -862,6 +899,25 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
# send_arp is called both times process_router is called
|
# send_arp is called both times process_router is called
|
||||||
self.assertEqual(self.send_arp.call_count, 2)
|
self.assertEqual(self.send_arp.call_count, 2)
|
||||||
|
|
||||||
|
def test_process_router_ipv6_interface_removed(self):
|
||||||
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
|
router = self._prepare_router_data()
|
||||||
|
ri = l3_agent.RouterInfo(router['id'], self.conf.root_helper,
|
||||||
|
self.conf.use_namespaces, router=router)
|
||||||
|
agent.external_gateway_added = mock.Mock()
|
||||||
|
ri.router = router
|
||||||
|
agent.process_router(ri)
|
||||||
|
# Add an IPv6 interface and reprocess
|
||||||
|
self._router_append_interface(router, count=1, ip_version=6)
|
||||||
|
agent.process_router(ri)
|
||||||
|
self._assert_ri_process_enabled(ri, 'radvd')
|
||||||
|
# Reset the calls so we can check for disable radvd
|
||||||
|
self.external_process.reset_mock()
|
||||||
|
# Remove the IPv6 interface and reprocess
|
||||||
|
del router[l3_constants.INTERFACE_KEY][1]
|
||||||
|
agent.process_router(ri)
|
||||||
|
self._assert_ri_process_disabled(ri, 'radvd')
|
||||||
|
|
||||||
def test_process_router_internal_network_added_unexpected_error(self):
|
def test_process_router_internal_network_added_unexpected_error(self):
|
||||||
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
|
||||||
router = self._prepare_router_data()
|
router = self._prepare_router_data()
|
||||||
@ -1358,7 +1414,10 @@ class TestBasicRouterOperations(base.BaseTestCase):
|
|||||||
ns_list = agent._list_namespaces()
|
ns_list = agent._list_namespaces()
|
||||||
agent._cleanup_namespaces(ns_list, [r['id'] for r in router_list])
|
agent._cleanup_namespaces(ns_list, [r['id'] for r in router_list])
|
||||||
|
|
||||||
self.assertEqual(pm.disable.call_count, len(stale_namespace_list))
|
# Expect process manager to disable two processes (metadata_proxy
|
||||||
|
# and radvd) per stale namespace.
|
||||||
|
expected_pm_disables = 2 * len(stale_namespace_list)
|
||||||
|
self.assertEqual(expected_pm_disables, pm.disable.call_count)
|
||||||
self.assertEqual(agent._destroy_router_namespace.call_count,
|
self.assertEqual(agent._destroy_router_namespace.call_count,
|
||||||
len(stale_namespace_list))
|
len(stale_namespace_list))
|
||||||
expected_args = [mock.call(ns) for ns in stale_namespace_list]
|
expected_args = [mock.call(ns) for ns in stale_namespace_list]
|
||||||
|
@ -120,27 +120,27 @@ class TestProcessManager(base.BaseTestCase):
|
|||||||
debug.assert_called_once_with(mock.ANY, mock.ANY)
|
debug.assert_called_once_with(mock.ANY, mock.ANY)
|
||||||
|
|
||||||
def test_get_pid_file_name_existing(self):
|
def test_get_pid_file_name_existing(self):
|
||||||
with mock.patch.object(ep.os.path, 'isdir') as isdir:
|
with mock.patch.object(ep.utils.os.path, 'isdir') as isdir:
|
||||||
isdir.return_value = True
|
isdir.return_value = True
|
||||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||||
retval = manager.get_pid_file_name(ensure_pids_dir=True)
|
retval = manager.get_pid_file_name(ensure_pids_dir=True)
|
||||||
self.assertEqual(retval, '/var/path/uuid.pid')
|
self.assertEqual(retval, '/var/path/uuid/pid')
|
||||||
|
|
||||||
def test_get_pid_file_name_not_existing(self):
|
def test_get_pid_file_name_not_existing(self):
|
||||||
with mock.patch.object(ep.os.path, 'isdir') as isdir:
|
with mock.patch.object(ep.utils.os.path, 'isdir') as isdir:
|
||||||
with mock.patch.object(ep.os, 'makedirs') as makedirs:
|
with mock.patch.object(ep.utils.os, 'makedirs') as makedirs:
|
||||||
isdir.return_value = False
|
isdir.return_value = False
|
||||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||||
retval = manager.get_pid_file_name(ensure_pids_dir=True)
|
retval = manager.get_pid_file_name(ensure_pids_dir=True)
|
||||||
self.assertEqual(retval, '/var/path/uuid.pid')
|
self.assertEqual(retval, '/var/path/uuid/pid')
|
||||||
makedirs.assert_called_once_with('/var/path', 0o755)
|
makedirs.assert_called_once_with('/var/path/uuid', 0o755)
|
||||||
|
|
||||||
def test_get_pid_file_name_default(self):
|
def test_get_pid_file_name_default(self):
|
||||||
with mock.patch.object(ep.os.path, 'isdir') as isdir:
|
with mock.patch.object(ep.utils.os.path, 'isdir') as isdir:
|
||||||
isdir.return_value = True
|
isdir.return_value = True
|
||||||
manager = ep.ProcessManager(self.conf, 'uuid')
|
manager = ep.ProcessManager(self.conf, 'uuid')
|
||||||
retval = manager.get_pid_file_name(ensure_pids_dir=False)
|
retval = manager.get_pid_file_name(ensure_pids_dir=False)
|
||||||
self.assertEqual(retval, '/var/path/uuid.pid')
|
self.assertEqual(retval, '/var/path/uuid/pid')
|
||||||
self.assertFalse(isdir.called)
|
self.assertFalse(isdir.called)
|
||||||
|
|
||||||
def test_pid(self):
|
def test_pid(self):
|
||||||
|
Loading…
Reference in New Issue
Block a user