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::
|
.. warning::
|
||||||
|
|
||||||
This feature is experimental with low test coverage, and the Dibbler client
|
This feature is experimental with low test coverage. There is currently no
|
||||||
which is used for this feature is no longer maintained. For details see:
|
reference implementation that would implement the feature in the tree. A
|
||||||
https://github.com/tomaszmrugalski/dibbler#project-status
|
third party driver may have to be used to utilize it.
|
||||||
|
|
||||||
From the Liberty release onwards, OpenStack Networking supports IPv6 prefix
|
From the Liberty release onwards, OpenStack Networking supports IPv6 prefix
|
||||||
delegation. This section describes the configuration and workflow steps
|
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
|
external (to the OpenStack Networking service) DHCPv6 server to manage your
|
||||||
project network prefixes.
|
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
|
Configuring OpenStack Networking for prefix delegation
|
||||||
------------------------------------------------------
|
------------------------------------------------------
|
||||||
|
|
||||||
@@ -571,15 +565,7 @@ To enable prefix delegation, edit the ``/etc/neutron/neutron.conf`` file.
|
|||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
If you are not using the default dibbler-based driver for prefix
|
Drivers may require extra configuration.
|
||||||
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.
|
|
||||||
|
|
||||||
This tells OpenStack Networking to use the prefix delegation mechanism for
|
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
|
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
|
Dibbler. Dibbler is available in many Linux package managers, or from source at
|
||||||
`tomaszmrugalski/dibbler <https://github.com/tomaszmrugalski/dibbler>`_.
|
`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
|
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
|
where the external network bridge exists. If you already have a prefix
|
||||||
delegation capable DHCPv6 server in place, then you can skip the following
|
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]
|
usage: neutron-sanity-check [-h] [--arp_header_match] [--arp_responder]
|
||||||
[--bridge_firewalling] [--config-dir DIR]
|
[--bridge_firewalling] [--config-dir DIR]
|
||||||
[--config-file PATH] [--debug] [--dhcp_release6]
|
[--config-file PATH] [--debug] [--dhcp_release6]
|
||||||
[--dibbler_version] [--dnsmasq_version]
|
[--dnsmasq_version]
|
||||||
[--ebtables_installed] [--icmpv6_header_match]
|
[--ebtables_installed] [--icmpv6_header_match]
|
||||||
[--ip6tables_installed] [--ip_nonlocal_bind]
|
[--ip6tables_installed] [--ip_nonlocal_bind]
|
||||||
[--iproute2_vxlan] [--ipset_installed]
|
[--iproute2_vxlan] [--ipset_installed]
|
||||||
@@ -27,7 +27,7 @@ neutron-sanity-check usage
|
|||||||
[--log-dir LOG_DIR] [--log-file PATH]
|
[--log-dir LOG_DIR] [--log-file PATH]
|
||||||
[--noarp_header_match] [--noarp_responder]
|
[--noarp_header_match] [--noarp_responder]
|
||||||
[--nobridge_firewalling] [--nodebug]
|
[--nobridge_firewalling] [--nodebug]
|
||||||
[--nodhcp_release6] [--nodibbler_version]
|
[--nodhcp_release6]
|
||||||
[--nodnsmasq_version] [--noebtables_installed]
|
[--nodnsmasq_version] [--noebtables_installed]
|
||||||
[--noicmpv6_header_match]
|
[--noicmpv6_header_match]
|
||||||
[--noip6tables_installed] [--noip_nonlocal_bind]
|
[--noip6tables_installed] [--noip_nonlocal_bind]
|
||||||
@@ -80,9 +80,6 @@ neutron-sanity-check optional arguments
|
|||||||
``--dhcp_release6``
|
``--dhcp_release6``
|
||||||
Check dhcp_release6 installation
|
Check dhcp_release6 installation
|
||||||
|
|
||||||
``--dibbler_version``
|
|
||||||
Check minimal dibbler version
|
|
||||||
|
|
||||||
``--dnsmasq_version``
|
``--dnsmasq_version``
|
||||||
Check minimal dnsmasq version
|
Check minimal dnsmasq version
|
||||||
|
|
||||||
@@ -141,9 +138,6 @@ neutron-sanity-check optional arguments
|
|||||||
``--nodhcp_release6``
|
``--nodhcp_release6``
|
||||||
The inverse of --dhcp_release6
|
The inverse of --dhcp_release6
|
||||||
|
|
||||||
``--nodibbler_version``
|
|
||||||
The inverse of --dibbler_version
|
|
||||||
|
|
||||||
``--nodnsmasq_version``
|
``--nodnsmasq_version``
|
||||||
The inverse of --dnsmasq_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.
|
* Patch https://review.opendev.org/c/openstack/neutron/+/286087 was abandoned.
|
||||||
* Prefix delegation doesn't have functional tests for the dibbler and pd
|
* Prefix delegation doesn't have a reference implementation in tree and hence
|
||||||
layers, nor for the L3 agent changes. This has been an area of repeated
|
is not covered with functional tests of any sort.
|
||||||
regressions.
|
|
||||||
|
|
||||||
Missing Infrastructure
|
Missing Infrastructure
|
||||||
----------------------
|
----------------------
|
||||||
|
@@ -13,24 +13,18 @@ at [1]_.
|
|||||||
ML2/OVN integration with the Nova placement API to provide guaranteed
|
ML2/OVN integration with the Nova placement API to provide guaranteed
|
||||||
minimum bandwidth for ports [2]_. Work in progress, see [3]_
|
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
|
* DHCP service for instances
|
||||||
|
|
||||||
ML2/OVS adds packet filtering rules to every instance that allow DHCP queries
|
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
|
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
|
explicitly allowed by security group rules attached to the instance. Note
|
||||||
that the default security group does allow all outgoing traffic, so this only
|
that the default security group does allow all outgoing traffic, so this only
|
||||||
becomes relevant when using custom security groups [6]_. Proposed patch is
|
becomes relevant when using custom security groups [4]_. Proposed patch is
|
||||||
[7]_ but it needs to be revived and updated.
|
[5]_ but it needs to be revived and updated.
|
||||||
|
|
||||||
* DNS resolution for instances
|
* 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
|
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``::
|
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
|
The core OVN implementation does not support fragmentation of East/West
|
||||||
traffic using an OVN router between two private networks. This is being
|
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
|
* North/South Fragmentation and path MTU discovery
|
||||||
|
|
||||||
@@ -70,13 +64,13 @@ at [1]_.
|
|||||||
[ovn]
|
[ovn]
|
||||||
ovn_emit_need_to_frag = true
|
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
|
* Traffic metering
|
||||||
|
|
||||||
Currently ``neutron-metering-agent`` can only work with the Neutron L3 agent.
|
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
|
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
|
* 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
|
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
|
networks to be distributed but port forwardings are always centralized in
|
||||||
ML2/OVN backend.
|
ML2/OVN backend.
|
||||||
This is being reported in [13]_.
|
This is being reported in [11]_.
|
||||||
|
|
||||||
References
|
References
|
||||||
----------
|
----------
|
||||||
@@ -95,13 +89,11 @@ References
|
|||||||
.. [1] https://github.com/ovn-org/ovn/blob/master/TODO.rst
|
.. [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
|
.. [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
|
.. [3] https://review.opendev.org/c/openstack/neutron/+/786478
|
||||||
.. [4] https://patchwork.ozlabs.org/project/openvswitch/patch/6aec0fb280f610a2083fbb6c61e251b1d237b21f.1576840560.git.lorenzo.bianconi@redhat.com/
|
.. [4] https://bugs.launchpad.net/neutron/+bug/1926515
|
||||||
.. [5] https://bugs.launchpad.net/neutron/+bug/1895972
|
.. [5] https://review.opendev.org/c/openstack/neutron/+/788594
|
||||||
.. [6] https://bugs.launchpad.net/neutron/+bug/1926515
|
.. [6] https://docs.openstack.org/neutron/latest/admin/config-dns-res.html
|
||||||
.. [7] https://review.opendev.org/c/openstack/neutron/+/788594
|
.. [7] https://bugs.launchpad.net/neutron/+bug/2032817
|
||||||
.. [8] https://docs.openstack.org/neutron/latest/admin/config-dns-res.html
|
.. [8] https://bugzilla.redhat.com/show_bug.cgi?id=2238494
|
||||||
.. [9] https://bugs.launchpad.net/neutron/+bug/2032817
|
.. [9] https://bugzilla.redhat.com/show_bug.cgi?id=2238969
|
||||||
.. [10] https://bugzilla.redhat.com/show_bug.cgi?id=2238494
|
.. [10] https://bugs.launchpad.net/neutron/+bug/2048773
|
||||||
.. [11] https://bugzilla.redhat.com/show_bug.cgi?id=2238969
|
.. [11] https://bugs.launchpad.net/neutron/+bug/2028846
|
||||||
.. [12] https://bugs.launchpad.net/neutron/+bug/2048773
|
|
||||||
.. [13] 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: CommandFilter, dnsmasq, root
|
||||||
dnsmasq_env: EnvFilter, env, root, PROCESS_TAG=, dnsmasq
|
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
|
# L3
|
||||||
radvd: CommandFilter, radvd, root
|
radvd: CommandFilter, radvd, root
|
||||||
radvd_env: EnvFilter, env, root, PROCESS_TAG=, radvd
|
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 namespace_manager
|
||||||
from neutron.agent.l3 import namespaces as l3_namespaces
|
from neutron.agent.l3 import namespaces as l3_namespaces
|
||||||
from neutron.agent.linux import external_process
|
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.metadata import driver as metadata_driver
|
||||||
from neutron.agent import rpc as agent_rpc
|
from neutron.agent import rpc as agent_rpc
|
||||||
from neutron.common import utils
|
from neutron.common import utils
|
||||||
@@ -70,15 +69,13 @@ SYNC_ROUTERS_MIN_CHUNK_SIZE = 32
|
|||||||
PRIORITY_RELATED_ROUTER = 0
|
PRIORITY_RELATED_ROUTER = 0
|
||||||
PRIORITY_RPC = 1
|
PRIORITY_RPC = 1
|
||||||
PRIORITY_SYNC_ROUTERS_TASK = 2
|
PRIORITY_SYNC_ROUTERS_TASK = 2
|
||||||
PRIORITY_PD_UPDATE = 3
|
|
||||||
|
|
||||||
# Actions
|
# Actions
|
||||||
DELETE_ROUTER = 1
|
DELETE_ROUTER = 1
|
||||||
DELETE_RELATED_ROUTER = 2
|
DELETE_RELATED_ROUTER = 2
|
||||||
ADD_UPDATE_ROUTER = 3
|
ADD_UPDATE_ROUTER = 3
|
||||||
ADD_UPDATE_RELATED_ROUTER = 4
|
ADD_UPDATE_RELATED_ROUTER = 4
|
||||||
PD_UPDATE = 5
|
UPDATE_NETWORK = 5
|
||||||
UPDATE_NETWORK = 6
|
|
||||||
|
|
||||||
RELATED_ACTION_MAP = {DELETE_ROUTER: DELETE_RELATED_ROUTER,
|
RELATED_ACTION_MAP = {DELETE_ROUTER: DELETE_RELATED_ROUTER,
|
||||||
ADD_UPDATE_ROUTER: ADD_UPDATE_RELATED_ROUTER}
|
ADD_UPDATE_ROUTER: ADD_UPDATE_RELATED_ROUTER}
|
||||||
@@ -117,6 +114,7 @@ class L3PluginApi:
|
|||||||
1.11 Added get_host_ha_router_count
|
1.11 Added get_host_ha_router_count
|
||||||
1.12 Added get_networks
|
1.12 Added get_networks
|
||||||
1.13 Removed get_external_network_id
|
1.13 Removed get_external_network_id
|
||||||
|
1.14 Removed process_prefix_update
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, topic, host):
|
def __init__(self, topic, host):
|
||||||
@@ -178,13 +176,6 @@ class L3PluginApi:
|
|||||||
return cctxt.cast(context, 'update_ha_routers_states',
|
return cctxt.cast(context, 'update_ha_routers_states',
|
||||||
host=self.host, states=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
|
@utils.timecost
|
||||||
def delete_agent_gateway_port(self, context, fip_net):
|
def delete_agent_gateway_port(self, context, fip_net):
|
||||||
"""Delete Floatingip_agent_gateway_port."""
|
"""Delete Floatingip_agent_gateway_port."""
|
||||||
@@ -335,12 +326,6 @@ class L3NATAgent(ha.AgentMixin,
|
|||||||
self.target_ex_net_id = None
|
self.target_ex_net_id = None
|
||||||
self.use_ipv6 = netutils.is_ipv6_enabled()
|
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
|
# Consume network updates to trigger router resync
|
||||||
consumers = [[topics.NETWORK, topics.UPDATE]]
|
consumers = [[topics.NETWORK, topics.UPDATE]]
|
||||||
agent_rpc.create_consumers([self], topics.AGENT, consumers)
|
agent_rpc.create_consumers([self], topics.AGENT, consumers)
|
||||||
@@ -762,13 +747,6 @@ class L3NATAgent(ha.AgentMixin,
|
|||||||
update.id, update.action, update.priority,
|
update.id, update.action, update.priority,
|
||||||
update.update_id,
|
update.update_id,
|
||||||
update.time_elapsed_since_create)
|
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 []
|
routers = [update.resource] if update.resource else []
|
||||||
|
|
||||||
@@ -977,14 +955,6 @@ class L3NATAgent(ha.AgentMixin,
|
|||||||
for router in self.router_info.values():
|
for router in self.router_info.values():
|
||||||
router.delete()
|
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):
|
class L3NATAgentWithStateReport(L3NATAgent):
|
||||||
|
|
||||||
@@ -1060,8 +1030,6 @@ class L3NATAgentWithStateReport(L3NATAgent):
|
|||||||
# Do the report state before we do the first full sync.
|
# Do the report state before we do the first full sync.
|
||||||
self._report_state()
|
self._report_state()
|
||||||
|
|
||||||
self.pd.after_start()
|
|
||||||
|
|
||||||
def agent_updated(self, context, payload):
|
def agent_updated(self, context, payload):
|
||||||
"""Handle the agent_updated notification event."""
|
"""Handle the agent_updated notification event."""
|
||||||
self.fullsync = True
|
self.fullsync = True
|
||||||
|
@@ -128,14 +128,14 @@ class AgentMixin:
|
|||||||
def enqueue_state_change(self, router_id, state):
|
def enqueue_state_change(self, router_id, state):
|
||||||
"""Inform the server about the new router state
|
"""Inform the server about the new router state
|
||||||
|
|
||||||
This function will also update the metadata proxy, the radvd daemon,
|
This function will also update the metadata proxy, the radvd daemon and
|
||||||
process the prefix delegation and inform to the L3 extensions. If the
|
inform to the L3 extensions. If the HA router changes to "primary",
|
||||||
HA router changes to "primary", this transition will be delayed for at
|
this transition will be delayed for at least "ha_vrrp_advert_int"
|
||||||
least "ha_vrrp_advert_int" seconds. When the "primary" router
|
seconds. When the "primary" router transitions to "backup",
|
||||||
transitions to "backup", "keepalived" will set the rest of HA routers
|
"keepalived" will set the rest of HA routers to "primary" until it
|
||||||
to "primary" until it decides which one should be the only "primary".
|
decides which one should be the only "primary". The transition from
|
||||||
The transition from "backup" to "primary" and then to "backup" again,
|
"backup" to "primary" and then to "backup" again, should not be
|
||||||
should not be registered in the Neutron server.
|
registered in the Neutron server.
|
||||||
|
|
||||||
:param router_id: router ID
|
:param router_id: router ID
|
||||||
:param state: ['primary', 'backup']
|
:param state: ['primary', 'backup']
|
||||||
@@ -180,7 +180,6 @@ class AgentMixin:
|
|||||||
if self.conf.enable_metadata_proxy:
|
if self.conf.enable_metadata_proxy:
|
||||||
self._update_metadata_proxy(ri, router_id, state)
|
self._update_metadata_proxy(ri, router_id, state)
|
||||||
self._update_radvd_daemon(ri, 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.state_change_notifier.queue_event((router_id, state))
|
||||||
self.l3_ext_manager.ha_state_change(self.context, state_change_data)
|
self.l3_ext_manager.ha_state_change(self.context, state_change_data)
|
||||||
|
|
||||||
|
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
import abc
|
import abc
|
||||||
import collections
|
import collections
|
||||||
|
import itertools
|
||||||
|
|
||||||
import netaddr
|
import netaddr
|
||||||
from neutron_lib import constants as lib_constants
|
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 iptables_manager
|
||||||
from neutron.agent.linux import ra
|
from neutron.agent.linux import ra
|
||||||
from neutron.common import coordination
|
from neutron.common import coordination
|
||||||
from neutron.common import ipv6_utils
|
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
from neutron.ipam import utils as ipam_utils
|
from neutron.ipam import utils as ipam_utils
|
||||||
|
|
||||||
@@ -135,7 +135,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
|
|
||||||
self.ex_gw_port = None
|
self.ex_gw_port = None
|
||||||
self.fip_map = {}
|
self.fip_map = {}
|
||||||
self.pd_subnets = {}
|
|
||||||
self.floating_ips = set()
|
self.floating_ips = set()
|
||||||
ns = self.create_router_namespace_object(
|
ns = self.create_router_namespace_object(
|
||||||
router_id, agent_conf, interface_driver, use_ipv6)
|
router_id, agent_conf, interface_driver, use_ipv6)
|
||||||
@@ -328,19 +327,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
|
|
||||||
self.iptables_manager.apply()
|
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):
|
def process_floating_ip_address_scope_rules(self):
|
||||||
"""Configure address scope related iptables rules for the router's
|
"""Configure address scope related iptables rules for the router's
|
||||||
floating IPs.
|
floating IPs.
|
||||||
@@ -676,56 +662,25 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
updated_ports = self._get_updated_ports(self.internal_ports,
|
updated_ports = self._get_updated_ports(self.internal_ports,
|
||||||
internal_ports)
|
internal_ports)
|
||||||
|
|
||||||
enable_ra = False
|
|
||||||
for p in old_ports:
|
for p in old_ports:
|
||||||
self.internal_network_removed(p)
|
self.internal_network_removed(p)
|
||||||
LOG.debug("removing port %s from internal_ports cache", p)
|
LOG.debug("removing port %s from internal_ports cache", p)
|
||||||
self.internal_ports.remove(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:
|
for p in new_ports:
|
||||||
self.internal_network_added(p)
|
self.internal_network_added(p)
|
||||||
LOG.debug("appending port %s to internal_ports cache", p)
|
LOG.debug("appending port %s to internal_ports cache", p)
|
||||||
self._update_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 = []
|
updated_cidrs = []
|
||||||
for p in updated_ports:
|
for p in updated_ports:
|
||||||
self._update_internal_ports_cache(p)
|
self._update_internal_ports_cache(p)
|
||||||
updated_cidrs += common_utils.fixed_ip_cidrs(p['fixed_ips'])
|
updated_cidrs += common_utils.fixed_ip_cidrs(p['fixed_ips'])
|
||||||
self.internal_network_updated(p)
|
self.internal_network_updated(p)
|
||||||
enable_ra = enable_ra or self._port_has_ipv6_subnet(p)
|
|
||||||
|
|
||||||
# Check if there is any pd prefix update
|
enable_ra = any(
|
||||||
for p in internal_ports:
|
self._port_has_ipv6_subnet(p)
|
||||||
if p['id'] in (set(current_port_ids) & set(existing_port_ids)):
|
for p in itertools.chain(new_ports, old_ports, updated_ports))
|
||||||
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
|
# Enable RA
|
||||||
if enable_ra:
|
if enable_ra:
|
||||||
@@ -740,7 +695,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
for stale_dev in stale_devs:
|
for stale_dev in stale_devs:
|
||||||
LOG.debug('Deleting stale internal router device: %s',
|
LOG.debug('Deleting stale internal router device: %s',
|
||||||
stale_dev)
|
stale_dev)
|
||||||
self.agent.pd.remove_stale_ri_ifname(self.router_id, stale_dev)
|
|
||||||
self.driver.unplug(stale_dev,
|
self.driver.unplug(stale_dev,
|
||||||
namespace=self.ns_name,
|
namespace=self.ns_name,
|
||||||
prefix=INTERNAL_DEV_PREFIX)
|
prefix=INTERNAL_DEV_PREFIX)
|
||||||
@@ -868,14 +822,12 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
def external_gateway_added(self, ex_gw_port, interface_name):
|
def external_gateway_added(self, ex_gw_port, interface_name):
|
||||||
preserve_ips = self._list_floating_ip_cidrs() + list(
|
preserve_ips = self._list_floating_ip_cidrs() + list(
|
||||||
self.centralized_port_forwarding_fip_set)
|
self.centralized_port_forwarding_fip_set)
|
||||||
preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id))
|
|
||||||
self._external_gateway_added(
|
self._external_gateway_added(
|
||||||
ex_gw_port, interface_name, self.ns_name, preserve_ips)
|
ex_gw_port, interface_name, self.ns_name, preserve_ips)
|
||||||
|
|
||||||
def external_gateway_updated(self, ex_gw_port, interface_name):
|
def external_gateway_updated(self, ex_gw_port, interface_name):
|
||||||
preserve_ips = self._list_floating_ip_cidrs() + list(
|
preserve_ips = self._list_floating_ip_cidrs() + list(
|
||||||
self.centralized_port_forwarding_fip_set)
|
self.centralized_port_forwarding_fip_set)
|
||||||
preserve_ips.extend(self.agent.pd.get_preserve_ips(self.router_id))
|
|
||||||
self._external_gateway_added(
|
self._external_gateway_added(
|
||||||
ex_gw_port, interface_name, self.ns_name, preserve_ips)
|
ex_gw_port, interface_name, self.ns_name, preserve_ips)
|
||||||
|
|
||||||
@@ -904,7 +856,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
dev != interface_name]
|
dev != interface_name]
|
||||||
for stale_dev in stale_devs:
|
for stale_dev in stale_devs:
|
||||||
LOG.debug('Deleting stale external router device: %s', stale_dev)
|
LOG.debug('Deleting stale external router device: %s', stale_dev)
|
||||||
self.agent.pd.remove_gw_interface(self.router['id'])
|
|
||||||
self.driver.unplug(stale_dev,
|
self.driver.unplug(stale_dev,
|
||||||
namespace=self.ns_name,
|
namespace=self.ns_name,
|
||||||
prefix=EXTERNAL_DEV_PREFIX)
|
prefix=EXTERNAL_DEV_PREFIX)
|
||||||
@@ -920,13 +871,10 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
if ex_gw_port:
|
if ex_gw_port:
|
||||||
if not self.ex_gw_port:
|
if not self.ex_gw_port:
|
||||||
self.external_gateway_added(ex_gw_port, interface_name)
|
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):
|
elif not self._gateway_ports_equal(ex_gw_port, self.ex_gw_port):
|
||||||
self.external_gateway_updated(ex_gw_port, interface_name)
|
self.external_gateway_updated(ex_gw_port, interface_name)
|
||||||
elif not ex_gw_port and self.ex_gw_port:
|
elif not ex_gw_port and self.ex_gw_port:
|
||||||
self.external_gateway_removed(self.ex_gw_port, interface_name)
|
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:
|
elif not ex_gw_port and not self.ex_gw_port:
|
||||||
for p in self.internal_ports:
|
for p in self.internal_ports:
|
||||||
interface_name = self.get_internal_device_name(p['id'])
|
interface_name = self.get_internal_device_name(p['id'])
|
||||||
@@ -1225,9 +1173,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
iptables['filter'].add_rule(
|
iptables['filter'].add_rule(
|
||||||
'scope',
|
'scope',
|
||||||
self.address_scope_filter_rule(device_name, mark))
|
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):
|
def process_ports_address_scope_iptables(self):
|
||||||
ports_scopemark = self._get_address_scope_mark()
|
ports_scopemark = self._get_address_scope_mark()
|
||||||
@@ -1292,7 +1237,6 @@ class RouterInfo(BaseRouterInfo):
|
|||||||
LOG.debug("Process delete, router %s", self.router['id'])
|
LOG.debug("Process delete, router %s", self.router['id'])
|
||||||
if self.router_namespace.exists():
|
if self.router_namespace.exists():
|
||||||
self._process_internal_ports()
|
self._process_internal_ports()
|
||||||
self.agent.pd.sync_router(self.router['id'])
|
|
||||||
self._process_external_on_delete()
|
self._process_external_on_delete()
|
||||||
else:
|
else:
|
||||||
LOG.warning("Can't gracefully delete the router %s: "
|
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(
|
self.centralized_port_forwarding_fip_set = set(self.router.get(
|
||||||
'port_forwardings_fip_set', set()))
|
'port_forwardings_fip_set', set()))
|
||||||
self._process_internal_ports()
|
self._process_internal_ports()
|
||||||
self.agent.pd.sync_router(self.router['id'])
|
|
||||||
self.process_external()
|
self.process_external()
|
||||||
self.process_address_scope()
|
self.process_address_scope()
|
||||||
# Process static routes for router
|
# Process static routes for router
|
||||||
|
@@ -42,7 +42,6 @@ def register_opts(conf):
|
|||||||
config.register_agent_state_opts_helper(conf)
|
config.register_agent_state_opts_helper(conf)
|
||||||
config.register_interface_opts(conf)
|
config.register_interface_opts(conf)
|
||||||
config.register_external_process_opts(conf)
|
config.register_external_process_opts(conf)
|
||||||
config.register_pddriver_opts(conf)
|
|
||||||
config.register_ra_opts(conf)
|
config.register_ra_opts(conf)
|
||||||
config.register_availability_zone_opts_helper(conf)
|
config.register_availability_zone_opts_helper(conf)
|
||||||
ovs_conf.register_ovs_opts(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.10 Added update_all_ha_network_port_statuses
|
||||||
# 1.11 Added get_host_ha_router_count
|
# 1.11 Added get_host_ha_router_count
|
||||||
# 1.12 Added get_networks
|
# 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
|
@property
|
||||||
def plugin(self):
|
def plugin(self):
|
||||||
@@ -327,17 +328,6 @@ class L3RpcCallback:
|
|||||||
LOG.debug('Updating HA routers states on host %s: %s', host, states)
|
LOG.debug('Updating HA routers states on host %s: %s', host, states)
|
||||||
self.l3plugin.update_routers_states(context, states, host)
|
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
|
@db_api.retry_db_errors
|
||||||
def delete_agent_gateway_port(self, context, **kwargs):
|
def delete_agent_gateway_port(self, context, **kwargs):
|
||||||
"""Delete Floatingip agent gateway port."""
|
"""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_HOST_ADDR6_LIST = '2.81'
|
||||||
DNSMASQ_VERSION_SEGFAULT_ISSUE = '2.86'
|
DNSMASQ_VERSION_SEGFAULT_ISSUE = '2.86'
|
||||||
DIRECT_PORT_QOS_MIN_OVS_VERSION = '2.11'
|
DIRECT_PORT_QOS_MIN_OVS_VERSION = '2.11'
|
||||||
MINIMUM_DIBBLER_VERSION = '1.0.1'
|
|
||||||
CONNTRACK_GRE_MODULE = 'nf_conntrack_proto_gre'
|
CONNTRACK_GRE_MODULE = 'nf_conntrack_proto_gre'
|
||||||
OVN_NB_DB_SCHEMA_GATEWAY_CHASSIS = '5.7.0'
|
OVN_NB_DB_SCHEMA_GATEWAY_CHASSIS = '5.7.0'
|
||||||
OVN_NB_DB_SCHEMA_PORT_GROUP = '5.11.0'
|
OVN_NB_DB_SCHEMA_PORT_GROUP = '5.11.0'
|
||||||
@@ -539,22 +538,6 @@ def conntrack_supported():
|
|||||||
return False
|
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):
|
def _fix_ip_nonlocal_bind_root_value(original_value):
|
||||||
current_value = ip_lib.get_ip_nonlocal_bind(namespace=None)
|
current_value = ip_lib.get_ip_nonlocal_bind(namespace=None)
|
||||||
if current_value != original_value:
|
if current_value != original_value:
|
||||||
|
@@ -163,15 +163,6 @@ def check_keepalived_garp_on_sighup_support():
|
|||||||
return result
|
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():
|
def check_nova_notify():
|
||||||
result = checks.nova_notify_supported()
|
result = checks.nova_notify_supported()
|
||||||
if not result:
|
if not result:
|
||||||
@@ -406,10 +397,6 @@ OPTS = [
|
|||||||
check_keepalived_garp_on_sighup_support,
|
check_keepalived_garp_on_sighup_support,
|
||||||
help=_('Check keepalived support sending garp on '
|
help=_('Check keepalived support sending garp on '
|
||||||
'SIGHUP.')),
|
'SIGHUP.')),
|
||||||
BoolOptCallback('dibbler_version', check_dibbler_version,
|
|
||||||
help=_('Check minimal dibbler version'),
|
|
||||||
deprecated_for_removal=True,
|
|
||||||
deprecated_since='Pike'),
|
|
||||||
BoolOptCallback('ipset_installed', check_ipset,
|
BoolOptCallback('ipset_installed', check_ipset,
|
||||||
help=_('Check ipset installation')),
|
help=_('Check ipset installation')),
|
||||||
BoolOptCallback('ip6tables_installed', check_ip6tables,
|
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 = [
|
INTERFACE_OPTS = [
|
||||||
cfg.BoolOpt('ovs_use_veth',
|
cfg.BoolOpt('ovs_use_veth',
|
||||||
default=False,
|
default=False,
|
||||||
@@ -174,14 +156,6 @@ def register_external_process_opts(cfg=cfg.CONF):
|
|||||||
cfg.register_opts(EXTERNAL_PROCESS_OPTS)
|
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):
|
def register_interface_opts(cfg=cfg.CONF):
|
||||||
cfg.register_opts(INTERFACE_OPTS)
|
cfg.register_opts(INTERFACE_OPTS)
|
||||||
|
|
||||||
|
@@ -74,13 +74,6 @@ OPTS = [
|
|||||||
"next-hop using a global unique address (GUA) is "
|
"next-hop using a global unique address (GUA) is "
|
||||||
"desired, it needs to be done via a subnet allocated "
|
"desired, it needs to be done via a subnet allocated "
|
||||||
"to the network and not through this parameter.")),
|
"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,
|
cfg.BoolOpt('enable_metadata_proxy', default=True,
|
||||||
help=_("Allow running metadata proxy.")),
|
help=_("Allow running metadata proxy.")),
|
||||||
cfg.StrOpt('metadata_access_mark',
|
cfg.StrOpt('metadata_access_mark',
|
||||||
|
@@ -68,8 +68,7 @@ core_opts = [
|
|||||||
help=_("Maximum number of host routes per subnet")),
|
help=_("Maximum number of host routes per subnet")),
|
||||||
cfg.BoolOpt('ipv6_pd_enabled', default=False,
|
cfg.BoolOpt('ipv6_pd_enabled', default=False,
|
||||||
help=_("Warning: This feature is experimental with low test "
|
help=_("Warning: This feature is experimental with low test "
|
||||||
"coverage, and the Dibbler client which is used for "
|
"coverage. "
|
||||||
"this feature is no longer maintained! "
|
|
||||||
"Enables IPv6 Prefix Delegation for automatic subnet "
|
"Enables IPv6 Prefix Delegation for automatic subnet "
|
||||||
"CIDR allocation. "
|
"CIDR allocation. "
|
||||||
"Set to True to enable IPv6 Prefix Delegation for "
|
"Set to True to enable IPv6 Prefix Delegation for "
|
||||||
@@ -81,8 +80,9 @@ core_opts = [
|
|||||||
"the default IPv6 subnetpool."),
|
"the default IPv6 subnetpool."),
|
||||||
deprecated_for_removal=True,
|
deprecated_for_removal=True,
|
||||||
deprecated_since='2023.2',
|
deprecated_since='2023.2',
|
||||||
deprecated_reason=("The Dibbler client used for this feature "
|
deprecated_reason=(
|
||||||
"is no longer maintained. See LP#1916428"),
|
"There is no reference implementation for the feature for "
|
||||||
|
"any of in-tree drivers."),
|
||||||
),
|
),
|
||||||
cfg.IntOpt('dhcp_lease_duration', default=86400,
|
cfg.IntOpt('dhcp_lease_duration', default=86400,
|
||||||
help=_("DHCP lease duration (in seconds). Use -1 to tell "
|
help=_("DHCP lease duration (in seconds). Use -1 to tell "
|
||||||
|
@@ -21,7 +21,7 @@ experimental_opts = [
|
|||||||
cfg.BoolOpt(EXPERIMENTAL_IPV6_PD,
|
cfg.BoolOpt(EXPERIMENTAL_IPV6_PD,
|
||||||
default=False,
|
default=False,
|
||||||
help=_('Enable execution of the experimental IPv6 Prefix '
|
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):
|
if new_cidr and ipv6_utils.is_ipv6_pd_enabled(s):
|
||||||
# This is an ipv6 prefix delegation-enabled subnet being given an
|
# 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'])
|
s['cidr'] = netaddr.IPNetwork(new_cidr, s['ip_version'])
|
||||||
# Update gateway_ip and allocation pools based on new cidr
|
# Update gateway_ip and allocation pools based on new cidr
|
||||||
s['gateway_ip'] = utils.get_first_host_ip(
|
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.agent.l3.config.OPTS,
|
||||||
neutron.conf.service.SERVICE_OPTS,
|
neutron.conf.service.SERVICE_OPTS,
|
||||||
neutron.conf.agent.l3.ha.OPTS,
|
neutron.conf.agent.l3.ha.OPTS,
|
||||||
neutron.conf.agent.common.PD_DRIVER_OPTS,
|
|
||||||
neutron.conf.agent.common.RA_OPTS)
|
neutron.conf.agent.common.RA_OPTS)
|
||||||
),
|
),
|
||||||
('agent',
|
('agent',
|
||||||
|
@@ -38,9 +38,6 @@ class SanityTestCase(base.BaseLoggingTestCase):
|
|||||||
def test_dnsmasq_version(self):
|
def test_dnsmasq_version(self):
|
||||||
checks.dnsmasq_version_supported()
|
checks.dnsmasq_version_supported()
|
||||||
|
|
||||||
def test_dibbler_version(self):
|
|
||||||
checks.dibbler_version_supported()
|
|
||||||
|
|
||||||
def test_ipset_support(self):
|
def test_ipset_support(self):
|
||||||
checks.ipset_supported()
|
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 namespace_manager
|
||||||
from neutron.agent.l3 import namespaces
|
from neutron.agent.l3 import namespaces
|
||||||
from neutron.agent.l3 import router_info as l3router
|
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 interface
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import iptables_manager
|
from neutron.agent.linux import iptables_manager
|
||||||
from neutron.agent.linux import pd
|
|
||||||
from neutron.agent.linux import ra
|
from neutron.agent.linux import ra
|
||||||
from neutron.agent.linux import utils as linux_utils
|
from neutron.agent.linux import utils as linux_utils
|
||||||
from neutron.agent.metadata import driver as metadata_driver
|
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_availability_zone_opts_helper(self.conf)
|
||||||
agent_config.register_interface_opts(self.conf)
|
agent_config.register_interface_opts(self.conf)
|
||||||
agent_config.register_external_process_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)
|
agent_config.register_ra_opts(self.conf)
|
||||||
self.conf.set_override('interface_driver',
|
self.conf.set_override('interface_driver',
|
||||||
'neutron.agent.linux.interface.NullDriver')
|
'neutron.agent.linux.interface.NullDriver')
|
||||||
self.conf.set_override('state_path', cfg.CONF.state_path)
|
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
|
# Enable conntrackd support for tests for it to get full test coverage
|
||||||
self.conf.set_override('ha_conntrackd_enabled', True)
|
self.conf.set_override('ha_conntrackd_enabled', True)
|
||||||
@@ -3423,678 +3419,6 @@ class TestBasicRouterOperations(BasicRouterOperationsFramework):
|
|||||||
expected += "%s " % dns
|
expected += "%s " % dns
|
||||||
self.assertIn(expected, self.utils_replace_file.call_args[0][1])
|
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):
|
def _verify_address_scopes_iptables_rule(self, mock_iptables_manager):
|
||||||
filter_calls = [mock.call.add_chain('scope'),
|
filter_calls = [mock.call.add_chain('scope'),
|
||||||
mock.call.add_rule('FORWARD', '-j $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')]}
|
expected = {'delete': [('110.100.30.0/24', '10.100.10.30')]}
|
||||||
self._check_agent_method_called(ri, expected)
|
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):
|
def test_add_ports_address_scope_iptables(self):
|
||||||
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
ri = router_info.RouterInfo(mock.Mock(), _uuid(), {}, **self.ri_kwargs)
|
||||||
port = {
|
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
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import netaddr
|
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
from neutron_lib import context
|
from neutron_lib import context
|
||||||
from neutron_lib.plugins import directory
|
from neutron_lib.plugins import directory
|
||||||
@@ -56,13 +55,3 @@ class TestL3RpcCallback(testlib_api.SqlTestCase):
|
|||||||
'ipv6_ra_mode': constants.IPV6_SLAAC,
|
'ipv6_ra_mode': constants.IPV6_SLAAC,
|
||||||
'ipv6_address_mode': constants.IPV6_SLAAC}}
|
'ipv6_address_mode': constants.IPV6_SLAAC}}
|
||||||
return self.plugin.create_subnet(self.ctx, subnet)
|
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-netns-cleanup = neutron.cmd.netns_cleanup:main
|
||||||
neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main
|
neutron-openvswitch-agent = neutron.cmd.eventlet.plugins.ovs_neutron_agent:main
|
||||||
neutron-ovs-cleanup = neutron.cmd.ovs_cleanup: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-server = neutron.cmd.eventlet.server:main
|
||||||
neutron-rpc-server = neutron.cmd.eventlet.server:main_rpc_eventlet
|
neutron-rpc-server = neutron.cmd.eventlet.server:main_rpc_eventlet
|
||||||
neutron-rootwrap = oslo_rootwrap.cmd:main
|
neutron-rootwrap = oslo_rootwrap.cmd:main
|
||||||
@@ -153,8 +152,6 @@ neutron.services.logapi.drivers =
|
|||||||
neutron.qos.agent_drivers =
|
neutron.qos.agent_drivers =
|
||||||
ovs = neutron.plugins.ml2.drivers.openvswitch.agent.extension_drivers.qos_driver:QosOVSAgentDriver
|
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
|
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 =
|
neutron.services.external_dns_drivers =
|
||||||
designate = neutron.services.externaldns.drivers.designate.driver:Designate
|
designate = neutron.services.externaldns.drivers.designate.driver:Designate
|
||||||
oslo.config.opts =
|
oslo.config.opts =
|
||||||
|
Reference in New Issue
Block a user