Merge "ovn: Remove standalone OVN Metadata agent"
This commit is contained in:
commit
7b4e8b7342
38 files changed
+260
-1537
No files matched your search
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
+165
-174
@@ -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
|
||||
File renamed without changes.
File renamed without changes.
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
File renamed without changes.
+2
-4
@@ -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()
|
||||
|
||||
|
||||
+2
-2
@@ -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
|
||||
+1
-1
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in new issue
Block a user