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:
@@ -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
|
||||
|
@@ -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
|
||||
|
||||
|
@@ -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
|
||||
----------------------
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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
|
@@ -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
|
@@ -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
|
||||
"""
|
@@ -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."""
|
||||
|
@@ -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)
|
@@ -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:
|
||||
|
@@ -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,
|
||||
|
@@ -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)
|
||||
|
||||
|
@@ -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',
|
||||
|
@@ -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 "
|
||||
|
@@ -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.')),
|
||||
]
|
||||
|
||||
|
||||
|
@@ -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(
|
||||
|
@@ -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',
|
||||
|
@@ -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()
|
||||
|
||||
|
@@ -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')]
|
||||
|
@@ -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 = {
|
||||
|
@@ -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)
|
@@ -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)
|
||||
|
7
releasenotes/notes/remove-dibbler-b846e534244c6a74.yaml
Normal file
7
releasenotes/notes/remove-dibbler-b846e534244c6a74.yaml
Normal 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.
|
@@ -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 =
|
||||
|
Reference in New Issue
Block a user