diff --git a/doc/source/admin/config-ipv6.rst b/doc/source/admin/config-ipv6.rst index fad8e475cbe..cdb55d889d4 100644 --- a/doc/source/admin/config-ipv6.rst +++ b/doc/source/admin/config-ipv6.rst @@ -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 = - - 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 `_. -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 diff --git a/doc/source/cli/neutron-sanity-check.rst b/doc/source/cli/neutron-sanity-check.rst index ff420a6980f..ff645a3aa11 100644 --- a/doc/source/cli/neutron-sanity-check.rst +++ b/doc/source/cli/neutron-sanity-check.rst @@ -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 diff --git a/doc/source/contributor/testing/coverage.rst b/doc/source/contributor/testing/coverage.rst index fe50a2f8ee1..6418b45aa96 100644 --- a/doc/source/contributor/testing/coverage.rst +++ b/doc/source/contributor/testing/coverage.rst @@ -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 ---------------------- diff --git a/doc/source/ovn/gaps.rst b/doc/source/ovn/gaps.rst index a81fda1a8e3..d014efa9c92 100644 --- a/doc/source/ovn/gaps.rst +++ b/doc/source/ovn/gaps.rst @@ -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 diff --git a/etc/neutron/rootwrap.d/rootwrap.filters b/etc/neutron/rootwrap.d/rootwrap.filters index fabacc8e441..df8021288cc 100644 --- a/etc/neutron/rootwrap.d/rootwrap.filters +++ b/etc/neutron/rootwrap.d/rootwrap.filters @@ -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 diff --git a/neutron/agent/l3/agent.py b/neutron/agent/l3/agent.py index 8c27b427ae8..1b3904a618d 100644 --- a/neutron/agent/l3/agent.py +++ b/neutron/agent/l3/agent.py @@ -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 diff --git a/neutron/agent/l3/ha.py b/neutron/agent/l3/ha.py index fce6126ba4d..e1432d2841f 100644 --- a/neutron/agent/l3/ha.py +++ b/neutron/agent/l3/ha.py @@ -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) diff --git a/neutron/agent/l3/router_info.py b/neutron/agent/l3/router_info.py index 91206a790f9..a45ca4bf3d5 100644 --- a/neutron/agent/l3/router_info.py +++ b/neutron/agent/l3/router_info.py @@ -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 diff --git a/neutron/agent/l3_agent.py b/neutron/agent/l3_agent.py index e914e7baf05..39ad5042409 100644 --- a/neutron/agent/l3_agent.py +++ b/neutron/agent/l3_agent.py @@ -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) diff --git a/neutron/agent/linux/dibbler.py b/neutron/agent/linux/dibbler.py deleted file mode 100644 index abfa067c9dd..00000000000 --- a/neutron/agent/linux/dibbler.py +++ /dev/null @@ -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 diff --git a/neutron/agent/linux/pd.py b/neutron/agent/linux/pd.py deleted file mode 100644 index 2428aaf13a7..00000000000 --- a/neutron/agent/linux/pd.py +++ /dev/null @@ -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 diff --git a/neutron/agent/linux/pd_driver.py b/neutron/agent/linux/pd_driver.py deleted file mode 100644 index db934b931b1..00000000000 --- a/neutron/agent/linux/pd_driver.py +++ /dev/null @@ -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 - """ diff --git a/neutron/api/rpc/handlers/l3_rpc.py b/neutron/api/rpc/handlers/l3_rpc.py index 82c0eed75e8..3f987c163e7 100644 --- a/neutron/api/rpc/handlers/l3_rpc.py +++ b/neutron/api/rpc/handlers/l3_rpc.py @@ -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.""" diff --git a/neutron/cmd/pd_notify.py b/neutron/cmd/pd_notify.py deleted file mode 100644 index 6fd7c123b10..00000000000 --- a/neutron/cmd/pd_notify.py +++ /dev/null @@ -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) diff --git a/neutron/cmd/sanity/checks.py b/neutron/cmd/sanity/checks.py index 18c8014eb03..1f0bd7132a4 100644 --- a/neutron/cmd/sanity/checks.py +++ b/neutron/cmd/sanity/checks.py @@ -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: diff --git a/neutron/cmd/sanity_check.py b/neutron/cmd/sanity_check.py index 93dddc62905..4a2e7c2b351 100644 --- a/neutron/cmd/sanity_check.py +++ b/neutron/cmd/sanity_check.py @@ -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, diff --git a/neutron/conf/agent/common.py b/neutron/conf/agent/common.py index a3aa59c614f..b00af8bc7bc 100644 --- a/neutron/conf/agent/common.py +++ b/neutron/conf/agent/common.py @@ -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) diff --git a/neutron/conf/agent/l3/config.py b/neutron/conf/agent/l3/config.py index 9ddf8f03405..6d35f11bfe2 100644 --- a/neutron/conf/agent/l3/config.py +++ b/neutron/conf/agent/l3/config.py @@ -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', diff --git a/neutron/conf/common.py b/neutron/conf/common.py index be221bb27ec..f042599a090 100644 --- a/neutron/conf/common.py +++ b/neutron/conf/common.py @@ -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 " diff --git a/neutron/conf/experimental.py b/neutron/conf/experimental.py index a18a45cfe84..bd21062d99a 100644 --- a/neutron/conf/experimental.py +++ b/neutron/conf/experimental.py @@ -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.')), ] diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index 6197e772b32..21eb339e147 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -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( diff --git a/neutron/opts.py b/neutron/opts.py index de8cb113ade..1f3d47fed60 100644 --- a/neutron/opts.py +++ b/neutron/opts.py @@ -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', diff --git a/neutron/tests/functional/sanity/test_sanity.py b/neutron/tests/functional/sanity/test_sanity.py index 1771cc12ee1..ad5bb02cb92 100644 --- a/neutron/tests/functional/sanity/test_sanity.py +++ b/neutron/tests/functional/sanity/test_sanity.py @@ -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() diff --git a/neutron/tests/unit/agent/l3/test_agent.py b/neutron/tests/unit/agent/l3/test_agent.py index d1f45063829..404f8c40182 100644 --- a/neutron/tests/unit/agent/l3/test_agent.py +++ b/neutron/tests/unit/agent/l3/test_agent.py @@ -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')] diff --git a/neutron/tests/unit/agent/l3/test_router_info.py b/neutron/tests/unit/agent/l3/test_router_info.py index 131dbadc373..cded789937c 100644 --- a/neutron/tests/unit/agent/l3/test_router_info.py +++ b/neutron/tests/unit/agent/l3/test_router_info.py @@ -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 = { diff --git a/neutron/tests/unit/agent/linux/test_pd.py b/neutron/tests/unit/agent/linux/test_pd.py deleted file mode 100644 index 5d10880cf13..00000000000 --- a/neutron/tests/unit/agent/linux/test_pd.py +++ /dev/null @@ -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) diff --git a/neutron/tests/unit/api/rpc/handlers/test_l3_rpc.py b/neutron/tests/unit/api/rpc/handlers/test_l3_rpc.py index fd0dbbd1a76..91ac3a26b3c 100644 --- a/neutron/tests/unit/api/rpc/handlers/test_l3_rpc.py +++ b/neutron/tests/unit/api/rpc/handlers/test_l3_rpc.py @@ -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) diff --git a/releasenotes/notes/remove-dibbler-b846e534244c6a74.yaml b/releasenotes/notes/remove-dibbler-b846e534244c6a74.yaml new file mode 100644 index 00000000000..819db13e05e --- /dev/null +++ b/releasenotes/notes/remove-dibbler-b846e534244c6a74.yaml @@ -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. diff --git a/setup.cfg b/setup.cfg index 143b77a25e8..ce483581693 100644 --- a/setup.cfg +++ b/setup.cfg @@ -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 =