Remove dibbler code from l3 agent and elsewhere

It's not tested, not recommended, not used, marked experimental. It's
time for it to go.

Note: base plugin code still allows to mark subnets for prefix
delegation. This can be, potentially, used with an out-of-tree driver
that would implement interaction with PD server.

Change-Id: I7144ad12180a01eff3ba6ce7290b1c8c7791ebfd
This commit is contained in:
Ihar Hrachyshka
2025-01-08 11:08:01 -05:00
parent c71369841d
commit 71ad55fbd7
29 changed files with 52 additions and 1805 deletions

View File

@@ -543,9 +543,9 @@ Prefix delegation
.. warning::
This feature is experimental with low test coverage, and the Dibbler client
which is used for this feature is no longer maintained. For details see:
https://github.com/tomaszmrugalski/dibbler#project-status
This feature is experimental with low test coverage. There is currently no
reference implementation that would implement the feature in the tree. A
third party driver may have to be used to utilize it.
From the Liberty release onwards, OpenStack Networking supports IPv6 prefix
delegation. This section describes the configuration and workflow steps
@@ -554,12 +554,6 @@ subnet CIDRs. This allows you as the OpenStack administrator to rely on an
external (to the OpenStack Networking service) DHCPv6 server to manage your
project network prefixes.
.. note::
Prefix delegation became available in the Liberty release, it is
not available in the Kilo release. HA and DVR routers
are not currently supported by this feature.
Configuring OpenStack Networking for prefix delegation
------------------------------------------------------
@@ -571,15 +565,7 @@ To enable prefix delegation, edit the ``/etc/neutron/neutron.conf`` file.
.. note::
If you are not using the default dibbler-based driver for prefix
delegation, then you also need to set the driver in
``/etc/neutron/neutron.conf``:
.. code-block:: console
pd_dhcp_driver = <class path to driver>
Drivers other than the default one may require extra configuration.
Drivers may require extra configuration.
This tells OpenStack Networking to use the prefix delegation mechanism for
subnet allocation when the user does not provide a CIDR or subnet pool id when
@@ -595,10 +581,6 @@ For the purposes of this guide we are using the open-source DHCPv6 server,
Dibbler. Dibbler is available in many Linux package managers, or from source at
`tomaszmrugalski/dibbler <https://github.com/tomaszmrugalski/dibbler>`_.
When using the reference implementation of the OpenStack Networking prefix
delegation driver, Dibbler must also be installed on your OpenStack Networking
node(s) to serve as a DHCPv6 client. Version 1.0.1 or higher is required.
This guide assumes that you are running a Dibbler server on the network node
where the external network bridge exists. If you already have a prefix
delegation capable DHCPv6 server in place, then you can skip the following

View File

@@ -17,7 +17,7 @@ neutron-sanity-check usage
usage: neutron-sanity-check [-h] [--arp_header_match] [--arp_responder]
[--bridge_firewalling] [--config-dir DIR]
[--config-file PATH] [--debug] [--dhcp_release6]
[--dibbler_version] [--dnsmasq_version]
[--dnsmasq_version]
[--ebtables_installed] [--icmpv6_header_match]
[--ip6tables_installed] [--ip_nonlocal_bind]
[--iproute2_vxlan] [--ipset_installed]
@@ -27,7 +27,7 @@ neutron-sanity-check usage
[--log-dir LOG_DIR] [--log-file PATH]
[--noarp_header_match] [--noarp_responder]
[--nobridge_firewalling] [--nodebug]
[--nodhcp_release6] [--nodibbler_version]
[--nodhcp_release6]
[--nodnsmasq_version] [--noebtables_installed]
[--noicmpv6_header_match]
[--noip6tables_installed] [--noip_nonlocal_bind]
@@ -80,9 +80,6 @@ neutron-sanity-check optional arguments
``--dhcp_release6``
Check dhcp_release6 installation
``--dibbler_version``
Check minimal dibbler version
``--dnsmasq_version``
Check minimal dnsmasq version
@@ -141,9 +138,6 @@ neutron-sanity-check optional arguments
``--nodhcp_release6``
The inverse of --dhcp_release6
``--nodibbler_version``
The inverse of --dibbler_version
``--nodnsmasq_version``
The inverse of --dnsmasq_version

View File

@@ -89,9 +89,8 @@ such as what L2 agent to use or what type of routers to create.
+--------------------+------+------------+-----+-----------+----------+------+
* Patch https://review.opendev.org/c/openstack/neutron/+/286087 was abandoned.
* Prefix delegation doesn't have functional tests for the dibbler and pd
layers, nor for the L3 agent changes. This has been an area of repeated
regressions.
* Prefix delegation doesn't have a reference implementation in tree and hence
is not covered with functional tests of any sort.
Missing Infrastructure
----------------------

View File

@@ -13,24 +13,18 @@ at [1]_.
ML2/OVN integration with the Nova placement API to provide guaranteed
minimum bandwidth for ports [2]_. Work in progress, see [3]_
* IPv6 Prefix Delegation
Currently ML2/OVN doesn't implement IPv6 prefix delegation. OVN logical
routers have this capability implemented in [4]_ and we have an open RFE to
fill this gap [5]_.
* DHCP service for instances
ML2/OVS adds packet filtering rules to every instance that allow DHCP queries
from instances to reach the DHCP agent. For OVN this traffic has to be
explicitly allowed by security group rules attached to the instance. Note
that the default security group does allow all outgoing traffic, so this only
becomes relevant when using custom security groups [6]_. Proposed patch is
[7]_ but it needs to be revived and updated.
becomes relevant when using custom security groups [4]_. Proposed patch is
[5]_ but it needs to be revived and updated.
* DNS resolution for instances
OVN cannot use the host's networking for DNS resolution, so Case 2b in [8]_
OVN cannot use the host's networking for DNS resolution, so Case 2b in [6]_
can only be used when additional DHCP agents are deployed. For Case 2a a
different configuration option has to be used in ``ml2_conf.ini``::
@@ -56,7 +50,7 @@ at [1]_.
The core OVN implementation does not support fragmentation of East/West
traffic using an OVN router between two private networks. This is being
tracked in [9]_ and [10]_.
tracked in [7]_ and [8]_.
* North/South Fragmentation and path MTU discovery
@@ -70,13 +64,13 @@ at [1]_.
[ovn]
ovn_emit_need_to_frag = true
This makes path MTU discovery fail, and is being tracked in [9]_ and [11]_.
This makes path MTU discovery fail, and is being tracked in [7]_ and [9]_.
* Traffic metering
Currently ``neutron-metering-agent`` can only work with the Neutron L3 agent.
It is not supported by the ``ovn-router`` service plugin nor by the
``neutron-ovn-agent``. This is being reported and tracked in [12]_.
``neutron-ovn-agent``. This is being reported and tracked in [10]_.
* Floating IP Port Forwarding in provider networks and with distributed routing
@@ -87,7 +81,7 @@ at [1]_.
Due to an incompatible setting of the router to make traffic in the vlan/flat
networks to be distributed but port forwardings are always centralized in
ML2/OVN backend.
This is being reported in [13]_.
This is being reported in [11]_.
References
----------
@@ -95,13 +89,11 @@ References
.. [1] https://github.com/ovn-org/ovn/blob/master/TODO.rst
.. [2] https://specs.openstack.org/openstack/neutron-specs/specs/rocky/minimum-bandwidth-allocation-placement-api.html
.. [3] https://review.opendev.org/c/openstack/neutron/+/786478
.. [4] https://patchwork.ozlabs.org/project/openvswitch/patch/6aec0fb280f610a2083fbb6c61e251b1d237b21f.1576840560.git.lorenzo.bianconi@redhat.com/
.. [5] https://bugs.launchpad.net/neutron/+bug/1895972
.. [6] https://bugs.launchpad.net/neutron/+bug/1926515
.. [7] https://review.opendev.org/c/openstack/neutron/+/788594
.. [8] https://docs.openstack.org/neutron/latest/admin/config-dns-res.html
.. [9] https://bugs.launchpad.net/neutron/+bug/2032817
.. [10] https://bugzilla.redhat.com/show_bug.cgi?id=2238494
.. [11] https://bugzilla.redhat.com/show_bug.cgi?id=2238969
.. [12] https://bugs.launchpad.net/neutron/+bug/2048773
.. [13] https://bugs.launchpad.net/neutron/+bug/2028846
.. [4] https://bugs.launchpad.net/neutron/+bug/1926515
.. [5] https://review.opendev.org/c/openstack/neutron/+/788594
.. [6] https://docs.openstack.org/neutron/latest/admin/config-dns-res.html
.. [7] https://bugs.launchpad.net/neutron/+bug/2032817
.. [8] https://bugzilla.redhat.com/show_bug.cgi?id=2238494
.. [9] https://bugzilla.redhat.com/show_bug.cgi?id=2238969
.. [10] https://bugs.launchpad.net/neutron/+bug/2048773
.. [11] https://bugs.launchpad.net/neutron/+bug/2028846

View File

@@ -46,10 +46,6 @@ haproxy_env: EnvFilter, env, root, PROCESS_TAG=, haproxy, -f, .*
dnsmasq: CommandFilter, dnsmasq, root
dnsmasq_env: EnvFilter, env, root, PROCESS_TAG=, dnsmasq
# DIBBLER
dibbler-client: CommandFilter, dibbler-client, root
dibbler-client_env: EnvFilter, env, root, PROCESS_TAG=, dibbler-client
# L3
radvd: CommandFilter, radvd, root
radvd_env: EnvFilter, env, root, PROCESS_TAG=, radvd

View File

@@ -53,7 +53,6 @@ from neutron.agent.l3 import legacy_router
from neutron.agent.l3 import namespace_manager
from neutron.agent.l3 import namespaces as l3_namespaces
from neutron.agent.linux import external_process
from neutron.agent.linux import pd
from neutron.agent.metadata import driver as metadata_driver
from neutron.agent import rpc as agent_rpc
from neutron.common import utils
@@ -70,15 +69,13 @@ SYNC_ROUTERS_MIN_CHUNK_SIZE = 32
PRIORITY_RELATED_ROUTER = 0
PRIORITY_RPC = 1
PRIORITY_SYNC_ROUTERS_TASK = 2
PRIORITY_PD_UPDATE = 3
# Actions
DELETE_ROUTER = 1
DELETE_RELATED_ROUTER = 2
ADD_UPDATE_ROUTER = 3
ADD_UPDATE_RELATED_ROUTER = 4
PD_UPDATE = 5
UPDATE_NETWORK = 6
UPDATE_NETWORK = 5
RELATED_ACTION_MAP = {DELETE_ROUTER: DELETE_RELATED_ROUTER,
ADD_UPDATE_ROUTER: ADD_UPDATE_RELATED_ROUTER}
@@ -117,6 +114,7 @@ class L3PluginApi:
1.11 Added get_host_ha_router_count
1.12 Added get_networks
1.13 Removed get_external_network_id
1.14 Removed process_prefix_update
"""
def __init__(self, topic, host):
@@ -178,13 +176,6 @@ class L3PluginApi:
return cctxt.cast(context, 'update_ha_routers_states',
host=self.host, states=states)
@utils.timecost
def process_prefix_update(self, context, prefix_update):
"""Process prefix update whenever prefixes get changed."""
cctxt = self.client.prepare(version='1.6')
return cctxt.call(context, 'process_prefix_update',
subnets=prefix_update)
@utils.timecost
def delete_agent_gateway_port(self, context, fip_net):
"""Delete Floatingip_agent_gateway_port."""
@@ -335,12 +326,6 @@ class L3NATAgent(ha.AgentMixin,
self.target_ex_net_id = None
self.use_ipv6 = netutils.is_ipv6_enabled()
self.pd = pd.PrefixDelegation(self.context, self.process_monitor,
self.driver,
self.plugin_rpc.process_prefix_update,
self.create_pd_router_update,
self.conf)
# Consume network updates to trigger router resync
consumers = [[topics.NETWORK, topics.UPDATE]]
agent_rpc.create_consumers([self], topics.AGENT, consumers)
@@ -762,13 +747,6 @@ class L3NATAgent(ha.AgentMixin,
update.id, update.action, update.priority,
update.update_id,
update.time_elapsed_since_create)
if update.action == PD_UPDATE:
self.pd.process_prefix_update()
LOG.info("Finished a router update for %s IPv6 PD, "
"update_id. %s. Time elapsed: %.3f",
update.id, update.update_id,
update.time_elapsed_since_start)
return
routers = [update.resource] if update.resource else []
@@ -977,14 +955,6 @@ class L3NATAgent(ha.AgentMixin,
for router in self.router_info.values():
router.delete()
def create_pd_router_update(self):
router_id = None
update = queue.ResourceUpdate(router_id,
PRIORITY_PD_UPDATE,
timestamp=timeutils.utcnow(),
action=PD_UPDATE)
self._queue.add(update)
class L3NATAgentWithStateReport(L3NATAgent):
@@ -1060,8 +1030,6 @@ class L3NATAgentWithStateReport(L3NATAgent):
# Do the report state before we do the first full sync.
self._report_state()
self.pd.after_start()
def agent_updated(self, context, payload):
"""Handle the agent_updated notification event."""
self.fullsync = True

View File

@@ -128,14 +128,14 @@ class AgentMixin:
def enqueue_state_change(self, router_id, state):
"""Inform the server about the new router state
This function will also update the metadata proxy, the radvd daemon,
process the prefix delegation and inform to the L3 extensions. If the
HA router changes to "primary", this transition will be delayed for at
least "ha_vrrp_advert_int" seconds. When the "primary" router
transitions to "backup", "keepalived" will set the rest of HA routers
to "primary" until it decides which one should be the only "primary".
The transition from "backup" to "primary" and then to "backup" again,
should not be registered in the Neutron server.
This function will also update the metadata proxy, the radvd daemon and
inform to the L3 extensions. If the HA router changes to "primary",
this transition will be delayed for at least "ha_vrrp_advert_int"
seconds. When the "primary" router transitions to "backup",
"keepalived" will set the rest of HA routers to "primary" until it
decides which one should be the only "primary". The transition from
"backup" to "primary" and then to "backup" again, should not be
registered in the Neutron server.
:param router_id: router ID
:param state: ['primary', 'backup']
@@ -180,7 +180,6 @@ class AgentMixin:
if self.conf.enable_metadata_proxy:
self._update_metadata_proxy(ri, router_id, state)
self._update_radvd_daemon(ri, state)
self.pd.process_ha_state(router_id, state == 'primary')
self.state_change_notifier.queue_event((router_id, state))
self.l3_ext_manager.ha_state_change(self.context, state_change_data)

View File

@@ -14,6 +14,7 @@
import abc
import collections
import itertools
import netaddr
from neutron_lib import constants as lib_constants
@@ -29,7 +30,6 @@ from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.agent.linux import ra
from neutron.common import coordination
from neutron.common import ipv6_utils
from neutron.common import utils as common_utils
from neutron.ipam import utils as ipam_utils
@@ -135,7 +135,6 @@ class RouterInfo(BaseRouterInfo):
self.ex_gw_port = None
self.fip_map = {}
self.pd_subnets = {}
self.floating_ips = set()
ns = self.create_router_namespace_object(
router_id, agent_conf, interface_driver, use_ipv6)
@@ -328,19 +327,6 @@ class RouterInfo(BaseRouterInfo):
self.iptables_manager.apply()
def _process_pd_iptables_rules(self, prefix, subnet_id):
"""Configure iptables rules for prefix delegated subnets"""
ext_scope = self._get_external_address_scope()
ext_scope_mark = self.get_address_scope_mark_mask(ext_scope)
ex_gw_device = self.get_external_device_name(
self.get_ex_gw_port()['id'])
scope_rule = self.address_scope_mangle_rule(ex_gw_device,
ext_scope_mark)
self.iptables_manager.ipv6['mangle'].add_rule(
'scope',
'-d %s ' % prefix + scope_rule,
tag=('prefix_delegation_%s' % subnet_id))
def process_floating_ip_address_scope_rules(self):
"""Configure address scope related iptables rules for the router's
floating IPs.
@@ -676,56 +662,25 @@ class RouterInfo(BaseRouterInfo):
updated_ports = self._get_updated_ports(self.internal_ports,
internal_ports)
enable_ra = False
for p in old_ports:
self.internal_network_removed(p)
LOG.debug("removing port %s from internal_ports cache", p)
self.internal_ports.remove(p)
enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
for subnet in p['subnets']:
if ipv6_utils.is_ipv6_pd_enabled(subnet):
self.agent.pd.disable_subnet(self.router_id, subnet['id'])
self.pd_subnets.pop(subnet['id'], None)
for p in new_ports:
self.internal_network_added(p)
LOG.debug("appending port %s to internal_ports cache", p)
self._update_internal_ports_cache(p)
enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
for subnet in p['subnets']:
if ipv6_utils.is_ipv6_pd_enabled(subnet):
interface_name = self.get_internal_device_name(p['id'])
self.agent.pd.enable_subnet(self.router_id, subnet['id'],
subnet['cidr'],
interface_name,
p['mac_address'])
if (subnet['cidr'] !=
lib_constants.PROVISIONAL_IPV6_PD_PREFIX):
self.pd_subnets[subnet['id']] = subnet['cidr']
updated_cidrs = []
for p in updated_ports:
self._update_internal_ports_cache(p)
updated_cidrs += common_utils.fixed_ip_cidrs(p['fixed_ips'])
self.internal_network_updated(p)
enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
# Check if there is any pd prefix update
for p in internal_ports:
if p['id'] in (set(current_port_ids) & set(existing_port_ids)):
for subnet in p.get('subnets', []):
if ipv6_utils.is_ipv6_pd_enabled(subnet):
old_prefix = self.agent.pd.update_subnet(
self.router_id,
subnet['id'],
subnet['cidr'])
if old_prefix:
self._internal_network_updated(p, subnet['id'],
subnet['cidr'],
old_prefix,
updated_cidrs)
self.pd_subnets[subnet['id']] = subnet['cidr']
enable_ra = True
enable_ra = any(
self._port_has_ipv6_subnet(p)
for p in itertools.chain(new_ports, old_ports, updated_ports))
# Enable RA
if enable_ra:
@@ -740,7 +695,6 @@ class RouterInfo(BaseRouterInfo):
for stale_dev in stale_devs:
LOG.debug('Deleting stale internal router device: %s',
stale_dev)
self.agent.pd.remove_stale_ri_ifname(self.router_id, stale_dev)
self.driver.unplug(stale_dev,
namespace=self.ns_name,
prefix=INTERNAL_DEV_PREFIX)
@@ -868,14 +822,12 @@ class RouterInfo(BaseRouterInfo):
def external_gateway_added(self, ex_gw_port, interface_name):
preserve_ips = self._list_floating_ip_cidrs() + list(
self.centralized_port_forwarding_fip_set)
preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id))
self._external_gateway_added(
ex_gw_port, interface_name, self.ns_name, preserve_ips)
def external_gateway_updated(self, ex_gw_port, interface_name):
preserve_ips = self._list_floating_ip_cidrs() + list(
self.centralized_port_forwarding_fip_set)
preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id))
self._external_gateway_added(
ex_gw_port, interface_name, self.ns_name, preserve_ips)
@@ -904,7 +856,6 @@ class RouterInfo(BaseRouterInfo):
dev != interface_name]
for stale_dev in stale_devs:
LOG.debug('Deleting stale external router device: %s', stale_dev)
self.agent.pd.remove_gw_interface(self.router['id'])
self.driver.unplug(stale_dev,
namespace=self.ns_name,
prefix=EXTERNAL_DEV_PREFIX)
@@ -920,13 +871,10 @@ class RouterInfo(BaseRouterInfo):
if ex_gw_port:
if not self.ex_gw_port:
self.external_gateway_added(ex_gw_port, interface_name)
self.agent.pd.add_gw_interface(self.router['id'],
interface_name)
elif not self._gateway_ports_equal(ex_gw_port, self.ex_gw_port):
self.external_gateway_updated(ex_gw_port, interface_name)
elif not ex_gw_port and self.ex_gw_port:
self.external_gateway_removed(self.ex_gw_port, interface_name)
self.agent.pd.remove_gw_interface(self.router['id'])
elif not ex_gw_port and not self.ex_gw_port:
for p in self.internal_ports:
interface_name = self.get_internal_device_name(p['id'])
@@ -1225,9 +1173,6 @@ class RouterInfo(BaseRouterInfo):
iptables['filter'].add_rule(
'scope',
self.address_scope_filter_rule(device_name, mark))
for subnet_id, prefix in self.pd_subnets.items():
if prefix != lib_constants.PROVISIONAL_IPV6_PD_PREFIX:
self._process_pd_iptables_rules(prefix, subnet_id)
def process_ports_address_scope_iptables(self):
ports_scopemark = self._get_address_scope_mark()
@@ -1292,7 +1237,6 @@ class RouterInfo(BaseRouterInfo):
LOG.debug("Process delete, router %s", self.router['id'])
if self.router_namespace.exists():
self._process_internal_ports()
self.agent.pd.sync_router(self.router['id'])
self._process_external_on_delete()
else:
LOG.warning("Can't gracefully delete the router %s: "
@@ -1304,7 +1248,6 @@ class RouterInfo(BaseRouterInfo):
self.centralized_port_forwarding_fip_set = set(self.router.get(
'port_forwardings_fip_set', set()))
self._process_internal_ports()
self.agent.pd.sync_router(self.router['id'])
self.process_external()
self.process_address_scope()
# Process static routes for router

View File

@@ -42,7 +42,6 @@ def register_opts(conf):
config.register_agent_state_opts_helper(conf)
config.register_interface_opts(conf)
config.register_external_process_opts(conf)
config.register_pddriver_opts(conf)
config.register_ra_opts(conf)
config.register_availability_zone_opts_helper(conf)
ovs_conf.register_ovs_opts(conf)

View File

@@ -1,193 +0,0 @@
# Copyright 2015 Cisco Systems
# 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 io
import os
import shutil
import jinja2
from neutron_lib import constants as lib_const
from neutron_lib.utils import file as file_utils
from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.linux import external_process
from neutron.agent.linux import pd
from neutron.agent.linux import pd_driver
from neutron.agent.linux import utils
LOG = logging.getLogger(__name__)
PD_SERVICE_NAME = 'dibbler'
CONFIG_TEMPLATE = jinja2.Template("""
# Config for dibbler-client.
# Use enterprise number based duid
duid-type duid-en {{ enterprise_number }} {{ va_id }}
# 8 (Debug) is most verbose. 7 (Info) is usually the best option
log-level 8
# No automatic downlink address assignment
downlink-prefix-ifaces "none"
# Use script to notify l3_agent of assigned prefix
script {{ script_path }}
# Ask for prefix over the external gateway interface
iface {{ interface_name }} {
# Bind to generated LLA
bind-to-address {{ bind_address }}
# ask for address
{% if hint_prefix != None %}
pd 1 {
prefix {{ hint_prefix }}
}
{% else %}
pd 1
{% endif %}
}
""")
# The first line must be #!/usr/bin/env bash
SCRIPT_TEMPLATE = jinja2.Template("""#!/usr/bin/env bash
exec neutron-pd-notify $1 {{ prefix_path }} {{ l3_agent_pid }}
""")
class PDDibbler(pd_driver.PDDriverBase):
def __init__(self, router_id, subnet_id, ri_ifname):
super().__init__(router_id, subnet_id, ri_ifname)
self.requestor_id = "{}:{}:{}".format(self.router_id,
self.subnet_id,
self.ri_ifname)
self.dibbler_client_working_area = "{}/{}".format(cfg.CONF.pd_confs,
self.requestor_id)
self.prefix_path = "%s/prefix" % self.dibbler_client_working_area
self.pid_path = "%s/client.pid" % self.dibbler_client_working_area
self.converted_subnet_id = self.subnet_id.replace('-', '')
def _is_dibbler_client_running(self):
return utils.get_value_from_file(self.pid_path)
def _generate_dibbler_conf(self, ex_gw_ifname, lla, hint_prefix):
dcwa = self.dibbler_client_working_area
script_path = utils.get_conf_file_name(dcwa, 'notify', 'sh', True)
buf = io.StringIO()
buf.write('%s' % SCRIPT_TEMPLATE.render(
prefix_path=self.prefix_path,
l3_agent_pid=os.getpid()))
file_utils.replace_file(script_path, buf.getvalue())
os.chmod(script_path, 0o744)
dibbler_conf = utils.get_conf_file_name(dcwa, 'client', 'conf', False)
buf = io.StringIO()
buf.write('%s' % CONFIG_TEMPLATE.render(
enterprise_number=cfg.CONF.vendor_pen,
va_id='0x%s' % self.converted_subnet_id,
script_path='"%s/notify.sh"' % dcwa,
interface_name='"%s"' % ex_gw_ifname,
bind_address='%s' % lla,
hint_prefix=hint_prefix))
file_utils.replace_file(dibbler_conf, buf.getvalue())
return dcwa
def _spawn_dibbler(self, pmon, router_ns, dibbler_conf):
def callback(pid_file):
dibbler_cmd = ['dibbler-client',
'start',
'-w', '%s' % dibbler_conf]
return dibbler_cmd
pm = external_process.ProcessManager(
uuid=self.requestor_id,
default_cmd_callback=callback,
namespace=router_ns,
service=PD_SERVICE_NAME,
conf=cfg.CONF,
pid_file=self.pid_path)
pm.enable(reload_cfg=False)
pmon.register(uuid=self.requestor_id,
service_name=PD_SERVICE_NAME,
monitored_process=pm)
def enable(self, pmon, router_ns, ex_gw_ifname, lla, prefix=None):
LOG.debug("Enable IPv6 PD for router %s subnet %s ri_ifname %s",
self.router_id, self.subnet_id, self.ri_ifname)
if not self._is_dibbler_client_running():
dibbler_conf = self._generate_dibbler_conf(ex_gw_ifname,
lla, prefix)
self._spawn_dibbler(pmon, router_ns, dibbler_conf)
LOG.debug("dibbler client enabled for router %s subnet %s"
" ri_ifname %s",
self.router_id, self.subnet_id, self.ri_ifname)
def disable(self, pmon, router_ns, switch_over=False):
LOG.debug("Disable IPv6 PD for router %s subnet %s ri_ifname %s",
self.router_id, self.subnet_id, self.ri_ifname)
dcwa = self.dibbler_client_working_area
def callback(pid_file):
dibbler_cmd = ['dibbler-client',
'stop',
'-w', '%s' % dcwa]
return dibbler_cmd
pmon.unregister(uuid=self.requestor_id,
service_name=PD_SERVICE_NAME)
pm = external_process.ProcessManager(
uuid=self.requestor_id,
namespace=router_ns,
service=PD_SERVICE_NAME,
conf=cfg.CONF,
pid_file=self.pid_path)
if switch_over:
pm.disable()
else:
pm.disable(get_stop_command=callback)
shutil.rmtree(dcwa, ignore_errors=True)
LOG.debug("dibbler client disabled for router %s subnet %s "
"ri_ifname %s",
self.router_id, self.subnet_id, self.ri_ifname)
def get_prefix(self):
prefix = utils.get_value_from_file(self.prefix_path)
if not prefix:
prefix = lib_const.PROVISIONAL_IPV6_PD_PREFIX
return prefix
@staticmethod
def get_sync_data():
try:
requestor_ids = os.listdir(cfg.CONF.pd_confs)
except OSError:
return []
sync_data = []
requestors = (r.split(':') for r in requestor_ids if r.count(':') == 2)
for router_id, subnet_id, ri_ifname in requestors:
pd_info = pd.PDInfo()
pd_info.router_id = router_id
pd_info.subnet_id = subnet_id
pd_info.ri_ifname = ri_ifname
pd_info.driver = PDDibbler(router_id, subnet_id, ri_ifname)
pd_info.client_started = (
pd_info.driver._is_dibbler_client_running())
pd_info.prefix = pd_info.driver.get_prefix()
sync_data.append(pd_info)
return sync_data

View File

@@ -1,423 +0,0 @@
# Copyright 2015 Cisco Systems
# 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 functools
import signal
import eventlet
from neutron_lib.callbacks import events
from neutron_lib.callbacks import registry
from neutron_lib.callbacks import resources
from neutron_lib import constants as n_const
from neutron_lib.utils import runtime
from oslo_log import log as logging
from oslo_utils import netutils
from stevedore import driver
from neutron.agent.linux import ip_lib
from neutron.common import utils
LOG = logging.getLogger(__name__)
class PrefixDelegation:
def __init__(self, context, pmon, intf_driver, notifier, pd_update_cb,
agent_conf):
self.context = context
self.pmon = pmon
self.intf_driver = intf_driver
self.notifier = notifier
self.routers = {}
self.pd_update_cb = pd_update_cb
self.agent_conf = agent_conf
self.pd_dhcp_driver = driver.DriverManager(
namespace='neutron.agent.linux.pd_drivers',
name=agent_conf.prefix_delegation_driver,
).driver
registry.subscribe(add_router,
resources.ROUTER,
events.BEFORE_CREATE)
registry.subscribe(update_router,
resources.ROUTER,
events.AFTER_UPDATE)
registry.subscribe(remove_router,
resources.ROUTER,
events.AFTER_DELETE)
self._get_sync_data()
def _is_pd_primary_router(self, router):
return router['primary']
@runtime.synchronized("l3-agent-pd")
def enable_subnet(self, router_id, subnet_id, prefix, ri_ifname, mac):
router = self.routers.get(router_id)
if router is None:
return
pd_info = router['subnets'].get(subnet_id)
if not pd_info:
pd_info = PDInfo(ri_ifname=ri_ifname, mac=mac)
router['subnets'][subnet_id] = pd_info
pd_info.bind_lla = self._get_lla(mac)
if pd_info.sync:
pd_info.mac = mac
pd_info.old_prefix = prefix
elif self._is_pd_primary_router(router):
self._add_lla(router, pd_info.get_bind_lla_with_mask())
def _delete_pd(self, router, pd_info):
if not self._is_pd_primary_router(router):
return
self._delete_lla(router, pd_info.get_bind_lla_with_mask())
if pd_info.client_started:
pd_info.driver.disable(self.pmon, router['ns_name'])
@runtime.synchronized("l3-agent-pd")
def disable_subnet(self, router_id, subnet_id):
prefix_update = {}
router = self.routers.get(router_id)
if not router:
return
pd_info = router['subnets'].get(subnet_id)
if not pd_info:
return
self._delete_pd(router, pd_info)
if self._is_pd_primary_router(router):
prefix_update[subnet_id] = n_const.PROVISIONAL_IPV6_PD_PREFIX
LOG.debug("Update server with prefixes: %s", prefix_update)
self.notifier(self.context, prefix_update)
del router['subnets'][subnet_id]
@runtime.synchronized("l3-agent-pd")
def update_subnet(self, router_id, subnet_id, prefix):
router = self.routers.get(router_id)
if router is not None:
pd_info = router['subnets'].get(subnet_id)
if pd_info and pd_info.old_prefix != prefix:
old_prefix = pd_info.old_prefix
pd_info.old_prefix = prefix
pd_info.prefix = prefix
return old_prefix
@runtime.synchronized("l3-agent-pd")
def add_gw_interface(self, router_id, gw_ifname):
router = self.routers.get(router_id)
if not router:
return
router['gw_interface'] = gw_ifname
if not self._is_pd_primary_router(router):
return
prefix_update = {}
for pd_info in router['subnets'].values():
# gateway is added after internal router ports.
# If a PD is being synced, and if the prefix is available,
# send update if prefix out of sync; If not available,
# start the PD client
bind_lla_with_mask = pd_info.get_bind_lla_with_mask()
if pd_info.sync:
pd_info.sync = False
if pd_info.client_started:
if pd_info.prefix != pd_info.old_prefix:
prefix_update['subnet_id'] = pd_info.prefix
else:
self._delete_lla(router, bind_lla_with_mask)
self._add_lla(router, bind_lla_with_mask)
else:
self._add_lla(router, bind_lla_with_mask)
if prefix_update:
LOG.debug("Update server with prefixes: %s", prefix_update)
self.notifier(self.context, prefix_update)
def delete_router_pd(self, router):
if not self._is_pd_primary_router(router):
return
prefix_update = {}
for subnet_id, pd_info in router['subnets'].items():
self._delete_lla(router, pd_info.get_bind_lla_with_mask())
if pd_info.client_started:
pd_info.driver.disable(self.pmon, router['ns_name'])
pd_info.prefix = None
pd_info.client_started = False
prefix = n_const.PROVISIONAL_IPV6_PD_PREFIX
prefix_update[subnet_id] = prefix
if prefix_update:
LOG.debug("Update server with prefixes: %s", prefix_update)
self.notifier(self.context, prefix_update)
@runtime.synchronized("l3-agent-pd")
def remove_gw_interface(self, router_id):
router = self.routers.get(router_id)
if router is not None:
router['gw_interface'] = None
self.delete_router_pd(router)
@runtime.synchronized("l3-agent-pd")
def get_preserve_ips(self, router_id):
preserve_ips = []
router = self.routers.get(router_id)
if router is not None:
for pd_info in router['subnets'].values():
preserve_ips.append(pd_info.get_bind_lla_with_mask())
return preserve_ips
@runtime.synchronized("l3-agent-pd")
def sync_router(self, router_id):
router = self.routers.get(router_id)
if router is not None and router['gw_interface'] is None:
self.delete_router_pd(router)
@runtime.synchronized("l3-agent-pd")
def remove_stale_ri_ifname(self, router_id, stale_ifname):
router = self.routers.get(router_id)
if router is not None:
subnet_to_delete = None
for subnet_id, pd_info in router['subnets'].items():
if pd_info.ri_ifname == stale_ifname:
self._delete_pd(router, pd_info)
subnet_to_delete = subnet_id
break
if subnet_to_delete:
del router['subnets'][subnet_to_delete]
@staticmethod
def _get_lla(mac):
lla = netutils.get_ipv6_addr_by_EUI64(n_const.IPv6_LLA_PREFIX,
mac)
return lla
def _get_llas(self, gw_ifname, ns_name):
try:
return self.intf_driver.get_ipv6_llas(gw_ifname, ns_name)
except RuntimeError:
# The error message was printed as part of the driver call
# This could happen if the gw_ifname was removed
# simply return and exit the thread
return
def _add_lla(self, router, lla_with_mask):
if router['gw_interface']:
try:
self.intf_driver.add_ipv6_addr(router['gw_interface'],
lla_with_mask,
router['ns_name'],
'link')
# There is a delay before the LLA becomes active.
# This is because the kernel runs DAD to make sure LLA
# uniqueness
# Spawn a thread to wait for the interface to be ready
self._spawn_lla_thread(router['gw_interface'],
router['ns_name'],
lla_with_mask)
except ip_lib.IpAddressAlreadyExists:
pass
def _spawn_lla_thread(self, gw_ifname, ns_name, lla_with_mask):
eventlet.spawn_n(self._ensure_lla_task,
gw_ifname,
ns_name,
lla_with_mask)
def _delete_lla(self, router, lla_with_mask):
if lla_with_mask and router['gw_interface']:
try:
self.intf_driver.delete_ipv6_addr(router['gw_interface'],
lla_with_mask,
router['ns_name'])
except RuntimeError:
# Ignore error if the lla doesn't exist
pass
def _ensure_lla_task(self, gw_ifname, ns_name, lla_with_mask):
# It would be insane for taking so long unless DAD test failed
# In that case, the subnet would never be assigned a prefix.
utils.wait_until_true(functools.partial(self._lla_available,
gw_ifname,
ns_name,
lla_with_mask),
timeout=n_const.LLA_TASK_TIMEOUT,
sleep=2)
def _lla_available(self, gw_ifname, ns_name, lla_with_mask):
llas = self._get_llas(gw_ifname, ns_name)
if self._is_lla_active(lla_with_mask, llas):
LOG.debug("LLA %s is active now", lla_with_mask)
self.pd_update_cb()
return True
@staticmethod
def _is_lla_active(lla_with_mask, llas):
for lla in llas:
if lla_with_mask == lla['cidr']:
return not lla['tentative']
return False
@runtime.synchronized("l3-agent-pd")
def process_ha_state(self, router_id, primary):
router = self.routers.get(router_id)
if router is None or router['primary'] == primary:
return
router['primary'] = primary
if primary:
for pd_info in router['subnets'].values():
bind_lla_with_mask = pd_info.get_bind_lla_with_mask()
self._add_lla(router, bind_lla_with_mask)
else:
for pd_info in router['subnets'].values():
self._delete_lla(router, pd_info.get_bind_lla_with_mask())
if pd_info.client_started:
pd_info.driver.disable(self.pmon,
router['ns_name'],
switch_over=True)
pd_info.client_started = False
@runtime.synchronized("l3-agent-pd")
def process_prefix_update(self):
LOG.debug("Processing IPv6 PD Prefix Update")
prefix_update = {}
for router_id, router in self.routers.items():
if not (self._is_pd_primary_router(router) and
router['gw_interface']):
continue
llas = None
for subnet_id, pd_info in router['subnets'].items():
if pd_info.client_started:
prefix = pd_info.driver.get_prefix()
if prefix != pd_info.prefix:
pd_info.prefix = prefix
prefix_update[subnet_id] = prefix
else:
if not llas:
llas = self._get_llas(router['gw_interface'],
router['ns_name'])
if self._is_lla_active(pd_info.get_bind_lla_with_mask(),
llas):
if not pd_info.driver:
pd_info.driver = self.pd_dhcp_driver(
router_id, subnet_id, pd_info.ri_ifname)
prefix = None
if (pd_info.prefix !=
n_const.PROVISIONAL_IPV6_PD_PREFIX):
prefix = pd_info.prefix
pd_info.driver.enable(self.pmon, router['ns_name'],
router['gw_interface'],
pd_info.bind_lla,
prefix)
pd_info.client_started = True
if prefix_update:
LOG.debug("Update server with prefixes: %s", prefix_update)
self.notifier(self.context, prefix_update)
def after_start(self):
LOG.debug('SIGUSR1 signal handler set')
signal.signal(signal.SIGUSR1, self._handle_sigusr1)
def _handle_sigusr1(self, signum, frame):
"""Update PD on receiving SIGUSR1.
The external DHCPv6 client uses SIGUSR1 to notify agent
of prefix changes.
"""
self.pd_update_cb()
def _get_sync_data(self):
sync_data = self.pd_dhcp_driver.get_sync_data()
for pd_info in sync_data:
router_id = pd_info.router_id
if not self.routers.get(router_id):
self.routers[router_id] = {'primary': True,
'gw_interface': None,
'ns_name': None,
'subnets': {}}
new_pd_info = PDInfo(pd_info=pd_info)
subnets = self.routers[router_id]['subnets']
subnets[pd_info.subnet_id] = new_pd_info
@runtime.synchronized("l3-agent-pd")
def remove_router(resource, event, l3_agent, payload):
router_id = payload.resource_id
router = l3_agent.pd.routers.get(router_id)
l3_agent.pd.delete_router_pd(router)
del l3_agent.pd.routers[router_id]['subnets']
del l3_agent.pd.routers[router_id]
def get_router_entry(ns_name, primary):
return {'primary': primary,
'gw_interface': None,
'ns_name': ns_name,
'subnets': {}}
@runtime.synchronized("l3-agent-pd")
def add_router(resource, event, l3_agent, payload):
added_router = payload.latest_state
router = l3_agent.pd.routers.get(added_router.router_id)
gw_ns_name = added_router.get_gw_ns_name()
primary = added_router.is_router_primary()
if not router:
l3_agent.pd.routers[added_router.router_id] = (
get_router_entry(gw_ns_name, primary))
else:
# This will happen during l3 agent restart
router['ns_name'] = gw_ns_name
router['primary'] = primary
@runtime.synchronized("l3-agent-pd")
def update_router(resource, event, l3_agent, payload):
updated_router = payload.latest_state
router = l3_agent.pd.routers.get(updated_router.router_id)
if not router:
LOG.exception("Router to be updated is not in internal routers "
"list: %s", updated_router.router_id)
else:
router['ns_name'] = updated_router.get_gw_ns_name()
class PDInfo:
"""A class to simplify storing and passing of information relevant to
Prefix Delegation operations for a given subnet.
"""
def __init__(self, pd_info=None, ri_ifname=None, mac=None):
if pd_info is None:
self.prefix = n_const.PROVISIONAL_IPV6_PD_PREFIX
self.old_prefix = n_const.PROVISIONAL_IPV6_PD_PREFIX
self.ri_ifname = ri_ifname
self.mac = mac
self.bind_lla = None
self.sync = False
self.driver = None
self.client_started = False
else:
self.prefix = pd_info.prefix
self.old_prefix = None
self.ri_ifname = pd_info.ri_ifname
self.mac = None
self.bind_lla = None
self.sync = True
self.driver = pd_info.driver
self.client_started = pd_info.client_started
def get_bind_lla_with_mask(self):
bind_lla_with_mask = '%s/64' % self.bind_lla
return bind_lla_with_mask

View File

@@ -1,53 +0,0 @@
# Copyright 2015 Cisco Systems
# 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 abc
from neutron.conf.agent import common as agent_conf
agent_conf.register_pddriver_opts()
class PDDriverBase(metaclass=abc.ABCMeta):
def __init__(self, router_id, subnet_id, ri_ifname):
self.router_id = router_id
self.subnet_id = subnet_id
self.ri_ifname = ri_ifname
@abc.abstractmethod
def enable(self, pmon, router_ns, ex_gw_ifname, lla):
"""Enable IPv6 Prefix Delegation for this PDDriver on the given
external interface, with the given link local address
"""
@abc.abstractmethod
def disable(self, pmon, router_ns):
"""Disable IPv6 Prefix Delegation for this PDDriver
"""
@abc.abstractmethod
def get_prefix(self):
"""Get the current assigned prefix for this PDDriver from the PD agent.
If no prefix is currently assigned, return
neutron_lib.constants.PROVISIONAL_IPV6_PD_PREFIX
"""
@staticmethod
@abc.abstractmethod
def get_sync_data():
"""Get the latest router_id, subnet_id, and ri_ifname from the PD agent
so that the PDDriver can be kept up to date
"""

View File

@@ -48,7 +48,8 @@ class L3RpcCallback:
# 1.10 Added update_all_ha_network_port_statuses
# 1.11 Added get_host_ha_router_count
# 1.12 Added get_networks
target = oslo_messaging.Target(version='1.12')
# 1.13 Removed process_prefix_update
target = oslo_messaging.Target(version='1.13')
@property
def plugin(self):
@@ -327,17 +328,6 @@ class L3RpcCallback:
LOG.debug('Updating HA routers states on host %s: %s', host, states)
self.l3plugin.update_routers_states(context, states, host)
def process_prefix_update(self, context, **kwargs):
subnets = kwargs.get('subnets')
updated_subnets = []
for subnet_id, prefix in subnets.items():
updated_subnets.append(
self.plugin.update_subnet(context,
subnet_id,
{'subnet': {'cidr': prefix}}))
return updated_subnets
@db_api.retry_db_errors
def delete_agent_gateway_port(self, context, **kwargs):
"""Delete Floatingip agent gateway port."""

View File

@@ -1,38 +0,0 @@
# Copyright (c) 2015 Cisco Systems.
# 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 os
import signal
import sys
from neutron_lib.utils import file as file_utils
def main():
"""Expected arguments:
sys.argv[1] - The add/update/delete operation performed by the PD agent
sys.argv[2] - The file where the new prefix should be written
sys.argv[3] - The process ID of the L3 agent to be notified of this change
"""
operation = sys.argv[1]
prefix_fname = sys.argv[2]
agent_pid = sys.argv[3]
prefix = os.getenv('PREFIX1', "::")
if operation in ["add", "update"]:
file_utils.replace_file(prefix_fname, "%s/64" % prefix)
elif operation == "delete":
file_utils.replace_file(prefix_fname, "::/64")
os.kill(int(agent_pid), signal.SIGUSR1)

View File

@@ -57,7 +57,6 @@ DNSMASQ_VERSION_DHCP_RELEASE6 = '2.76'
DNSMASQ_VERSION_HOST_ADDR6_LIST = '2.81'
DNSMASQ_VERSION_SEGFAULT_ISSUE = '2.86'
DIRECT_PORT_QOS_MIN_OVS_VERSION = '2.11'
MINIMUM_DIBBLER_VERSION = '1.0.1'
CONNTRACK_GRE_MODULE = 'nf_conntrack_proto_gre'
OVN_NB_DB_SCHEMA_GATEWAY_CHASSIS = '5.7.0'
OVN_NB_DB_SCHEMA_PORT_GROUP = '5.11.0'
@@ -539,22 +538,6 @@ def conntrack_supported():
return False
def get_minimal_dibbler_version_supported():
return MINIMUM_DIBBLER_VERSION
def dibbler_version_supported():
try:
cmd = ['dibbler-client',
'help']
out = agent_utils.execute(cmd)
return '-w' in out
except (OSError, RuntimeError, IndexError, ValueError) as e:
LOG.debug("Exception while checking minimal dibbler version. "
"Exception: %s", e)
return False
def _fix_ip_nonlocal_bind_root_value(original_value):
current_value = ip_lib.get_ip_nonlocal_bind(namespace=None)
if current_value != original_value:

View File

@@ -163,15 +163,6 @@ def check_keepalived_garp_on_sighup_support():
return result
def check_dibbler_version():
result = checks.dibbler_version_supported()
if not result:
LOG.error('The installed version of dibbler-client is too old. '
'Please update to at least version %s.',
checks.get_minimal_dibbler_version_supported())
return result
def check_nova_notify():
result = checks.nova_notify_supported()
if not result:
@@ -406,10 +397,6 @@ OPTS = [
check_keepalived_garp_on_sighup_support,
help=_('Check keepalived support sending garp on '
'SIGHUP.')),
BoolOptCallback('dibbler_version', check_dibbler_version,
help=_('Check minimal dibbler version'),
deprecated_for_removal=True,
deprecated_since='Pike'),
BoolOptCallback('ipset_installed', check_ipset,
help=_('Check ipset installation')),
BoolOptCallback('ip6tables_installed', check_ip6tables,

View File

@@ -29,24 +29,6 @@ EXTERNAL_PROCESS_OPTS = [
]
PD_OPTS = [
cfg.StrOpt('pd_dhcp_driver',
default='dibbler',
help=_('Service to handle DHCPv6 Prefix delegation.')),
]
PD_DRIVER_OPTS = [
cfg.StrOpt('pd_confs',
default='$state_path/pd',
help=_('Location to store IPv6 Prefix Delegation files.')),
cfg.StrOpt('vendor_pen',
default='8888',
help=_("A decimal value as Vendor's Registered Private "
"Enterprise Number as required by RFC3315 DUID-EN.")),
]
INTERFACE_OPTS = [
cfg.BoolOpt('ovs_use_veth',
default=False,
@@ -174,14 +156,6 @@ def register_external_process_opts(cfg=cfg.CONF):
cfg.register_opts(EXTERNAL_PROCESS_OPTS)
def register_pd_opts(cfg=cfg.CONF):
cfg.register_opts(PD_OPTS)
def register_pddriver_opts(cfg=cfg.CONF):
cfg.register_opts(PD_DRIVER_OPTS)
def register_interface_opts(cfg=cfg.CONF):
cfg.register_opts(INTERFACE_OPTS)

View File

@@ -74,13 +74,6 @@ OPTS = [
"next-hop using a global unique address (GUA) is "
"desired, it needs to be done via a subnet allocated "
"to the network and not through this parameter.")),
cfg.StrOpt('prefix_delegation_driver',
default='dibbler',
help=_('Driver used for IPv6 Prefix Delegation. This needs to '
'be an entry point defined in the '
'neutron.agent.linux.pd_drivers namespace. See '
'setup.cfg for entry points included with the Neutron '
'source code.')),
cfg.BoolOpt('enable_metadata_proxy', default=True,
help=_("Allow running metadata proxy.")),
cfg.StrOpt('metadata_access_mark',

View File

@@ -68,8 +68,7 @@ core_opts = [
help=_("Maximum number of host routes per subnet")),
cfg.BoolOpt('ipv6_pd_enabled', default=False,
help=_("Warning: This feature is experimental with low test "
"coverage, and the Dibbler client which is used for "
"this feature is no longer maintained! "
"coverage. "
"Enables IPv6 Prefix Delegation for automatic subnet "
"CIDR allocation. "
"Set to True to enable IPv6 Prefix Delegation for "
@@ -81,8 +80,9 @@ core_opts = [
"the default IPv6 subnetpool."),
deprecated_for_removal=True,
deprecated_since='2023.2',
deprecated_reason=("The Dibbler client used for this feature "
"is no longer maintained. See LP#1916428"),
deprecated_reason=(
"There is no reference implementation for the feature for "
"any of in-tree drivers."),
),
cfg.IntOpt('dhcp_lease_duration', default=86400,
help=_("DHCP lease duration (in seconds). Use -1 to tell "

View File

@@ -21,7 +21,7 @@ experimental_opts = [
cfg.BoolOpt(EXPERIMENTAL_IPV6_PD,
default=False,
help=_('Enable execution of the experimental IPv6 Prefix '
'Delegation functionality in the L3 agent.')),
'Delegation functionality in the plugin.')),
]

View File

@@ -1004,7 +1004,7 @@ class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon,
if new_cidr and ipv6_utils.is_ipv6_pd_enabled(s):
# This is an ipv6 prefix delegation-enabled subnet being given an
# updated cidr by the process_prefix_update RPC
# updated cidr by the plugin.
s['cidr'] = netaddr.IPNetwork(new_cidr, s['ip_version'])
# Update gateway_ip and allocation pools based on new cidr
s['gateway_ip'] = utils.get_first_host_ip(

View File

@@ -248,7 +248,6 @@ def list_l3_agent_opts():
neutron.conf.agent.l3.config.OPTS,
neutron.conf.service.SERVICE_OPTS,
neutron.conf.agent.l3.ha.OPTS,
neutron.conf.agent.common.PD_DRIVER_OPTS,
neutron.conf.agent.common.RA_OPTS)
),
('agent',

View File

@@ -38,9 +38,6 @@ class SanityTestCase(base.BaseLoggingTestCase):
def test_dnsmasq_version(self):
checks.dnsmasq_version_supported()
def test_dibbler_version(self):
checks.dibbler_version_supported()
def test_ipset_support(self):
checks.ipset_supported()

View File

@@ -50,11 +50,9 @@ from neutron.agent.l3 import link_local_allocator as lla
from neutron.agent.l3 import namespace_manager
from neutron.agent.l3 import namespaces
from neutron.agent.l3 import router_info as l3router
from neutron.agent.linux import dibbler
from neutron.agent.linux import interface
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.agent.linux import pd
from neutron.agent.linux import ra
from neutron.agent.linux import utils as linux_utils
from neutron.agent.metadata import driver as metadata_driver
@@ -91,12 +89,10 @@ class BasicRouterOperationsFramework(base.BaseTestCase):
agent_config.register_availability_zone_opts_helper(self.conf)
agent_config.register_interface_opts(self.conf)
agent_config.register_external_process_opts(self.conf)
agent_config.register_pd_opts(self.conf)
agent_config.register_ra_opts(self.conf)
self.conf.set_override('interface_driver',
'neutron.agent.linux.interface.NullDriver')
self.conf.set_override('state_path', cfg.CONF.state_path)
self.conf.set_override('pd_dhcp_driver', '')
# Enable conntrackd support for tests for it to get full test coverage
self.conf.set_override('ha_conntrackd_enabled', True)
@@ -3423,678 +3419,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
expected += "%s " % dns
self.assertIn(expected, self.utils_replace_file.call_args[0][1])
def _pd_expected_call_external_process(self, requestor, ri,
enable=True, ha=False):
expected_calls = []
if enable:
expected_calls.append(mock.call(uuid=requestor,
service='dibbler',
default_cmd_callback=mock.ANY,
namespace=ri.ns_name,
conf=mock.ANY,
pid_file=mock.ANY))
expected_calls.append(mock.call().enable(reload_cfg=False))
else:
expected_calls.append(mock.call(uuid=requestor,
service='dibbler',
namespace=ri.ns_name,
conf=mock.ANY,
pid_file=mock.ANY))
# in the HA switchover case, disable is called without arguments
if ha:
expected_calls.append(mock.call().disable())
else:
expected_calls.append(mock.call().disable(
get_stop_command=mock.ANY))
return expected_calls
def _pd_setup_agent_router(self, enable_ha=False):
router = l3_test_common.prepare_router_data()
agent = l3_agent.L3NATAgent(HOSTNAME, self.conf)
agent._router_added(router['id'], router)
# Make sure radvd monitor is created
ri = agent.router_info[router['id']]
ri.iptables_manager.ipv6['mangle'] = mock.MagicMock()
ri._process_pd_iptables_rules = mock.MagicMock()
if not ri.radvd:
ri.radvd = ra.DaemonMonitor(router['id'],
ri.ns_name,
agent.process_monitor,
ri.get_internal_device_name,
self.conf)
if enable_ha:
agent.pd.routers[router['id']]['primary'] = False
return agent, router, ri
def _pd_remove_gw_interface(self, intfs, agent, ri):
expected_pd_update = {}
expected_calls = []
for intf in intfs:
requestor_id = self._pd_get_requestor_id(intf, ri)
expected_calls += (self._pd_expected_call_external_process(
requestor_id, ri, False))
for subnet in intf['subnets']:
expected_pd_update[subnet['id']] = (
lib_constants.PROVISIONAL_IPV6_PD_PREFIX)
# Implement the prefix update notifier
# Keep track of the updated prefix
self.pd_update = {}
def pd_notifier(context, prefix_update):
self.pd_update = prefix_update
for subnet_id, prefix in prefix_update.items():
for intf in intfs:
for subnet in intf['subnets']:
if subnet['id'] == subnet_id:
# Update the prefix
subnet['cidr'] = prefix
break
# Remove the gateway interface
agent.pd.notifier = pd_notifier
agent.pd.remove_gw_interface(ri.router['id'])
self._pd_assert_dibbler_calls(
expected_calls,
self.external_process.mock_calls[-len(expected_calls):])
self.assertEqual(expected_pd_update, self.pd_update)
def _pd_remove_interfaces(self, intfs, agent, ri):
expected_pd_update = []
expected_calls = []
for intf in intfs:
# Remove the router interface
ri.router[lib_constants.INTERFACE_KEY].remove(intf)
requestor_id = self._pd_get_requestor_id(intf, ri)
expected_calls += (self._pd_expected_call_external_process(
requestor_id, ri, False))
for subnet in intf['subnets']:
expected_pd_update += [
{subnet['id']: lib_constants.PROVISIONAL_IPV6_PD_PREFIX}]
# Implement the prefix update notifier
# Keep track of the updated prefix
self.pd_update = []
def pd_notifier(context, prefix_update):
self.pd_update.append(prefix_update)
for intf in intfs:
for subnet in intf['subnets']:
if subnet['id'] in prefix_update:
# Update the prefix
subnet['cidr'] = prefix_update[subnet['id']]
# Process the router for removed interfaces
agent.pd.notifier = pd_notifier
ri.process()
# The number of external process calls takes radvd into account.
# This is because there is no ipv6 interface any more after removing
# the interfaces, and radvd will be killed because of that
self._pd_assert_dibbler_calls(
expected_calls,
self.external_process.mock_calls[-len(expected_calls) - 2:])
self._pd_assert_radvd_calls(ri, False)
self.assertEqual(expected_pd_update, self.pd_update)
def _pd_get_requestor_id(self, intf, ri):
ifname = ri.get_internal_device_name(intf['id'])
for subnet in intf['subnets']:
return dibbler.PDDibbler(ri.router['id'],
subnet['id'], ifname).requestor_id
def _pd_assert_dibbler_calls(self, expected, actual):
'''Check the external process calls for dibbler are expected
in the case of multiple pd-enabled router ports, the exact sequence
of these calls are not deterministic. It's known, though, that each
external_process call is followed with either an enable() or disable()
'''
num_ext_calls = len(expected) // 2
expected_ext_calls = []
actual_ext_calls = []
expected_action_calls = []
actual_action_calls = []
for c in range(num_ext_calls):
expected_ext_calls.append(expected[c * 2])
actual_ext_calls.append(actual[c * 2])
expected_action_calls.append(expected[c * 2 + 1])
actual_action_calls.append(actual[c * 2 + 1])
self.assertEqual(expected_action_calls, actual_action_calls)
for exp in expected_ext_calls:
for act in actual_ext_calls:
if exp == act:
break
else:
msg = "Unexpected dibbler external process call."
self.fail(msg)
def _pd_assert_radvd_calls(self, ri, enable=True):
exp_calls = self._radvd_expected_call_external_process(ri, enable)
self.assertEqual(exp_calls,
self.external_process.mock_calls[-len(exp_calls):])
def _pd_assert_update_subnet_calls(self, router_id, intfs,
mock_pd_update_subnet):
for intf in intfs:
mock_pd_update_subnet.assert_any_call(router_id,
intf['subnets'][0]['id'],
intf['subnets'][0]['cidr'])
def _pd_get_prefixes(self, agent, ri,
existing_intfs, new_intfs, mock_get_prefix):
# First generate the prefixes that will be used for each interface
prefixes = {}
expected_pd_update = {}
expected_calls = []
last_prefix = ''
for ifno, intf in enumerate(existing_intfs + new_intfs):
requestor_id = self._pd_get_requestor_id(intf, ri)
prefixes[requestor_id] = "2001:db8:%d::/64" % ifno
last_prefix = prefixes[requestor_id]
if intf in new_intfs:
subnet_id = (intf['subnets'][0]['id'] if intf['subnets']
else None)
expected_pd_update[subnet_id] = prefixes[requestor_id]
expected_calls += (
self._pd_expected_call_external_process(requestor_id, ri))
# Implement the prefix update notifier
# Keep track of the updated prefix
self.pd_update = {}
def pd_notifier(context, prefix_update):
self.pd_update = prefix_update
for subnet_id, prefix in prefix_update.items():
gateway_ip = '%s1' % netaddr.IPNetwork(prefix).network
for intf in new_intfs:
for fip in intf['fixed_ips']:
if fip['subnet_id'] == subnet_id:
fip['ip_address'] = gateway_ip
for subnet in intf['subnets']:
if subnet['id'] == subnet_id:
# Update the prefix
subnet['cidr'] = prefix
subnet['gateway_ip'] = gateway_ip
break
# Start the dibbler client
agent.pd.notifier = pd_notifier
agent.pd.process_prefix_update()
# Get the prefix and check that the neutron server is notified
def get_prefix(pdo):
key = '{}:{}:{}'.format(
pdo.router_id, pdo.subnet_id, pdo.ri_ifname)
return prefixes[key]
mock_get_prefix.side_effect = get_prefix
agent.pd.process_prefix_update()
# Make sure that the updated prefixes are expected
self._pd_assert_dibbler_calls(
expected_calls,
self.external_process.mock_calls[-len(expected_calls):])
self.assertEqual(expected_pd_update, self.pd_update)
return last_prefix
def _pd_verify_update_results(self, ri, pd_intfs, mock_pd_update_subnet):
# verify router port initialized
for intf in pd_intfs:
self.mock_driver.init_router_port.assert_any_call(
ri.get_internal_device_name(intf['id']),
ip_cidrs=l3router.common_utils.fixed_ip_cidrs(
intf['fixed_ips']),
namespace=ri.ns_name)
# verify that subnet is updated in PD
self._pd_assert_update_subnet_calls(ri.router['id'], pd_intfs,
mock_pd_update_subnet)
# Check that radvd is started
self._pd_assert_radvd_calls(ri)
def _pd_add_gw_interface(self, agent, ri):
gw_ifname = ri.get_external_device_name(ri.router['gw_port']['id'])
agent.pd.add_gw_interface(ri.router['id'], gw_ifname)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_have_subnet(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Add one pd-enabled subnet that has already been assigned
'''
prefix = '2001:db8:10::/64'
# Initial setup
agent, router, ri = self._pd_setup_agent_router()
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router, prefix=prefix)
ri.process()
pd_intfs = l3_test_common.get_assigned_pd_interfaces(router)
subnet_id = pd_intfs[0]['subnets'][0]['id']
# Check that _process_pd_iptables_rules() is called correctly
self.assertEqual({subnet_id: prefix}, ri.pd_subnets)
ri._process_pd_iptables_rules.assert_called_once_with(prefix,
subnet_id)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_add_remove_subnet(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Add and remove one pd-enabled subnet
Remove the interface by deleting it from the router
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router()
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
ri.process()
# Provisional PD prefix on startup, so nothing cached
self.assertEqual({}, ri.pd_subnets)
# No client should be started since there is no gateway port
self.assertFalse(self.external_process.call_count)
self.assertFalse(mock_get_prefix.call_count)
# Add the gateway interface
self._pd_add_gw_interface(agent, ri)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
subnet_id = pd_intfs[0]['subnets'][0]['id']
# Get one prefix
prefix = self._pd_get_prefixes(agent, ri, [],
pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
# Check that _process_pd_iptables_rules() is called correctly
self.assertEqual({subnet_id: prefix}, ri.pd_subnets)
ri._process_pd_iptables_rules.assert_called_once_with(prefix,
subnet_id)
# Now remove the interface
self._pd_remove_interfaces(pd_intfs, agent, ri)
self.assertEqual({}, ri.pd_subnets)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_remove_gateway(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Add one pd-enabled subnet and remove the gateway port
Remove the gateway port and check the prefix is removed
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router()
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
ri.process()
# Add the gateway interface
self._pd_add_gw_interface(agent, ri)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Get one prefix
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
# Now remove the gw interface
self._pd_remove_gw_interface(pd_intfs, agent, ri)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_add_remove_2_subnets(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Add and remove two pd-enabled subnets
Remove the interfaces by deleting them from the router
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router()
# Create 2 pd-enabled subnets and add router interfaces
l3_test_common.router_append_pd_enabled_subnet(router, count=2)
ri.process()
# No client should be started
self.assertFalse(self.external_process.call_count)
self.assertFalse(mock_get_prefix.call_count)
# Add the gateway interface
self._pd_add_gw_interface(agent, ri)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Get prefixes
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
# Now remove the interface
self._pd_remove_interfaces(pd_intfs, agent, ri)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_remove_gateway_2_subnets(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Add one pd-enabled subnet, followed by adding another one
Remove the gateway port and check the prefix is removed
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router()
# Add the gateway interface
self._pd_add_gw_interface(agent, ri)
# Create 1 pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router, count=1)
ri.process()
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Get prefixes
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
# Now add another interface
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(update_router, count=1)
ri.process()
update_router_2 = copy.deepcopy(update_router)
pd_intfs1 = l3_test_common.get_unassigned_pd_interfaces(
update_router_2)
# Get prefixes
self._pd_get_prefixes(agent, ri, pd_intfs, pd_intfs1, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router_2
ri.process()
self._pd_verify_update_results(ri, pd_intfs1, mock_pd_update_subnet)
# Now remove the gw interface
self._pd_remove_gw_interface(pd_intfs + pd_intfs1, agent, ri)
@mock.patch.object(l3router.RouterInfo, 'enable_radvd')
@mock.patch.object(pd.PrefixDelegation, '_add_lla')
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_ha_standby(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet,
mock_add_lla, mock_enable_radvd):
'''Test HA in the standby router
The intent is to test the PD code with HA. To avoid unnecessary
complexities, use the regular router.
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router(enable_ha=True)
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
self._pd_add_gw_interface(agent, ri)
ri.process()
self.assertFalse(mock_add_lla.called)
# No client should be started since it's standby router
agent.pd.process_prefix_update()
self.assertFalse(self.external_process.called)
self.assertFalse(mock_get_prefix.called)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.assign_prefix_for_pd_interfaces(
update_router)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_assert_update_subnet_calls(router['id'], pd_intfs,
mock_pd_update_subnet)
# No client should be started since it's standby router
agent.pd.process_prefix_update()
self.assertFalse(self.external_process.called)
self.assertFalse(mock_get_prefix.called)
@mock.patch.object(pd.PrefixDelegation, '_add_lla')
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_ha_active(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet,
mock_add_lla):
'''Test HA in the active router
The intent is to test the PD code with HA. To avoid unnecessary
complexities, use the regular router.
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router(enable_ha=True)
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
self._pd_add_gw_interface(agent, ri)
ri.process()
self.assertFalse(mock_add_lla.called)
# No client should be started since it's standby router
agent.pd.process_prefix_update()
self.assertFalse(self.external_process.called)
self.assertFalse(mock_get_prefix.called)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Turn the router to be active
agent.pd.process_ha_state(router['id'], True)
# Get prefixes
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_ha_switchover(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Test HA in the active router
The intent is to test the PD code with HA. To avoid unnecessary
complexities, use the regular router.
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router(enable_ha=True)
# Turn the router to be active
agent.pd.process_ha_state(router['id'], True)
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
self._pd_add_gw_interface(agent, ri)
ri.process()
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Get prefixes
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
# Turn the router to be standby
agent.pd.process_ha_state(router['id'], False)
expected_calls = []
for intf in pd_intfs:
requestor_id = self._pd_get_requestor_id(intf, ri)
expected_calls += (self._pd_expected_call_external_process(
requestor_id, ri, False, ha=True))
self._pd_assert_dibbler_calls(
expected_calls,
self.external_process.mock_calls[-len(expected_calls):])
@mock.patch.object(pd.PrefixDelegation, 'update_subnet')
@mock.patch.object(dibbler.PDDibbler, 'get_prefix', autospec=True)
@mock.patch.object(dibbler.os, 'getpid', return_value=1234)
@mock.patch.object(pd.PrefixDelegation, '_is_lla_active',
return_value=True)
@mock.patch.object(dibbler.os, 'chmod')
@mock.patch.object(dibbler.shutil, 'rmtree')
@mock.patch.object(pd.PrefixDelegation, '_get_sync_data')
def test_pd_lla_already_exists(self, mock1, mock2, mock3, mock4,
mock_getpid, mock_get_prefix,
mock_pd_update_subnet):
'''Test HA in the active router
The intent is to test the PD code with HA. To avoid unnecessary
complexities, use the regular router.
'''
# Initial setup
agent, router, ri = self._pd_setup_agent_router(enable_ha=True)
agent.pd.intf_driver = mock.MagicMock()
agent.pd.intf_driver.add_ipv6_addr.side_effect = (
ip_lib.IpAddressAlreadyExists())
# Create one pd-enabled subnet and add router interface
l3_test_common.router_append_pd_enabled_subnet(router)
self._pd_add_gw_interface(agent, ri)
ri.process()
# No client should be started since it's standby router
agent.pd.process_prefix_update()
self.assertFalse(self.external_process.called)
self.assertFalse(mock_get_prefix.called)
update_router = copy.deepcopy(router)
pd_intfs = l3_test_common.get_unassigned_pd_interfaces(update_router)
# Turn the router to be active
agent.pd.process_ha_state(router['id'], True)
# Get prefixes
self._pd_get_prefixes(agent, ri, [], pd_intfs, mock_get_prefix)
# Update the router with the new prefix
ri.router = update_router
ri.process()
self._pd_verify_update_results(ri, pd_intfs, mock_pd_update_subnet)
@mock.patch.object(dibbler.os, 'chmod')
def test_pd_generate_dibbler_conf(self, mock_chmod):
pddib = dibbler.PDDibbler("router_id", "subnet-id", "ifname")
pddib._generate_dibbler_conf("ex_gw_ifname",
"fe80::f816:3eff:fef5:a04e", None)
expected = 'bind-to-address fe80::f816:3eff:fef5:a04e\n'\
'# ask for address\n \n pd 1\n \n}'
self.assertIn(expected, self.utils_replace_file.call_args[0][1])
pddib._generate_dibbler_conf("ex_gw_ifname",
"fe80::f816:3eff:fef5:a04e",
"2001:db8:2c50:2026::/64")
expected = 'bind-to-address fe80::f816:3eff:fef5:a04e\n'\
'# ask for address\n \n pd 1 '\
'{\n prefix 2001:db8:2c50:2026::/64\n }\n \n}'
self.assertIn(expected, self.utils_replace_file.call_args[0][1])
def _verify_address_scopes_iptables_rule(self, mock_iptables_manager):
filter_calls = [mock.call.add_chain('scope'),
mock.call.add_rule('FORWARD', '-j $scope')]

View File

@@ -191,28 +191,6 @@ class TestRouterInfo(base.BaseTestCase):
expected = {'delete': [('110.100.30.0/24', '10.100.10.30')]}
self._check_agent_method_called(ri, expected)
def test__process_pd_iptables_rules(self):
subnet_id = _uuid()
ex_gw_port = {'id': _uuid()}
prefix = '2001:db8:cafe::/64'
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
ipv6_mangle = ri.iptables_manager.ipv6['mangle'] = mock.MagicMock()
ri.get_ex_gw_port = mock.Mock(return_value=ex_gw_port)
ri.get_external_device_name = mock.Mock(return_value='fake_device')
ri.get_address_scope_mark_mask = mock.Mock(return_value='fake_mark')
ri._process_pd_iptables_rules(prefix, subnet_id)
mangle_rule = '-d %s ' % prefix
mangle_rule += ri.address_scope_mangle_rule('fake_device', 'fake_mark')
ipv6_mangle.add_rule.assert_called_once_with(
'scope',
mangle_rule,
tag='prefix_delegation_%s' % subnet_id)
def test_add_ports_address_scope_iptables(self):
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
port = {

View File

@@ -1,136 +0,0 @@
# 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.
from unittest import mock
from neutron_lib.callbacks import events
from neutron.agent.l3 import dvr_edge_router
from neutron.agent.l3 import dvr_local_router
from neutron.agent.l3 import legacy_router
from neutron.agent.linux import pd
from neutron.tests import base as tests_base
class FakeRouter:
def __init__(self, router_id):
self.router_id = router_id
class TestPrefixDelegation(tests_base.DietTestCase):
def test_remove_router(self):
l3_agent = mock.Mock()
router_id = 1
l3_agent.pd.routers = {router_id:
pd.get_router_entry(None, True)}
pd.remove_router(None, None, l3_agent,
payload=events.DBEventPayload(
mock.ANY,
resource_id=router_id,
states=(FakeRouter(router_id),)))
self.assertTrue(l3_agent.pd.delete_router_pd.called)
self.assertEqual({}, l3_agent.pd.routers)
def _test_add_update_pd(self, l3_agent, router, ns_name):
# add entry
pd.add_router(None, None, l3_agent,
payload=events.DBEventPayload(
mock.ANY, states=(router,)))
pd_router = l3_agent.pd.routers.get(router.router_id)
self.assertEqual(ns_name, pd_router.get('ns_name'))
# clear namespace name, update entry
pd_router['ns_name'] = None
pd.update_router(None, None, l3_agent,
payload=events.DBEventPayload(
mock.ANY,
resource_id=router.router_id,
states=(router,)))
pd_router = l3_agent.pd.routers.get(router.router_id)
self.assertEqual(ns_name, pd_router.get('ns_name'))
@mock.patch.object(dvr_edge_router.DvrEdgeRouter,
'_load_used_fip_information')
def test_add_update_dvr_edge_router(self, *args):
l3_agent = mock.Mock()
l3_agent.pd.routers = {}
router_id = '1'
ri = dvr_edge_router.DvrEdgeRouter(l3_agent,
'host',
router_id,
mock.Mock(),
mock.Mock(),
mock.Mock())
ns_name = ri.snat_namespace.name
self._test_add_update_pd(l3_agent, ri, ns_name)
@mock.patch.object(dvr_local_router.DvrLocalRouter,
'_load_used_fip_information')
def test_add_update_dvr_local_router(self, *args):
l3_agent = mock.Mock()
l3_agent.pd.routers = {}
router_id = '1'
ri = dvr_local_router.DvrLocalRouter(l3_agent,
'host',
router_id,
mock.Mock(),
mock.Mock(),
mock.Mock())
ns_name = ri.ns_name
self._test_add_update_pd(l3_agent, ri, ns_name)
def test_add_update_legacy_router(self):
l3_agent = mock.Mock()
l3_agent.pd.routers = {}
router_id = '1'
ri = legacy_router.LegacyRouter(l3_agent,
router_id,
mock.Mock(),
mock.Mock(),
mock.Mock())
ns_name = ri.ns_name
self._test_add_update_pd(l3_agent, ri, ns_name)
def test_update_no_router_exception(self):
l3_agent = mock.Mock()
l3_agent.pd.routers = {}
router = mock.Mock()
router.router_id = '1'
with mock.patch.object(pd.LOG, 'exception') as log:
pd.update_router(None, None, l3_agent,
payload=events.DBEventPayload(
mock.ANY,
resource_id=router.router_id,
states=(router,)))
self.assertTrue(log.called)
def test_remove_stale_ri_ifname(self):
pd_info_1 = mock.Mock()
pd_info_1.ri_ifname = 'STALE'
pd_info_2 = mock.Mock()
pd_info_2.ri_ifname = 'NOT_STALE'
router = {
'subnets': {
'FAKE_SUBNET_ID1': pd_info_1,
'FAKE_SUBNET_ID2': pd_info_2}}
class FakePD(pd.PrefixDelegation):
def __init__(self, router):
self.routers = {'FAKE_ROUTER_ID': router}
fake_pd = FakePD(router)
fake_pd._delete_pd = mock.Mock()
fake_pd.remove_stale_ri_ifname('FAKE_ROUTER_ID', 'STALE')
fake_pd._delete_pd.assert_called_with(router, pd_info_1)
self.assertEqual(len(router['subnets'].keys()), 1)

View File

@@ -13,7 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import netaddr
from neutron_lib import constants
from neutron_lib import context
from neutron_lib.plugins import directory
@@ -56,13 +55,3 @@ class TestL3RpcCallback(testlib_api.SqlTestCase):
'ipv6_ra_mode': constants.IPV6_SLAAC,
'ipv6_address_mode': constants.IPV6_SLAAC}}
return self.plugin.create_subnet(self.ctx, subnet)
def test_process_prefix_update(self):
subnet = self._prepare_ipv6_pd_subnet()
data = {subnet['id']: netaddr.IPNetwork('2001:db8::/64')}
allocation_pools = [{'start': '2001:db8::2',
'end': '2001:db8::ffff:ffff:ffff:ffff'}]
res = self.callbacks.process_prefix_update(self.ctx, subnets=data)
updated_subnet = res[0]
self.assertEqual(str(data[subnet['id']]), updated_subnet['cidr'])
self.assertEqual(updated_subnet['allocation_pools'], allocation_pools)

View File

@@ -0,0 +1,7 @@
---
upgrade:
- |
IPv6 Prefix Delegation implementation based on ``dibbler`` was removed from
the L3 agent. No in-tree drivers support the feature at the moment. To
continue using the feature, you will have to switch to a different plugin
that supports it.

View File

@@ -43,7 +43,6 @@ console_scripts =
neutron-netns-cleanup = neutron.cmd.netns_cleanup:main
neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main
neutron-ovs-cleanup = neutron.cmd.ovs_cleanup:main
neutron-pd-notify = neutron.cmd.pd_notify:main
neutron-server = neutron.cmd.eventlet.server:main
neutron-rpc-server = neutron.cmd.eventlet.server:main_rpc_eventlet
neutron-rootwrap = oslo_rootwrap.cmd:main
@@ -153,8 +152,6 @@ neutron.services.logapi.drivers =
neutron.qos.agent_drivers =
ovs = neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers.qos_driver:QosOVSAgentDriver
sriov = neutron.plugins.ml2.drivers.mech_sriov.agent.extension_drivers.qos_driver:QosSRIOVAgentDriver
neutron.agent.linux.pd_drivers =
dibbler = neutron.agent.linux.dibbler:PDDibbler
neutron.services.external_dns_drivers =
designate = neutron.services.externaldns.drivers.designate.driver:Designate
oslo.config.opts =