Merge "ovn: Remove standalone OVN Metadata agent"

This commit is contained in:
Zuul authored and Gerrit Code Review committed 2026-09-30 09:14:31 +00:00
commit 7b4e8b7342
38 files changed
+260 -1537

No files matched your search

+5 -5
View File
@@ -6,9 +6,9 @@ Metadata Service Caching
The OpenStack Networking service proxies requests that VMs send to the
Compute service to obtain their metadata. This functionality is provided by the
``neutron-metadata-agent``, ``neutron-ovn-metadata-agent`` or
``neutron-ovn-agent`` with the metadata extension, depending on the
ML2 backend used in the deployment.
``neutron-metadata-agent`` or ``neutron-ovn-agent`` with the metadata
extension (``extensions = metadata``), depending on the ML2 backend used in the
deployment.
To obtain metadata from the Compute service, the instance ID needs to be sent
to the ``nova-metadata-api``.
These two metadata agents provide the same functionality, but do it
@@ -19,8 +19,8 @@ out the ID of the instance which is asking for metadata:
details about a port with a specific fixed IP address connected to the given
network or router (proxy service is spawned for each Neutron router or
Neutron network),
* ``neutron-ovn-metadata-agent`` and ``neutron-ovn-agent`` check the
instance ID in the port details of the OVN Southbound DB.
* ``neutron-ovn-agent`` with the metadata extension checks the instance ID in
the port details of the OVN Southbound DB.
For large scale deployments which are using the ``neutron-metadata-agent`` this
may cause significant load on the RPC bus and neutron-server, since by default
+2 -2
View File
@@ -38,7 +38,7 @@ neutron/ Package
``neutron/agent/``
L2/L3/DHCP/metadata agent implementations and their extensions.
``neutron/agent/ovn/``
OVN metadata agent.
OVN agent and metadata extension.
``neutron/db/``
Database models, mixins, and migration scripts (Alembic).
``neutron/objects/``
@@ -47,7 +47,7 @@ neutron/ Package
API extension definitions (resource attributes, actions).
``neutron/cmd/``
Entry points for Neutron services (``neutron-server``,
``neutron-openvswitch-agent``, ``neutron-ovn-metadata-agent``,
``neutron-openvswitch-agent``, ``neutron-ovn-agent``,
``neutron-dhcp-agent``, ``neutron-l3-agent``, etc.).
``neutron/conf/``
oslo.config option declarations, one file per subsystem.
+11 -5
View File
@@ -101,7 +101,7 @@ information.
Permit remote access to TCP ports: 6640 (OVS) to VTEPS (if you use
vteps), 6642 (SBDB) to hosts running neutron-server, gateway nodes
that run ovn-controller, and compute node services like ovn-controller
and ovn-metadata-agent. 6641 (NBDB) to hosts running neutron-server.
and neutron-ovn-agent. 6641 (NBDB) to hosts running neutron-server.
* Since we are using ``options:redirect-type`` set to ``bridged`` for Logical
Router Ports in VLAN and FLAT networks, OVN redirects packets to the
@@ -310,10 +310,10 @@ Each compute node runs the OVS and ``ovn-controller`` services. The
``ovn-controller`` service replaces the conventional OVS layer-2 agent.
#. Install the ``ovn-host``, ``openvswitch`` and
``neutron-ovn-metadata-agent`` packages (RHEL/Fedora).
``neutron-ovn-agent`` packages (RHEL/Fedora).
#. Install the ``ovn-host``, ``openvswitch-switch`` and
``neutron-ovn-metadata-agent`` packages (Ubuntu/Debian).
``neutron-ovn-agent`` packages (Ubuntu/Debian).
#. Start the OVS service.
@@ -356,13 +356,19 @@ Each compute node runs the OVS and ``ovn-controller`` services. The
Replace ``IP_ADDRESS`` with the IP address of the overlay network
interface on the compute node.
#. Start the ``ovn-controller`` and ``neutron-ovn-metadata-agent`` services.
#. Start the ``ovn-controller`` and ``neutron-ovn-agent`` services.
.. note::
Ensure the ``neutron-ovn-agent`` configuration includes
``extensions = metadata`` in the ``[ovn]`` section to enable the
metadata service on each compute node.
Using the *systemd* unit:
.. code-block:: console
# systemctl start ovn-controller neutron-ovn-metadata-agent
# systemctl start ovn-controller neutron-ovn-agent
Verify operation
----------------
@@ -1,6 +0,0 @@
[DEFAULT]
output_file = etc/neutron_ovn_metadata_agent.ini.sample
wrap_width = 79
namespace = neutron.ovn.metadata.agent
namespace = oslo.log
+4 -8
View File
@@ -213,17 +213,13 @@ class OVNNeutronAgent(service.Service):
def _cleanup_previous_tags(self):
"""Remove any existing tag related to the OVN Metadata agent
The OVN Metadata agent is deprecated and marked for removal in 2026.2.
The OVN Metadata agent was removed in 2026.2.
This code should stay during the following SLURP release (2027.1) and
be removed in the next release (2027.2).
While both agents can provide the same functionality (OVN Metadata
agent and OVN agent with the metadata extension), it is needed to
provide a cleanup method for any leftover tag from the other agent.
"""
metadata_keys = (ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY,
ovn_const.OVN_AGENT_METADATA_DESC_KEY,
ovn_const.OVN_AGENT_METADATA_ID_KEY)
metadata_keys = ('neutron:ovn-metadata-sb-cfg',
'neutron:description-metadata',
'neutron:ovn-metadata-id')
self.sb_idl.db_remove(
'Chassis_Private', self.chassis, 'external_ids',
*metadata_keys, if_exists=True).execute(check_error=True)
@@ -43,11 +43,8 @@ class OVNExtensionEventEmptyExtensionName(exceptions.NeutronException):
class OVNExtensionEvent(metaclass=abc.ABCMeta):
"""Implements a method to retrieve the correct caller agent
The events inheriting from this class could be called from the OVN metadata
agent or as part of an extension of the OVN agent ("metadata" extension,
for example). In future releases, the OVN metadata agent will be superseded
by the OVN agent (with the "metadata" extension) and this class removed,
keeping only the compatibility with the OVN agent (to be removed in C+2).
The events inheriting from this class are called as part of an extension
of the OVN agent (e.g. the "metadata" extension).
"""
def __init__(self, *args, extension_name=None, **kwargs):
@@ -63,8 +60,7 @@ class OVNExtensionEvent(metaclass=abc.ABCMeta):
def agent(self):
"""This method provide support for the OVN agent
This event can be used in the OVN metadata agent and in the OVN
agent metadata extension.
This event can be used in the OVN agent metadata extension.
"""
if not self._agent_or_extension:
if isinstance(self._agent, service.Service):
-214
View File
@@ -1,214 +0,0 @@
# Copyright 2024 Red Hat, Inc.
#
# 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 collections
import functools
import re
import threading
from oslo_config import cfg
from oslo_log import log
from ovsdbapp.backend.ovs_idl import event as row_event
from ovsdbapp.backend.ovs_idl import vlog
from neutron.agent.linux import external_process
from neutron.agent.ovn.extensions import extension_manager
from neutron.agent.ovn.metadata import agent as metadata_agent
from neutron.agent.ovn.metadata import server_socket as metadata_server
from neutron.common.ovn import constants as ovn_const
from neutron.conf.agent.database import agents_db
from neutron.conf.agent.metadata import config as meta_conf
from neutron.conf.agent.ovn.metadata import config as ovn_meta
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
LOG = log.getLogger(__name__)
EXT_NAME = 'metadata'
agents_db.register_db_agents_opts()
_SYNC_STATE_LOCK = threading.RLock()
CHASSIS_METADATA_LOCK = 'chassis_metadata_lock'
SB_IDL_TABLES = ['Encap',
'Port_Binding',
'Datapath_Binding',
'SB_Global',
'Chassis',
'Chassis_Private',
]
NS_PREFIX = ovn_const.OVN_METADATA_PREFIX
MAC_PATTERN = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
OVN_VIF_PORT_TYPES = (
"", ovn_const.LSP_TYPE_EXTERNAL, ovn_const.LSP_TYPE_LOCALPORT)
MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac',
'ip_addresses',
'logical_port'])
def _sync_lock(f):
"""Decorator to block all operations for a global sync call."""
@functools.wraps(f)
def wrapped(*args, **kwargs):
with _SYNC_STATE_LOCK:
return f(*args, **kwargs)
return wrapped
class ChassisPrivateCreateEvent(extension_manager.OVNExtensionEvent,
row_event.RowEvent):
"""Row create event - Chassis name == our_chassis."""
def __init__(self, ovn_agent):
self._first_time = True
events = (self.ROW_CREATE,)
super().__init__(events, 'Chassis_Private', None,
extension_name='metadata')
self._agent = ovn_agent
self.conditions = (('name', '=', self._agent.chassis),)
self.event_name = self.__class__.__name__
def run(self, event, row, old):
if self._first_time:
self._first_time = False
return
# Re-register the OVN agent with the local chassis in case its
# entry was re-created (happens when restarting the ovn-controller)
self.agent._update_chassis_private_config()
self.agent.sync()
class MetadataExtension(extension_manager.OVNAgentExtension,
metadata_agent.MetadataAgent):
def __init__(self):
super().__init__(conf=cfg.CONF)
vlog.use_python_logger(max_level=config.get_ovn_ovsdb_log_level())
self._process_monitor = None
self._proxy = None
# We'll restart all haproxy instances upon start so that they honor
# any potential changes in their configuration.
self.restarted_metadata_proxy_set = set()
@staticmethod
def _register_config_options():
ovn_meta.register_meta_conf_opts(meta_conf.SHARED_OPTS)
ovn_meta.register_meta_conf_opts(
meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_PROXY_HANDLER_OPTS)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_RATE_LIMITING_OPTS,
group=meta_conf.RATE_LIMITING_GROUP)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_HAPROXY_OPTS,
group=meta_conf.HAPROXY_GROUP)
def initialize(self, *args):
self._register_config_options()
self._process_monitor = external_process.ProcessMonitor(
config=self.agent_api.conf, resource_type='metadata')
@property
def name(self):
return 'Metadata OVN agent extension'
@property
def ovs_idl_events(self):
return []
@property
def nb_idl_tables(self):
return []
@property
def nb_idl_events(self):
return []
@property
def sb_idl_tables(self):
return SB_IDL_TABLES
@property
def sb_idl_events(self):
return [metadata_agent.PortBindingUpdatedEvent,
metadata_agent.PortBindingDeletedEvent,
metadata_agent.PortBindingCreateWithChassis,
ChassisPrivateCreateEvent,
]
# NOTE(ralonsoh): the following properties are needed during the migration
# to the Metadata agent to the OVN agent, while sharing the code with
# ``metadata_agent.MetadataAgent``
@property
def nb_idl(self):
return self.agent_api.nb_idl
@nb_idl.setter
def nb_idl(self, val):
self.agent_api.nb_idl = val
@property
def sb_idl(self):
return self.agent_api.sb_idl
@sb_idl.setter
def sb_idl(self, val):
self.agent_api.sb_idl = val
@property
def ovs_idl(self):
return self.agent_api.ovs_idl
@property
def conf(self):
return self.agent_api.conf
@property
def chassis(self):
return self.agent_api.chassis
@property
def ovn_bridge(self):
return self.agent_api.ovn_bridge
@_sync_lock
def resync(self):
"""Resync the Metadata OVN agent extension.
Reload the configuration and sync the agent again.
"""
self.agent_api.load_config()
self._update_chassis_private_config()
self.sync()
def start(self):
self._load_config()
# Launch the server that will act as a proxy between the VM's and Nova.
self._proxy = metadata_server.UnixDomainMetadataProxy(
self.agent_api.conf, self.agent_api.chassis,
sb_idl=self.agent_api.sb_idl)
self._proxy.run()
# Do the initial sync.
self.sync(provision=False)
# Register the agent with its corresponding Chassis
self._update_chassis_private_config()
# Start the metadata server.
proxy_thread = threading.Thread(target=self._proxy.wait)
proxy_thread.start()
# Raise the "is_started" flag.
self._is_started = True
@@ -14,7 +14,7 @@
import collections
import functools
import secrets
import re
import threading
import uuid
@@ -30,22 +30,33 @@ from neutron.agent.linux import external_process
from neutron.agent.linux import ip_lib
from neutron.agent.linux import iptables_manager
from neutron.agent.ovn.extensions import extension_manager
from neutron.agent.ovn.metadata import driver as metadata_driver
from neutron.agent.ovn.metadata import ovsdb
from neutron.agent.ovn.metadata import server_socket as metadata_server
from neutron.agent.ovn.extensions.metadata import driver as meta_driver
from neutron.agent.ovn.extensions.metadata import server_socket as meta_server
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils as ovn_utils
from neutron.common import utils
from neutron.conf.agent.database import agents_db
from neutron.conf.agent.metadata import config as meta_conf
from neutron.conf.agent.ovn.metadata import config as ovn_meta
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
LOG = log.getLogger(__name__)
EXT_NAME = 'metadata'
agents_db.register_db_agents_opts()
_SYNC_STATE_LOCK = threading.RLock()
CHASSIS_METADATA_LOCK = 'chassis_metadata_lock'
SB_IDL_TABLES = ['Encap',
'Port_Binding',
'Datapath_Binding',
'SB_Global',
'Chassis',
'Chassis_Private',
]
NS_PREFIX = ovn_const.OVN_METADATA_PREFIX
MAC_PATTERN = re.compile(r'([0-9A-F]{2}[:-]){5}([0-9A-F]{2})', re.I)
OVN_VIF_PORT_TYPES = (
"", ovn_const.LSP_TYPE_EXTERNAL, ovn_const.LSP_TYPE_LOCALPORT)
@@ -55,7 +66,8 @@ MetadataPortInfo = collections.namedtuple('MetadataPortInfo', ['mac',
'mtu',
])
OVN_METADATA_UUID_NAMESPACE = uuid.UUID('d34bf9f6-da32-4871-9af8-15a4626b41ab')
OVN_METADATA_UUID_NAMESPACE = uuid.UUID(
'd34bf9f6-da32-4871-9af8-15a4626b41ab')
def _sync_lock(f):
@@ -265,73 +277,6 @@ class PortBindingDeletedEvent(PortBindingEvent):
return True
class ChassisPrivateCreateEvent(row_event.RowEvent):
"""Row create event - Chassis name == our_chassis.
On connection, we get a dump of all chassis so if we catch a creation
of our own chassis it has to be a reconnection. In this case, we need
to do a full sync to make sure that we capture all changes while the
connection to OVSDB was down.
"""
def __init__(self, agent):
self._extension = None
self.first_time = True
events = (self.ROW_CREATE,)
super().__init__(events, 'Chassis_Private', None)
self.agent = agent
self.conditions = (('name', '=', self.agent.chassis),)
self.event_name = self.__class__.__name__
def run(self, event, row, old):
if self.first_time:
self.first_time = False
else:
# NOTE(lucasagomes): Re-register the ovn metadata agent
# with the local chassis in case its entry was re-created
# (happens when restarting the ovn-controller)
self.agent.register_metadata_agent()
LOG.info("Connection to OVSDB established, doing a full sync")
self.agent.sync()
class SbGlobalUpdateEvent(row_event.RowEvent):
"""Row update event on SB_Global table."""
def __init__(self, agent):
table = 'SB_Global'
events = (self.ROW_UPDATE,)
super().__init__(events, table, None)
self.agent = agent
self.event_name = self.__class__.__name__
self.first_run = True
def run(self, event, row, old):
def _update_chassis(self, row):
self.agent.sb_idl.db_set(
'Chassis_Private', self.agent.chassis, ('external_ids', {
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY:
str(row.nb_cfg)})).execute()
delay = 0
if self.first_run:
self.first_run = False
else:
# We occasionally see port binding failed errors due to
# the ml2 driver refusing to bind the port to a dead agent.
# if all agents heartbeat at the same time, they will all
# cause a load spike on the server. To mitigate that we
# need to spread out the load by introducing a random delay.
# clamp the max delay between 3 and 10 seconds.
max_delay = max(min(cfg.CONF.agent_down_time // 3, 10), 3)
delay = secrets.SystemRandom().randint(0, max_delay)
LOG.debug("Delaying updating chassis table for %s seconds", delay)
timer = threading.Timer(delay, _update_chassis, [self, row])
timer.start()
class MetadataAgent:
def __init__(self, conf):
@@ -383,111 +328,12 @@ class MetadataAgent:
self.chassis, self.chassis_id, self.ovn_bridge)
def _update_chassis_private_config(self):
"""Update the Chassis_Private register information
This method should be called once the Metadata Agent has been
registered (method ``register_metadata_agent`` has been called) and
the corresponding Chassis_Private register has been created/updated.
"""
"""Update the Chassis_Private register information."""
external_ids = {ovn_const.OVN_AGENT_OVN_BRIDGE: self.ovn_bridge}
self.sb_idl.db_set(
'Chassis_Private', self.chassis,
('external_ids', external_ids)).execute(check_error=True)
def _update_metadata_sb_cfg_key(self):
"""Update the Chassis_Private nb_cfg information in external_ids
This method should be called once the Metadata Agent has been
registered (method ``register_metadata_agent`` has been called) and
the corresponding Chassis_Private register has been created/updated
and chassis private config has been updated.
"""
nb_cfg = self.sb_idl.db_get('Chassis_Private',
self.chassis, 'nb_cfg').execute()
external_ids = {ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: str(nb_cfg)}
self.sb_idl.db_set(
'Chassis_Private', self.chassis,
('external_ids', external_ids)).execute(check_error=True)
def _cleanup_previous_tags(self):
"""Remove any existing tag related to the OVN agent
The OVN Metadata agent is deprecated and marked for removal in 2026.2.
While both agents can provide the same functionality (OVN Metadata
agent and OVN agent with the metadata extension), it is needed to
provide a cleanup method for any leftover tag from the other agent.
"""
metadata_keys = (ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY,
ovn_const.OVN_AGENT_NEUTRON_DESC_KEY,
ovn_const.OVN_AGENT_NEUTRON_ID_KEY)
self.sb_idl.db_remove(
'Chassis_Private', self.chassis, 'external_ids',
*metadata_keys, if_exists=True).execute(check_error=True)
@_sync_lock
def resync(self):
"""Resync the agent.
Reload the configuration and sync the agent again.
"""
self._load_config()
self._update_chassis_private_config()
self._update_metadata_sb_cfg_key()
self.sync()
def start(self):
# Open the connection to OVS database
self.ovs_idl = ovsdb.MetadataAgentOvsIdl().start()
self._load_config()
tables = ('Encap', 'Port_Binding', 'Datapath_Binding', 'SB_Global',
'Chassis', 'Chassis_Private')
events = (PortBindingUpdatedEvent(self),
PortBindingCreateWithChassis(self),
PortBindingDeletedEvent(self),
SbGlobalUpdateEvent(self),
ChassisPrivateCreateEvent(self),
)
self._post_fork_event.clear()
self.sb_idl = ovsdb.MetadataAgentOvnSbIdl(
chassis=self._chassis, tables=tables, events=events).start()
# Now IDL connections can be safely used.
self._post_fork_event.set()
# Launch the server that will act as a proxy between the VM's and Nova.
self._proxy = metadata_server.UnixDomainMetadataProxy(
self.conf, self._chassis, sb_idl=self.sb_idl)
self._proxy.run()
# Do the initial sync.
# Provisioning handled by PortBindingCreateWithChassis
self.sync(provision=False)
# Register the agent with its corresponding Chassis
self._cleanup_previous_tags()
self.register_metadata_agent()
self._update_chassis_private_config()
self._update_metadata_sb_cfg_key()
LOG.warning(
'The OVN Metadata agent is deprecated in favor of the OVN agent '
'with the metadata extension. It has been deprecated in 2025.2 '
'and will be removed in 2026.2')
self._proxy.wait()
@ovn_utils.retry()
def register_metadata_agent(self):
# NOTE(lucasagomes): db_add() will not overwrite the UUID if
# it's already set.
# Generate unique, but consistent metadata id for chassis name
agent_id = uuid.uuid5(self.chassis_id, 'metadata_agent')
ext_ids = {ovn_const.OVN_AGENT_METADATA_ID_KEY: str(agent_id)}
self.sb_idl.db_add('Chassis_Private', self.chassis, 'external_ids',
ext_ids).execute(check_error=True)
def _get_own_chassis_name(self):
"""Return the external_ids:system-id value of the Open_vSwitch table.
@@ -587,7 +433,7 @@ class MetadataAgent:
LOG.info("Cleaning up %s namespace which is not needed anymore",
namespace)
metadata_driver.MetadataDriver.destroy_monitored_metadata_proxy(
meta_driver.MetadataDriver.destroy_monitored_metadata_proxy(
self._process_monitor, net_name, self.conf, namespace)
veth_name = self._get_veth_name(net_name)
@@ -859,8 +705,153 @@ class MetadataAgent:
bind_address_v6 = n_const.METADATA_V6_IP
# Spawn metadata proxy if it's not already running.
metadata_driver.MetadataDriver.spawn_monitored_metadata_proxy(
meta_driver.MetadataDriver.spawn_monitored_metadata_proxy(
self._process_monitor, namespace, n_const.METADATA_PORT,
self.conf, bind_address=n_const.METADATA_V4_IP,
network_id=net_name, bind_address_v6=bind_address_v6,
bind_interface=veth_name[1])
class ChassisPrivateCreateEvent(extension_manager.OVNExtensionEvent,
row_event.RowEvent):
"""Row create event - Chassis name == our_chassis."""
def __init__(self, ovn_agent):
self._first_time = True
events = (self.ROW_CREATE,)
super().__init__(events, 'Chassis_Private', None,
extension_name='metadata')
self._agent = ovn_agent
self.conditions = (('name', '=', self._agent.chassis),)
self.event_name = self.__class__.__name__
def run(self, event, row, old):
if self._first_time:
self._first_time = False
return
# Re-register the OVN agent with the local chassis in case its
# entry was re-created (happens when restarting the ovn-controller)
self.agent._update_chassis_private_config()
self.agent.sync()
class MetadataExtension(extension_manager.OVNAgentExtension,
MetadataAgent):
def __init__(self):
super().__init__(conf=cfg.CONF)
vlog.use_python_logger(max_level=config.get_ovn_ovsdb_log_level())
self._process_monitor = None
self._proxy = None
# We'll restart all haproxy instances upon start so that they honor
# any potential changes in their configuration.
self.restarted_metadata_proxy_set = set()
@staticmethod
def _register_config_options():
ovn_meta.register_meta_conf_opts(meta_conf.SHARED_OPTS)
ovn_meta.register_meta_conf_opts(
meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_PROXY_HANDLER_OPTS)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_RATE_LIMITING_OPTS,
group=meta_conf.RATE_LIMITING_GROUP)
ovn_meta.register_meta_conf_opts(meta_conf.METADATA_HAPROXY_OPTS,
group=meta_conf.HAPROXY_GROUP)
def initialize(self, *args):
self._register_config_options()
self._process_monitor = external_process.ProcessMonitor(
config=self.agent_api.conf, resource_type='metadata')
@property
def name(self):
return 'Metadata OVN agent extension'
@property
def ovs_idl_events(self):
return []
@property
def nb_idl_tables(self):
return []
@property
def nb_idl_events(self):
return []
@property
def sb_idl_tables(self):
return SB_IDL_TABLES
@property
def sb_idl_events(self):
return [PortBindingUpdatedEvent,
PortBindingDeletedEvent,
PortBindingCreateWithChassis,
ChassisPrivateCreateEvent,
]
@property
def nb_idl(self):
return self.agent_api.nb_idl
@nb_idl.setter
def nb_idl(self, val):
self.agent_api.nb_idl = val
@property
def sb_idl(self):
return self.agent_api.sb_idl
@sb_idl.setter
def sb_idl(self, val):
self.agent_api.sb_idl = val
@property
def ovs_idl(self):
return self.agent_api.ovs_idl
@property
def conf(self):
return self.agent_api.conf
@property
def chassis(self):
return self.agent_api.chassis
@property
def ovn_bridge(self):
return self.agent_api.ovn_bridge
@_sync_lock
def resync(self):
"""Resync the Metadata OVN agent extension.
Reload the configuration and sync the agent again.
"""
self.agent_api.load_config()
self._update_chassis_private_config()
self.sync()
def start(self):
self._load_config()
# Launch the server that will act as a proxy between the VM's and Nova.
self._proxy = meta_server.UnixDomainMetadataProxy(
self.agent_api.conf, self.agent_api.chassis,
sb_idl=self.agent_api.sb_idl)
self._proxy.run()
# Do the initial sync.
self.sync(provision=False)
# Register the agent with its corresponding Chassis
self._update_chassis_private_config()
# Start the metadata server.
proxy_thread = threading.Thread(target=self._proxy.wait)
proxy_thread.start()
# Raise the "is_started" flag.
self._is_started = True
-80
View File
@@ -1,80 +0,0 @@
# Copyright 2017 Red Hat, Inc.
#
# 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 oslo_log import log
from ovs.db import idl
from ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.schema.open_vswitch import impl_idl as idl_ovs
from neutron.common.ovn import utils as ovn_utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as config
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import impl_idl_ovn
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import ovsdb_monitor
LOG = log.getLogger(__name__)
class MetadataAgentOvnSbIdl(ovsdb_monitor.OvnIdl):
SCHEMA = 'OVN_Southbound'
def __init__(self, chassis=None, events=None, tables=None):
connection_string = config.get_ovn_sb_connection()
ovsdb_monitor._check_and_set_ssl_files(self.SCHEMA)
helper = self._get_ovsdb_helper(connection_string)
if tables is None:
tables = ('Chassis', 'Encap', 'Port_Binding', 'Datapath_Binding',
'SB_Global')
for table in tables:
helper.register_table(table)
super().__init__(None, connection_string, helper, leader_only=False)
if chassis:
for table in set(tables).intersection({'Chassis',
'Chassis_Private'}):
self.set_table_condition(table, [['name', '==', chassis]])
if events:
self.notify_handler.watch_events(events)
@ovn_utils.retry(max_=180)
def _get_ovsdb_helper(self, connection_string):
return idlutils.get_schema_helper(connection_string, self.SCHEMA)
@ovn_utils.retry()
def start(self):
LOG.info('Getting OvsdbSbOvnIdl for MetadataAgent with retry')
conn = connection.Connection(
self, timeout=config.get_ovn_ovsdb_timeout())
return impl_idl_ovn.OvsdbSbOvnIdl(conn)
def post_connect(self):
pass
class MetadataAgentOvsIdl:
def start(self):
connection_string = config.cfg.CONF.ovs.ovsdb_connection
helper = idlutils.get_schema_helper(connection_string,
'Open_vSwitch')
tables = ('Open_vSwitch', 'Bridge', 'Port', 'Interface')
for table in tables:
helper.register_table(table)
ovs_idl = idl.Idl(
connection_string, helper,
probe_interval=config.get_ovn_ovsdb_probe_interval())
conn = connection.Connection(
ovs_idl, timeout=config.cfg.CONF.ovs.ovsdb_connection_timeout)
return idl_ovs.OvsdbIdl(conn)
-47
View File
@@ -1,47 +0,0 @@
# Copyright 2017 OpenStack Foundation.
#
# 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 sys
from oslo_config import cfg
from oslo_log import log as logging
from neutron.agent.ovn.metadata import agent
from neutron.common import config
from neutron.common import utils
from neutron.conf.agent.metadata import config as meta
from neutron.conf.agent.ovn.metadata import config as ovn_meta
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
LOG = logging.getLogger(__name__)
def main():
config.register_common_config_options()
ovn_conf.register_opts()
ovn_meta.register_meta_conf_opts(meta.SHARED_OPTS)
ovn_meta.register_meta_conf_opts(meta.UNIX_DOMAIN_METADATA_PROXY_OPTS)
ovn_meta.register_meta_conf_opts(meta.METADATA_PROXY_HANDLER_OPTS)
ovn_meta.register_meta_conf_opts(meta.METADATA_RATE_LIMITING_OPTS,
group=meta.RATE_LIMITING_GROUP)
ovn_meta.register_meta_conf_opts(meta.METADATA_HAPROXY_OPTS,
group=meta.HAPROXY_GROUP)
ovn_meta.register_meta_conf_opts(ovn_meta.OVS_OPTS, group='ovs')
config.init(sys.argv[1:])
config.setup_logging()
config.setup_gmr()
ovn_meta.setup_privsep()
utils.log_opt_values(LOG)
agt = agent.MetadataAgent(cfg.CONF)
agt.start()
-24
View File
@@ -1,24 +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.
import setproctitle
from neutron.agent.ovn import metadata_agent
from neutron_lib import constants
def main():
proctitle = "{} ({})".format(
constants.AGENT_PROCESS_OVN_METADATA, setproctitle.getproctitle())
setproctitle.setproctitle(proctitle)
metadata_agent.main()
-5
View File
@@ -87,23 +87,18 @@ OVN_LB_HM_PORT_DISTRIBUTED = 'ovn-lb-hm:distributed'
# Agent extension constants
OVN_AGENT_DESC_KEY = 'neutron:description'
OVN_AGENT_METADATA_SB_CFG_KEY = 'neutron:ovn-metadata-sb-cfg'
OVN_AGENT_METADATA_DESC_KEY = 'neutron:description-metadata'
OVN_AGENT_METADATA_ID_KEY = 'neutron:ovn-metadata-id'
OVN_AGENT_NEUTRON_SB_CFG_KEY = 'neutron:ovn-neutron-agent-sb-cfg'
OVN_AGENT_NEUTRON_DESC_KEY = 'neutron:description-neutron-agent'
OVN_AGENT_NEUTRON_ID_KEY = 'neutron:ovn-neutron-agent-id'
OVN_AGENT_OVN_BRIDGE = 'neutron:ovn-bridge'
OVN_CONTROLLER_AGENT = 'OVN Controller agent'
OVN_CONTROLLER_GW_AGENT = 'OVN Controller Gateway agent'
OVN_METADATA_AGENT = 'OVN Metadata agent'
OVN_NEUTRON_AGENT = 'OVN Neutron agent'
OVN_CONTROLLER_TYPES = (OVN_CONTROLLER_AGENT,
OVN_CONTROLLER_GW_AGENT,
)
OVN_AGENT_TYPES = (OVN_CONTROLLER_AGENT,
OVN_CONTROLLER_GW_AGENT,
OVN_METADATA_AGENT,
OVN_NEUTRON_AGENT,
)
-45
View File
@@ -12,53 +12,8 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import shlex
from oslo_config import cfg
from oslo_privsep import priv_context
from neutron._i18n import _
from neutron.conf.agent.metadata import config as meta_conf
OVS_OPTS = [
cfg.StrOpt('ovsdb_connection',
default='unix:/usr/local/var/run/openvswitch/db.sock',
regex=r'^(tcp|ssl|unix):.+',
help=_('The connection string for the native OVSDB backend. '
'Use tcp:IP:PORT for TCP connections. '
'Use unix:FILE for unix domain socket connections.')),
cfg.IntOpt('ovsdb_connection_timeout',
default=180,
min=1,
help=_('Timeout in seconds for the OVSDB '
'connection transaction'))
]
def register_meta_conf_opts(opts, cfg=cfg.CONF, group=None):
cfg.register_opts(opts, group=group)
def list_metadata_agent_opts():
return [
('DEFAULT',
itertools.chain(
meta_conf.SHARED_OPTS,
meta_conf.METADATA_PROXY_HANDLER_OPTS,
meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS)
),
('ovs', OVS_OPTS),
(meta_conf.RATE_LIMITING_GROUP,
meta_conf.METADATA_RATE_LIMITING_OPTS),
(meta_conf.HAPROXY_GROUP,
meta_conf.METADATA_HAPROXY_OPTS),
]
def get_root_helper(conf):
return conf.AGENT.root_helper
def setup_privsep():
priv_context.init(root_helper=shlex.split(get_root_helper(cfg.CONF)))
@@ -179,41 +179,6 @@ class ControllerGatewayAgent(ControllerAgent):
self.__class__ = ControllerAgent
class MetadataAgent(NeutronAgent):
agent_type = ovn_const.OVN_METADATA_AGENT
binary = 'neutron-ovn-metadata-agent'
@property
def alive(self):
# If ovn-controller is down, then metadata agent is down even
# if the metadata-agent binary is updating external_ids.
try:
if not AgentCache().get(self.chassis_private.name).alive:
return False
except KeyError:
return False
return super().alive
@property
def nb_cfg(self):
return int(self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0))
@staticmethod
def id_from_chassis_private(chassis_private):
return chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_ID_KEY)
@property
def agent_id(self):
return self.id_from_chassis_private(self.chassis_private)
@property
def description(self):
return self.chassis_private.external_ids.get(
ovn_const.OVN_AGENT_METADATA_DESC_KEY, '')
class OVNNeutronAgent(NeutronAgent):
agent_type = ovn_const.OVN_NEUTRON_AGENT
binary = 'neutron-ovn-agent'
@@ -300,9 +265,6 @@ class AgentCache:
if external_ids.get(ovn_const.OVN_AGENT_NEUTRON_ID_KEY):
self.update(ovn_const.OVN_NEUTRON_AGENT, ch_private,
clear_down=True)
elif external_ids.get(ovn_const.OVN_AGENT_METADATA_ID_KEY):
self.update(ovn_const.OVN_METADATA_AGENT, ch_private,
clear_down=True)
LOG.debug('Agents populated in the agent cache:')
for a in self.agents.values():
LOG.debug('- Agent type: %s, hostname: %s, name: %s',
@@ -1071,9 +1071,7 @@ class OvsdbSbOvnIdl(sb_impl_idl.OvnSbApiIdlImpl, Backend):
def set_chassis_neutron_description(self, chassis, description,
agent_type):
desc_key = (ovn_const.OVN_AGENT_METADATA_DESC_KEY
if agent_type == ovn_const.OVN_METADATA_AGENT else
ovn_const.OVN_AGENT_DESC_KEY)
desc_key = ovn_const.OVN_AGENT_DESC_KEY
return cmd.UpdateChassisExtIdsCommand(
self, chassis, {desc_key: description}, if_exists=False)
@@ -503,21 +503,13 @@ class ChassisOVNAgentWriteEvent(ChassisAgentEvent):
@staticmethod
def _agent_sb_cfg(row):
external_ids = row.external_ids
# Try the OVN agent SB cfg first, then fallback to the OVN Metadata
# agent
ovn_sb_cfg = external_ids.get(ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY)
if ovn_sb_cfg:
return int(ovn_sb_cfg)
# NOTE(ralonsoh): to remove when the OVN Metadata agent is removed.
return int(
external_ids.get(ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, -1))
external_ids.get(ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY, -1))
@staticmethod
def agent_id(row):
external_ids = row.external_ids
# NOTE(ralonsoh): to update when the OVN Metadata agent is removed.
return (external_ids.get(ovn_const.OVN_AGENT_NEUTRON_ID_KEY) or
external_ids.get(ovn_const.OVN_AGENT_METADATA_ID_KEY))
return external_ids.get(ovn_const.OVN_AGENT_NEUTRON_ID_KEY)
def match_fn(self, event, row, old=None):
if not self.agent_id(row):
@@ -545,15 +537,8 @@ class ChassisOVNAgentWriteEvent(ChassisAgentEvent):
return False
def run(self, event, row, old):
external_ids = row.external_ids
if external_ids.get(ovn_const.OVN_AGENT_NEUTRON_ID_KEY):
n_agent.AgentCache().update(ovn_const.OVN_NEUTRON_AGENT, row,
clear_down=True)
else:
# NOTE(ralonsoh): to remove when the OVN Metadata agent is
# removed.
n_agent.AgentCache().update(ovn_const.OVN_METADATA_AGENT, row,
clear_down=True)
n_agent.AgentCache().update(ovn_const.OVN_NEUTRON_AGENT, row,
clear_down=True)
class PortBindingChassisEvent(row_event.RowEvent):
@@ -23,8 +23,8 @@ from ovsdbapp.backend.ovs_idl import idlutils
from neutron.agent.ovn.agent import ovn_neutron_agent
from neutron.agent.ovn.agent import ovsdb as agent_ovsdb
from neutron.agent.ovn.metadata import agent as metadata_agent
from neutron.agent.ovn.metadata import server_socket
from neutron.agent.ovn.extensions import metadata as metadata_agent
from neutron.agent.ovn.extensions.metadata import server_socket
from neutron.agent.ovsdb import impl_idl
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils as n_utils
@@ -212,9 +212,9 @@ class TestOVNNeutronAgentMetadataExtension(TestOVNNeutronAgentBase):
def test__cleanup_previous_tags(self):
external_ids = {
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: '1',
ovn_const.OVN_AGENT_METADATA_DESC_KEY: 'description',
ovn_const.OVN_AGENT_METADATA_ID_KEY: uuidutils.generate_uuid()}
'neutron:ovn-metadata-sb-cfg': '1',
'neutron:description-metadata': 'description',
'neutron:ovn-metadata-id': uuidutils.generate_uuid()}
self.ovn_agent.sb_idl.db_set(
'Chassis_Private', self.ovn_agent.chassis,
('external_ids', external_ids)).execute(check_error=True)
@@ -227,12 +227,9 @@ class TestOVNNeutronAgentMetadataExtension(TestOVNNeutronAgentBase):
external_ids = self.ovn_agent.sb_idl.db_get(
'Chassis_Private', self.ovn_agent.chassis,
'external_ids').execute(check_error=True)
for _key in (ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY,
ovn_const.OVN_AGENT_METADATA_DESC_KEY,
ovn_const.OVN_AGENT_METADATA_ID_KEY):
for _key in ('neutron:ovn-metadata-sb-cfg',
'neutron:description-metadata',
'neutron:ovn-metadata-id'):
self.assertNotIn(_key, external_ids)
# Just in case, check that we are NOT deleting the needed tags.
# NOTE(ralonsoh): OVN_AGENT_METADATA_ID_KEY is missing here, there is
# a bug to add it (LP#2118876)
self.assertIn(ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY, external_ids)
@@ -1,716 +0,0 @@
# Copyright 2020 Red Hat, Inc.
# 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 re
from unittest import mock
from neutron_lib import constants
from oslo_config import fixture as fixture_config
from oslo_utils import uuidutils
from ovsdbapp.backend.ovs_idl import event
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.tests.functional.schema.ovn_southbound import event as test_event
import testtools
from neutron.agent.linux import iptables_manager
from neutron.agent.ovn.metadata import agent
from neutron.agent.ovn.metadata import ovsdb
from neutron.agent.ovn.metadata import server_socket as metadata_server
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils as n_utils
from neutron.conf.agent.metadata import config as meta_config
from neutron.conf.agent.ovn.metadata import config as meta_config_ovn
from neutron.tests.common import net_helpers
from neutron.tests.functional import base
from neutron.tests.functional.resources.ovsdb import events
AGENT_CHASSIS_TABLE = 'Chassis_Private'
class NoDatapathProvision(Exception):
pass
class MetadataAgentHealthEvent(event.WaitEvent):
event_name = 'MetadataAgentHealthEvent'
def __init__(self, chassis, sb_cfg, timeout=5):
self.chassis = chassis
self.sb_cfg = sb_cfg
super().__init__(
(self.ROW_UPDATE,),
AGENT_CHASSIS_TABLE,
(('name', '=', self.chassis),),
timeout=timeout)
def matches(self, event, row, old=None):
if not super().matches(event, row, old):
return False
return int(row.external_ids.get(
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY, 0)) >= self.sb_cfg
class PortBindingUpdateEvent(event.WaitEvent):
def __init__(self, lsp, timeout=5):
table = 'Port_Binding'
events = (self.ROW_UPDATE,)
conditions = (('logical_port', '=', lsp),)
super().__init__(events, table, conditions, timeout=timeout)
class TestMetadataAgent(base.TestOVNFunctionalBase):
OVN_BRIDGE = 'br-int'
FAKE_CHASSIS_HOST = 'ovn-host-fake'
def setUp(self):
super().setUp()
self.handler = self.sb_api.idl.notify_handler
# We only have OVN NB and OVN SB running for functional tests
self.mock_ovsdb_idl = mock.Mock()
mock_metadata_instance = mock.Mock()
mock_metadata_instance.start.return_value = self.mock_ovsdb_idl
mock_metadata = mock.patch.object(
ovsdb, 'MetadataAgentOvsIdl').start()
mock_metadata.return_value = mock_metadata_instance
self._mock_get_ovn_br = mock.patch.object(
agent.MetadataAgent,
'_get_ovn_bridge',
return_value=self.OVN_BRIDGE).start()
self.agent = self._start_metadata_agent()
self.addCleanup(self.agent._proxy.close)
def _start_metadata_agent(self):
conf = self.useFixture(fixture_config.Config()).conf
conf.register_opts(meta_config.SHARED_OPTS)
conf.register_opts(meta_config.UNIX_DOMAIN_METADATA_PROXY_OPTS)
conf.register_opts(meta_config.METADATA_PROXY_HANDLER_OPTS)
conf.register_opts(meta_config_ovn.OVS_OPTS, group='ovs')
meta_config_ovn.setup_privsep()
ovn_sb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('sb')
conf.set_override('ovn_sb_connection', [ovn_sb_db], group='ovn')
conf.set_override('metadata_workers', '0')
self.chassis_name = self.add_fake_chassis(self.FAKE_CHASSIS_HOST)
mock.patch.object(agent.MetadataAgent,
'_get_own_chassis_name',
return_value=self.chassis_name).start()
agt = agent.MetadataAgent(conf)
with mock.patch.object(metadata_server.UnixDomainMetadataProxy,
'wait'):
agt.start()
external_ids = agt.sb_idl.db_get(
'Chassis_Private', agt.chassis, 'external_ids').execute(
check_error=True)
self.assertEqual(external_ids[ovn_const.OVN_AGENT_OVN_BRIDGE],
self.OVN_BRIDGE)
self.assertEqual(
external_ids[ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY],
'0')
# Metadata agent will open connections to OVS and SB databases.
# Close connections to them when the test ends,
self.addCleanup(agt.ovs_idl.ovsdb_connection.stop)
self.addCleanup(agt.sb_idl.ovsdb_connection.stop)
return agt
def test_metadata_agent_healthcheck(self):
# Let's list the agents to force the nb_cfg to be bumped on NB
# db, which will automatically increment the nb_cfg counter on
# NB_Global and make ovn-controller copy it over to SB_Global. Upon
# this event, Metadata agent will update the external_ids on its
# Chassis row to signal that it's healthy.
row_event = MetadataAgentHealthEvent(self.chassis_name, 1)
self.handler.watch_event(row_event)
self.new_list_request('agents').get_response(self.api)
# If we do not time out waiting for the event, then we are assured
# that the metadata agent has populated the external_ids from the
# chassis with the nb_cfg, 1 revisions when listing the agents.
self.assertTrue(row_event.wait())
def _create_metadata_port(self, txn, lswitch_name, port_name=None):
mdt_port_name = port_name or 'ovn-mdt-' + uuidutils.generate_uuid()
txn.add(
self.nb_api.lsp_add(
lswitch_name,
mdt_port_name,
type=ovn_const.LSP_TYPE_LOCALPORT,
addresses='AA:AA:AA:AA:AA:AA 192.168.122.123',
external_ids={
ovn_const.OVN_CIDRS_EXT_ID_KEY: '192.168.122.123/24',
ovn_const.OVN_DEVID_EXT_ID_KEY:
ovn_const.OVN_METADATA_PREFIX + lswitch_name,
ovn_const.OVN_DEVICE_OWNER_EXT_ID_KEY:
constants.DEVICE_OWNER_DISTRIBUTED
}))
def _update_metadata_port_ip(self, metadata_port_name):
external_ids = {
ovn_const.OVN_CIDRS_EXT_ID_KEY: "192.168.122.2/24",
ovn_const.OVN_DEVID_EXT_ID_KEY:
ovn_const.OVN_METADATA_PREFIX + uuidutils.generate_uuid()
}
self.nb_api.set_lswitch_port(lport_name=metadata_port_name,
external_ids=external_ids).execute()
def _create_logical_switch_port(self, type_=None, addresses=None):
lswitch_name = 'ovn-' + uuidutils.generate_uuid()
lswitchport_name = 'ovn-port-' + uuidutils.generate_uuid()
# It may take some time to ovn-northd to translate from OVN NB DB to
# the OVN SB DB. Wait for port binding event to happen before binding
# the port to chassis.
pb_event = test_event.WaitForPortBindingEvent(lswitchport_name)
self.handler.watch_event(pb_event)
lswitch_port_columns = {}
if addresses:
lswitch_port_columns['addresses'] = addresses
if type_:
lswitch_port_columns['type'] = type_
with self.nb_api.transaction(check_error=True, log_errors=True) as txn:
txn.add(
self.nb_api.ls_add(lswitch_name))
txn.add(
self.nb_api.create_lswitch_port(
lswitchport_name, lswitch_name, **lswitch_port_columns))
self._create_metadata_port(txn, lswitch_name)
self.assertTrue(pb_event.wait())
return lswitchport_name, lswitch_name
def test_agent_resync_on_non_existing_bridge(self):
BR_NEW = 'br-new'
self._mock_get_ovn_br.return_value = BR_NEW
self.agent.ovs_idl.list_br.return_value.execute.return_value = [BR_NEW]
# The agent has initialized with br-int and above list_br doesn't
# return it, hence the agent should trigger reconfiguration and store
# new br-new value to its attribute.
self.assertEqual(self.OVN_BRIDGE, self.agent.ovn_bridge)
# NOTE: The IP address is specifically picked such that it fits the
# metadata port external_ids: { neutron:cidrs }. This is because agent
# will only trigger if the logical port is part of a neutron subnet
lswitchport_name, _ = self._create_logical_switch_port(
addresses='AA:AA:AA:AA:AA:AB 192.168.122.125'
)
# Trigger PortBindingChassisCreatedEvent
self.sb_api.lsp_bind(lswitchport_name, self.chassis_name).execute(
check_error=True, log_errors=True)
exc = Exception("Agent bridge hasn't changed from %s to %s "
"in 10 seconds after Port_Binding event" %
(self.agent.ovn_bridge, BR_NEW))
n_utils.wait_until_true(
lambda: BR_NEW == self.agent.ovn_bridge,
timeout=10,
exception=exc)
def _test_agent_events_prepare(self, lsp_type=None):
lswitchport_name, lswitch_name = self._create_logical_switch_port(
lsp_type)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
self.sb_api.lsp_bind(lswitchport_name, self.chassis_name).execute(
check_error=True, log_errors=True)
# Wait until port is bound
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath provisioning did not happen on port binding"))
return lswitchport_name, lswitch_name
def test_agent_unbind_port(self):
lswitchport_name, lswitch_name = self._test_agent_events_prepare()
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
self.sb_api.lsp_unbind(lswitchport_name).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath teardown did not happen after the port was "
"unbound"))
def _test_agent_delete_bound_external_port(self, lsp_type=None):
lswitchport_name, lswitch_name = self._test_agent_events_prepare(
lsp_type)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision,\
mock.patch.object(agent.LOG, 'warning') as m_log_warn:
self.nb_api.delete_lswitch_port(
lswitchport_name, lswitch_name).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath teardown did not happen after external port was "
"deleted"))
if lsp_type == ovn_const.LSP_TYPE_EXTERNAL:
m_log_warn.assert_not_called()
else:
m_log_warn.assert_called()
def test_agent_delete_bound_external_port(self):
self._test_agent_delete_bound_external_port(
lsp_type=ovn_const.LSP_TYPE_EXTERNAL)
def test_agent_delete_bound_nonexternal_port(self):
self._test_agent_delete_bound_external_port()
def test_agent_registration_at_chassis_create_event(self):
def check_for_metadata():
chassis = self.sb_api.lookup(
AGENT_CHASSIS_TABLE, self.chassis_name)
return ovn_const.OVN_AGENT_METADATA_ID_KEY in chassis.external_ids
exc = Exception('Chassis not created, %s is not in chassis '
'external_ids' % ovn_const.OVN_AGENT_METADATA_ID_KEY)
n_utils.wait_until_true(check_for_metadata, timeout=5, exception=exc)
# Delete Chassis and assert
chassis = self.sb_api.lookup('Chassis', self.chassis_name)
self.del_fake_chassis(chassis.name)
self.assertRaises(idlutils.RowNotFound, self.sb_api.lookup,
'Chassis', self.chassis_name)
# Re-add the Chassis
self.add_fake_chassis(self.FAKE_CHASSIS_HOST, name=self.chassis_name)
exc = Exception('Agent metadata failed to re-register itself '
'after the Chassis %s was re-created' %
self.chassis_name)
# Check if metadata agent was re-registered
chassis = self.sb_api.lookup('Chassis', self.chassis_name)
n_utils.wait_until_true(
check_for_metadata,
timeout=10,
exception=exc)
def test_agent_metadata_port_ip_update_event(self):
lswitch_name = 'ovn-' + uuidutils.generate_uuid()
mdt_port_name = 'ovn-mdt-' + uuidutils.generate_uuid()
mdt_pb_event = test_event.WaitForPortBindingEvent(mdt_port_name)
self.handler.watch_event(mdt_pb_event)
with self.nb_api.transaction(
check_error=True, log_errors=True) as txn:
txn.add(
self.nb_api.ls_add(lswitch_name))
self._create_metadata_port(txn, lswitch_name, mdt_port_name)
self.assertTrue(mdt_pb_event.wait())
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
self.sb_api.lsp_bind(mdt_port_name, self.chassis_name).execute(
check_error=True, log_errors=True)
# Wait until port is bound
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath provisioning did not happen on port binding"))
m_provision.reset_mock()
self._update_metadata_port_ip(mdt_port_name)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath provisioning not called after external ids was "
"changed"))
def test_agent_metadata_port_dhcp_reenable_event(self):
# Test the Port_Binding update event triggered by reenable DHCP after
# disable DHCP on the subnet where the metadata's port is located.
lswitch_name = 'ovn-' + uuidutils.generate_uuid()
mdt_port_name = 'ovn-mdt-' + uuidutils.generate_uuid()
mdt_pb_event = events.WaitForCreatePortBindingEvent(mdt_port_name)
self.handler.watch_event(mdt_pb_event)
with self.nb_api.transaction(
check_error=True, log_errors=True) as txn:
txn.add(
self.nb_api.ls_add(lswitch_name))
self._create_metadata_port(txn, lswitch_name, mdt_port_name)
external_ids = {ovn_const.OVN_CIDRS_EXT_ID_KEY: ""}
txn.add(
self.nb_api.set_lswitch_port(lport_name=mdt_port_name,
external_ids=external_ids))
self.assertTrue(mdt_pb_event.wait())
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
self.sb_api.lsp_bind(mdt_port_name, self.chassis_name).execute(
check_error=True, log_errors=True)
# Wait until port is bound
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath provisioning did not happen on port binding"))
m_provision.reset_mock()
self._update_metadata_port_ip(mdt_port_name)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=Exception(
"Datapath provisioning not called after external ids was "
"changed"))
def test_metadata_agent_only_monitors_own_chassis(self):
# We already have the fake chassis which we should be monitoring, so
# create an event looking for a change to another chassis
other_name = uuidutils.generate_uuid()
other_chassis = self.add_fake_chassis(self.FAKE_CHASSIS_HOST,
name=other_name)
self.assertEqual(other_chassis, other_name)
event = MetadataAgentHealthEvent(chassis=other_name, sb_cfg=-1,
timeout=0)
# Use the agent's sb_idl to watch for the event since it has condition
self.agent.sb_idl.idl.notify_handler.watch_event(event)
# Use the test sb_api to set other_chassis values since shouldn't exist
# on agent's sb_idl
self.sb_api.db_set(
AGENT_CHASSIS_TABLE, other_chassis,
('external_ids', {'test': 'value'})).execute(check_error=True)
event2 = MetadataAgentHealthEvent(chassis=self.chassis_name, sb_cfg=-1)
self.agent.sb_idl.idl.notify_handler.watch_event(event2)
# Use the test's sb_api again to send a command so we can see if it
# completes and short-circuit the need to wait for a timeout to pass
# the test. If we get the result to this, we would have gotten the
# previous result as well.
self.sb_api.db_set(
AGENT_CHASSIS_TABLE, self.chassis_name,
('external_ids', {'test': 'value'})).execute(check_error=True)
self.assertTrue(event2.wait())
self.assertFalse(event.wait())
def test__ensure_datapath_checksum_if_dpdk(self):
self.mock_ovsdb_idl.db_get.return_value.execute.return_value = (
ovn_const.CHASSIS_DATAPATH_NETDEV)
regex = re.compile(r'-A POSTROUTING -p tcp -m tcp '
r'-j CHECKSUM --checksum-fill')
namespace = self.useFixture(net_helpers.NamespaceFixture()).name
self.agent._ensure_datapath_checksum(namespace)
iptables_mgr = iptables_manager.IptablesManager(
use_ipv6=True, nat=False, namespace=namespace, external_lock=False)
for rule in iptables_mgr.get_rules_for_table('mangle'):
if regex.match(rule):
return
self.fail('Rule not found in "mangle" table, in namespace %s' %
namespace)
def test_metadata_proxy_handler_idl(self):
# This test relies on the configuration option metadata_workers=0
proxy_sb_idl = metadata_server.MetadataProxyHandler._sb_idl
agent_sb_idl = self.agent.sb_idl
self.assertEqual(agent_sb_idl, proxy_sb_idl)
def test_metadata_provisioned_on_additional_chassis_change(self):
other_chassis_name = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis", name=other_chassis_name)
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
self.sb_api.lsp_bind(
lswitchport_name, other_chassis_name).execute(
check_error=True, log_errors=True)
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
# Update the additional_chassis
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[agent_chassis.uuid]).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Additional chassis didn't trigger Port Binding event"))
def test_metadata_not_provisioned_on_foreign_additional_chassis_change(
self):
other_chassis_name = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis", name=other_chassis_name)
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
other_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', other_chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
pb_event = PortBindingUpdateEvent(lswitchport_name)
self.agent.sb_idl.idl.notify_handler.watch_event(pb_event)
self.sb_api.lsp_bind(
lswitchport_name, agent_chassis.name).execute(
check_error=True, log_errors=True)
self.assertTrue(pb_event.wait())
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
# Update the additional_chassis, the agent should not see the
# notification because it has only its own chassis row locally and
# does not see other chassis
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[other_chassis.uuid]).execute(
check_error=True, log_errors=True)
with testtools.ExpectedException(NoDatapathProvision):
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=1,
exception=NoDatapathProvision(
"Provisioning wasn't triggered"))
def test_metadata_teardown_on_additional_chassis_removed(self):
other_chassis_name = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis", name=other_chassis_name)
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
self.sb_api.lsp_bind(
lswitchport_name, other_chassis_name).execute(
check_error=True, log_errors=True)
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
# Update the additional_chassis
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[agent_chassis.uuid]).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Additional chassis didn't trigger Port Binding event"))
m_provision.reset_mock()
# Remove the additional_chassis but keep the chassis. This is
# simulates the live migration has failed
self.sb_api.db_set('Port_Binding', pb.uuid,
additional_chassis=[]).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Removing additional chassis did not call teardown"))
def test_metadata_additional_chassis_removed_chassis_set(self):
other_chassis_name = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis", name=other_chassis_name)
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
self.sb_api.lsp_bind(
lswitchport_name, other_chassis_name).execute(
check_error=True, log_errors=True)
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
# Update the additional_chassis
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[agent_chassis.uuid]).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Additional chassis didn't trigger Port Binding event"))
m_provision.reset_mock()
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[], chassis=agent_chassis.uuid).execute(
check_error=True, log_errors=True)
with testtools.ExpectedException(NoDatapathProvision):
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=1,
exception=NoDatapathProvision(
"Removing additional chassis did not call teardown"))
def _test_metadata_additional_chassis_removed(self, new_chassis_uuid):
other_chassis_name = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis", name=other_chassis_name)
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
self.sb_api.lsp_bind(
lswitchport_name, other_chassis_name).execute(
check_error=True, log_errors=True)
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
# Update the additional_chassis
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[agent_chassis.uuid]).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Additional chassis didn't trigger Port Binding event"))
m_provision.reset_mock()
self.sb_api.db_set(
'Port_Binding', pb.uuid,
additional_chassis=[], chassis=new_chassis_uuid).execute(
check_error=True, log_errors=True)
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=10,
exception=NoDatapathProvision(
"Removing additional chassis did not call teardown"))
def test_metadata_additional_chassis_removed_different_chassis_set(self):
other_chassis_name2 = uuidutils.generate_uuid()
self.add_fake_chassis("other_chassis2", name=other_chassis_name2)
other_chassis2 = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', other_chassis_name2)
self._test_metadata_additional_chassis_removed(other_chassis2.uuid)
def test_metadata_additional_chassis_removed_chassis_unset(self):
self._test_metadata_additional_chassis_removed(new_chassis_uuid=[])
def test_metadata_port_binding_column_updated(self):
agent_chassis = idlutils.row_by_value(
self.sb_api, 'Chassis', 'name', self.chassis_name)
lswitchport_name, lswitch_name = self._create_logical_switch_port()
pb_event = PortBindingUpdateEvent(lswitchport_name)
self.agent.sb_idl.idl.notify_handler.watch_event(pb_event)
self.sb_api.lsp_bind(
lswitchport_name, agent_chassis.name).execute(
check_error=True, log_errors=True)
self.assertTrue(pb_event.wait())
pb = idlutils.row_by_value(
self.sb_api, 'Port_Binding', 'logical_port', lswitchport_name)
with mock.patch.object(
agent.MetadataAgent, 'provision_datapath') as m_provision:
self.sb_api.db_add('Port_Binding', pb.uuid,
'external_ids', {'foo': 'bar'}).execute(
check_error=True, log_errors=True)
with testtools.ExpectedException(NoDatapathProvision):
n_utils.wait_until_true(
lambda: m_provision.called,
timeout=1,
exception=NoDatapathProvision(
"Provisioning wasn't triggered"))
def test__cleanup_previous_tags(self):
def check_tags():
try:
external_ids = self.sb_api.db_get(
'Chassis_Private', self.chassis_name,
'external_ids').execute(check_error=True)
for _key in (ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY,
ovn_const.OVN_AGENT_NEUTRON_DESC_KEY,
ovn_const.OVN_AGENT_NEUTRON_ID_KEY):
self.assertNotIn(_key, external_ids)
# Just in case, check that we are NOT deleting the needed tags.
for _key in (ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY,
ovn_const.OVN_AGENT_METADATA_ID_KEY):
self.assertIn(_key, external_ids)
return True
except Exception:
return False
external_ids = {
ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY: '1',
ovn_const.OVN_AGENT_NEUTRON_DESC_KEY: 'description',
ovn_const.OVN_AGENT_NEUTRON_ID_KEY: uuidutils.generate_uuid()}
self.sb_api.db_set(
'Chassis_Private', self.chassis_name,
('external_ids', external_ids)).execute(check_error=True)
self.agent._cleanup_previous_tags()
n_utils.wait_until_true(check_tags, timeout=10)
@@ -1393,26 +1393,22 @@ class TestAgentApi(base.TestOVNFunctionalBase):
mock.patch.object(self.mech_driver, 'ping_all_chassis',
return_value=False).start()
metadata_agent_id = uuidutils.generate_uuid()
# To be *mostly* sure the agent cache has been updated, we need to
# wait for the Chassis events to run. So add a new event that should
# run afterthey do and wait for it. I've only had to do this when
# run after they do and wait for it. I've only had to do this when
# adding *a bunch* of Chassis at a time, but better safe than sorry.
chassis_name = uuidutils.generate_uuid()
agent_event = AgentWaitEvent(self.mech_driver, [chassis_name])
self.sb_api.idl.notify_handler.watch_event(agent_event)
self.chassis = self.add_fake_chassis(
self.host, name=chassis_name,
external_ids={
ovn_const.OVN_AGENT_METADATA_ID_KEY: metadata_agent_id})
self.host, name=chassis_name)
self.assertTrue(agent_event.wait())
self.agent_types = {
self.TEST_AGENT: self._create_test_agent(),
ovn_const.OVN_CONTROLLER_AGENT: self.chassis,
ovn_const.OVN_METADATA_AGENT: metadata_agent_id,
}
def _create_test_agent(self):
@@ -1464,8 +1460,8 @@ class TestAgentApi(base.TestOVNFunctionalBase):
# "ovn-controller" ends without deleting "Chassis" and
# "Chassis_Private" registers. If "Chassis" register is deleted,
# then Chassis_Private.chassis = []; both metadata and controller
# agents will still be present in the agent list.
# then Chassis_Private.chassis = []; the controller agent will
# still be present in the agent list.
agent_event = AgentWaitEvent(self.mech_driver, [self.chassis],
events=(event.RowEvent.ROW_DELETE,))
self.sb_api.idl.notify_handler.watch_event(agent_event)
@@ -1490,25 +1486,14 @@ class TestAgentApi(base.TestOVNFunctionalBase):
self.context, agent_id)
# OVN controller agent deletion, that triggers the "Chassis" and
# "Chassis_Private" registers deletion. The registers deletion triggers
# the host OVN agents deletion, both controller and metadata if
# present.
# "Chassis_Private" registers deletion.
controller_id = self.agent_types[ovn_const.OVN_CONTROLLER_AGENT]
metadata_id = self.agent_types[ovn_const.OVN_METADATA_AGENT]
self._check_chassis_registers()
self.plugin.delete_agent(self.context, controller_id)
self._check_chassis_registers(present=False)
wait_until_removed(controller_id)
self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
self.context, controller_id)
self.assertEqual(
metadata_id,
self.plugin.get_agent(self.context, metadata_id)['id'])
self.plugin.delete_agent(self.context, metadata_id)
wait_until_removed(metadata_id)
self.assertRaises(agent_exc.AgentNotFound, self.plugin.get_agent,
self.context, metadata_id)
class _TestRouter(base.TestOVNFunctionalBase):
@@ -28,8 +28,8 @@ from neutron.agent.linux.ip_lib import IpLinkCommand as ip_link
from neutron.agent.linux.ip_lib import IpNetnsCommand as ip_netns
from neutron.agent.linux.ip_lib import IPWrapper as ip_wrap
from neutron.agent.linux import utils as linux_utils
from neutron.agent.ovn.metadata import agent
from neutron.agent.ovn.metadata import driver
from neutron.agent.ovn.extensions import metadata as agent
from neutron.agent.ovn.extensions.metadata import driver
from neutron.common.ovn import constants as ovn_const
from neutron.common import utils
from neutron.conf.agent.metadata import config as meta_conf
@@ -64,8 +64,6 @@ class ConfFixture(config_fixture.Config):
meta_conf.UNIX_DOMAIN_METADATA_PROXY_OPTS, self.conf)
ovn_meta_conf.register_meta_conf_opts(
meta_conf.METADATA_PROXY_HANDLER_OPTS, self.conf)
ovn_meta_conf.register_meta_conf_opts(
ovn_meta_conf.OVS_OPTS, self.conf, group='ovs')
ovn_conf.register_opts()
@@ -24,8 +24,8 @@ from oslo_utils import uuidutils
from neutron.agent.linux import external_process as ep
from neutron.agent.linux import utils as linux_utils
from neutron.agent.metadata import driver_base
from neutron.agent.ovn.metadata import agent as metadata_agent
from neutron.agent.ovn.metadata import driver as metadata_driver
from neutron.agent.ovn.extensions import metadata as metadata_agent
from neutron.agent.ovn.extensions.metadata import driver as metadata_driver
from neutron.common import metadata as comm_meta
from neutron.conf.agent.metadata import config as meta_conf
from neutron.conf.agent.ovn.metadata import config as ovn_meta_conf
@@ -21,7 +21,7 @@ from oslo_config import fixture as config_fixture
import webob
from neutron.agent.metadata import proxy_base
from neutron.agent.ovn.metadata import server_socket as agent
from neutron.agent.ovn.extensions.metadata import server_socket as agent
from neutron.common import metadata as common_metadata
from neutron.tests import base
Whitespace-only changes.
@@ -49,10 +49,7 @@ class AgentCacheTestCase(base.BaseTestCase):
'hostname': f'host{i:d}',
})
ext_ids = {}
if agent_type == ovn_const.OVN_METADATA_AGENT:
ext_ids = {
ovn_const.OVN_AGENT_METADATA_ID_KEY: 'chassis' + str(i)}
elif agent_type == ovn_const.OVN_NEUTRON_AGENT:
if agent_type == ovn_const.OVN_NEUTRON_AGENT:
ext_ids = {
ovn_const.OVN_AGENT_NEUTRON_ID_KEY: 'chassis' + str(i)}
chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row(
@@ -67,7 +64,6 @@ class AgentCacheTestCase(base.BaseTestCase):
self.assertEqual(self.num_agents, len(list(self.agent_cache)))
for agent_class in (neutron_agent.NeutronAgent,
neutron_agent.MetadataAgent,
neutron_agent.OVNNeutronAgent):
mock.patch.object(agent_class, 'alive', return_value=True).start()
@@ -107,7 +103,7 @@ class AgentCacheTestCase(base.BaseTestCase):
pass
def test_agents_by_chassis_private(self):
ext_ids = {ovn_const.OVN_AGENT_METADATA_ID_KEY: 'chassis5'}
ext_ids = {ovn_const.OVN_AGENT_NEUTRON_ID_KEY: 'chassis5'}
chassis_private = fakes.FakeOvsdbRow.create_one_ovsdb_row(
attrs={'name': 'chassis5',
'external_ids': ext_ids})
@@ -216,7 +212,6 @@ class AgentCachePopulateTestCase(base.BaseTestCase):
self.agent_cache = neutron_agent.AgentCache(driver=mock.ANY)
self.addCleanup(self._clean_agent_cache)
for agent_class in (neutron_agent.NeutronAgent,
neutron_agent.MetadataAgent,
neutron_agent.OVNNeutronAgent):
mock.patch.object(agent_class, 'alive', return_value=True).start()
@@ -272,19 +267,6 @@ class AgentCachePopulateTestCase(base.BaseTestCase):
filters={'agent_type': ovn_const.OVN_CONTROLLER_AGENT})
self.assertEqual(1, len(agents))
def test_populate_metadata_agents(self):
ext_ids = {ovn_const.OVN_AGENT_METADATA_ID_KEY: 'meta-id-0'}
rows = [self._make_chassis_private('ch0', external_ids=ext_ids)]
self.agent_cache.driver.sb_ovn = self._mock_sb_idl(rows)
self.agent_cache.populate()
# One controller + one metadata agent
self.assertEqual(2, len(list(self.agent_cache)))
agents = self.agent_cache.get_agents(
filters={'agent_type': ovn_const.OVN_METADATA_AGENT})
self.assertEqual(1, len(agents))
def test_populate_neutron_agents(self):
ext_ids = {ovn_const.OVN_AGENT_NEUTRON_ID_KEY: 'neutron-id-0'}
rows = [self._make_chassis_private('ch0', external_ids=ext_ids)]
@@ -1066,19 +1066,6 @@ class TestChassisOVNAgentWriteEvent(base.BaseTestCase):
mock.call().update(
ovn_const.OVN_NEUTRON_AGENT, row, clear_down=True)])
def test_run_metadata_agent(self):
# Test run method with metadata agent
row = self.ovsdb_row(
attrs={'external_ids': {
ovn_const.OVN_AGENT_METADATA_ID_KEY: 'metadata-456'}})
with mock.patch('neutron.plugins.ml2.drivers.ovn.agent.neutron_agent.'
'AgentCache') as agent_cache:
self.event.run(self.event.ROW_CREATE, row, None)
agent_cache.assert_has_calls([
mock.call().update(
ovn_const.OVN_METADATA_AGENT, row, clear_down=True)])
class TestFIPAddDeleteEvent(base.BaseTestCase):
@@ -136,10 +136,6 @@ class MechDriverSetupBase(abc.ABC):
ovn_const.OVN_AGENT_OVN_BRIDGE: n_const.DEFAULT_BR_INT,
ovn_const.OVN_DATAPATH_TYPE: DEFAULT_DP_TYPE,
}
if agent_type == ovn_const.OVN_METADATA_AGENT:
chassis_private.external_ids.update({
ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY: nb_cfg,
ovn_const.OVN_AGENT_METADATA_ID_KEY: str(uuid.uuid4())})
chassis_private.chassis = [self._add_chassis(chassis_private.name,
hostname)]
return neutron_agent.AgentCache().update(agent_type, chassis_private)
@@ -2882,55 +2878,51 @@ class TestOVNMechanismDriver(TestOVNMechanismDriverBase):
def test_agent_alive_true(self):
chassis_private = self._add_chassis_private(5)
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
ovn_const.OVN_METADATA_AGENT):
self.mech_driver.nb_ovn.nb_global.nb_cfg = 5
agent = self._add_chassis_agent(5, agent_type, chassis_private)
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
self.mech_driver.nb_ovn.nb_global.nb_cfg = 5
agent = self._add_chassis_agent(5, ovn_const.OVN_CONTROLLER_AGENT,
chassis_private)
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
def test_agent_alive_true_one_diff(self):
# Agent should be reported as alive when the nb_cfg delta is 1
# even if the last update time was old enough.
nb_cfg = 5
chassis_private = self._add_chassis_private(nb_cfg)
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
ovn_const.OVN_METADATA_AGENT):
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 1
agent = self._add_chassis_agent(nb_cfg, agent_type,
chassis_private)
now = timeutils.utcnow()
fake_now = now + datetime.timedelta(cfg.CONF.agent_down_time + 1)
with mock.patch.object(timeutils, 'utcnow') as get_now:
get_now.return_value = fake_now
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 1
agent = self._add_chassis_agent(nb_cfg,
ovn_const.OVN_CONTROLLER_AGENT,
chassis_private)
now = timeutils.utcnow()
fake_now = now + datetime.timedelta(cfg.CONF.agent_down_time + 1)
with mock.patch.object(timeutils, 'utcnow') as get_now:
get_now.return_value = fake_now
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
def test_agent_alive_not_timed_out(self):
nb_cfg = 3
chassis_private = self._add_chassis_private(nb_cfg)
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
ovn_const.OVN_METADATA_AGENT):
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2
agent = self._add_chassis_agent(nb_cfg, agent_type,
chassis_private)
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2
agent = self._add_chassis_agent(nb_cfg,
ovn_const.OVN_CONTROLLER_AGENT,
chassis_private)
self.assertTrue(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
def test_agent_alive_timed_out(self):
nb_cfg = 3
chassis_private = self._add_chassis_private(nb_cfg)
for agent_type in (ovn_const.OVN_CONTROLLER_AGENT,
ovn_const.OVN_METADATA_AGENT):
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2
now = timeutils.utcnow(with_timezone=True)
agent = self._add_chassis_agent(nb_cfg, agent_type,
chassis_private)
fake_now = now + datetime.timedelta(cfg.CONF.agent_down_time + 1)
with mock.patch.object(timeutils, 'utcnow') as get_now:
get_now.return_value = fake_now
self.assertFalse(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
self.mech_driver.nb_ovn.nb_global.nb_cfg = nb_cfg + 2
now = timeutils.utcnow(with_timezone=True)
agent = self._add_chassis_agent(nb_cfg,
ovn_const.OVN_CONTROLLER_AGENT,
chassis_private)
fake_now = now + datetime.timedelta(cfg.CONF.agent_down_time + 1)
with mock.patch.object(timeutils, 'utcnow') as get_now:
get_now.return_value = fake_now
self.assertFalse(agent.alive, "Agent of type %s alive=%s" %
(agent.agent_type, agent.alive))
def test_agent_with_nb_cfg_timestamp_timeout(self):
nb_cfg = 3
@@ -4580,10 +4572,6 @@ class TestOVNMechanismDriverSegment(MechDriverSetupBase,
def test_check_segment_for_agent(self):
segment = {'physical_network': 'physnet1'}
agent = {'agent_type': ovn_const.OVN_METADATA_AGENT}
self.assertFalse(
self.mech_driver.check_segment_for_agent(segment, agent))
agent = {'agent_type': ovn_const.OVN_CONTROLLER_AGENT,
'configurations': {}}
self.assertFalse(
-2
View File
@@ -62,7 +62,6 @@ vmware = [
"neutron.ml2.ovs.agent" = "neutron.opts:list_ovs_opts"
"neutron.ml2.sriov.agent" = "neutron.opts:list_sriov_agent_opts"
"neutron.ml2.ovn.agent" = "neutron.conf.agent.ovn.ovn_neutron_agent.config:list_ovn_neutron_agent_opts"
"neutron.ovn.metadata.agent" = "neutron.conf.agent.ovn.metadata.config:list_metadata_agent_opts"
"nova.auth" = "neutron.opts:list_nova_auth_opts"
"placement.auth" = "neutron.opts:list_placement_auth_opts"
@@ -327,7 +326,6 @@ neutron-periodic-workers = "neutron.cmd.server:main_periodic"
neutron-status = "neutron.cmd.status:main"
neutron-ovn-agent = "neutron.cmd.agents.ovn_neutron_agent:main"
neutron-ovn-maintenance-worker = "neutron.cmd.server:main_ovn_maintenance"
neutron-ovn-metadata-agent = "neutron.cmd.agents.ovn_metadata:main"
neutron-ovn-migration-mtu = "neutron.cmd.ovn.migration_mtu:main"
neutron-ovn-db-sync-util = "neutron.cmd.ovn.neutron_ovn_db_sync_util:main"
neutron-sanitize-port-binding-profile-allocation = "neutron.cmd.sanitize_port_binding_profile_allocation:main"
@@ -0,0 +1,12 @@
---
upgrade:
- |
The standalone OVN Metadata agent (``neutron-ovn-metadata-agent``) has been
removed. It was deprecated in 2025.2 in favor of the
`OVN agent <https://docs.openstack.org/neutron/latest/ovn/ovn_agent.html>`_
with the "metadata" plugable extension enabled. To migrate, stop the OVN
Metadata agent and start the OVN agent (``neutron-ovn-agent``) with the
``metadata`` extension enabled in the configuration file
(``extensions = metadata`` in the ``[DEFAULT]`` section). The rest of the
configuration is the same; the new agent can reuse the old configuration
files.
+2
View File
@@ -25,6 +25,8 @@ ignore_regexes=(
"^plugins/ml2/test_tracked_resources.py$"
"^plugins/ml2/drivers/openvswitch/agent/test_agent_scheduler.py$"
"^plugins/ml2/drivers/openvswitch/agent/test_ovs_tunnel.py$"
# Code lives in the package __init__.py, not in a separate agent.py
"^agent/ovn/extensions/metadata/test_agent.py$"
)
error_count=0
@@ -44,9 +44,6 @@ disable_service ovn-northd
# on the controller node that depend on it.
disable_service ovn-controller
# Disable the ovn metadata agent.
disable_service neutron-ovn-metadata-agent
# Disable the nova compute service on the controller node because the
# architecture only deploys it on separate compute nodes.
disable_service n-cpu
-6
View File
@@ -66,7 +66,6 @@
q-metering: true
ovn-controller: false
ovn-northd: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -98,7 +97,6 @@
ovn-controller: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-bak: false
c-vol: false
@@ -140,7 +138,6 @@
q-metering: true
ovn-controller: false
ovn-northd: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -194,7 +191,6 @@
ovn-controller: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-bak: false
c-vol: false
@@ -314,7 +310,6 @@
etcd: false
br-ex-tcpdump: true
br-int-flows: true
q-ovn-metadata-agent: false
q-ovn-agent: true
ovn-controller: true
ovn-northd: true
@@ -364,7 +359,6 @@
q-l3: false
q-meta: false
q-metering: false
q-ovn-metadata-agent: false
q-ovn-agent: true
tls-proxy: false
# Cinder services
-2
View File
@@ -33,7 +33,6 @@
ovn-northd: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Neutron services
q-agt: true
q-dhcp: true
@@ -153,7 +152,6 @@
ovn-controller: true
ovs-vswitchd: true
ovsdb-server: true
q-ovn-metadata-agent: false
q-ovn-agent: true
br-ex-tcpdump: true
br-int-flows: true
-2
View File
@@ -174,7 +174,6 @@
placement-api: false
br-ex-tcpdump: false
br-int-flows: false
q-ovn-metadata-agent: false
q-ovn-agent: false
ovn-bgp: false
ovn-controller: false
@@ -275,7 +274,6 @@
ovn-northd: false
br-ex-tcpdump: false
br-int-flows: true
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
-8
View File
@@ -113,7 +113,6 @@
ovn-northd: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -171,7 +170,6 @@
ovn-controller: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -239,7 +237,6 @@
ovn-northd: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -278,7 +275,6 @@
ovn-controller: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-bak: false
c-vol: false
@@ -324,7 +320,6 @@
ovn-northd: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-api: false
c-bak: false
@@ -359,7 +354,6 @@
ovn-controller: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Cinder services
c-bak: false
c-vol: false
@@ -470,7 +464,6 @@
etcd: false
br-ex-tcpdump: true
br-int-flows: true
q-ovn-metadata-agent: false
q-ovn-agent: true
ovn-controller: true
ovn-northd: true
@@ -518,7 +511,6 @@
q-l3: false
q-meta: false
q-metering: false
q-ovn-metadata-agent: false
q-ovn-agent: true
tls-proxy: true
dstat: true
-2
View File
@@ -24,7 +24,6 @@
ovn-northd: false
ovs-vswitchd: false
ovsdb-server: false
q-ovn-metadata-agent: false
# Neutron services
q-agt: true
q-dhcp: true
@@ -509,7 +508,6 @@
n-novnc-cell1: true
n-sch: true
n-super-cond: true
q-ovn-metadata-agent: false
q-ovn-agent: true
ovn-controller: true
ovn-northd: true