Merge "[OVN] Implementation of OVN Neutron Agent"

This commit is contained in:
Zuul 2023-02-02 04:57:57 +00:00 committed by Gerrit Code Review
commit 2202cc1d89
16 changed files with 734 additions and 0 deletions

View File

@ -0,0 +1,6 @@
[DEFAULT]
output_file = etc/ovn_agent.ini.sample
wrap_width = 79
namespace = neutron.ml2.ovn.agent
namespace = oslo.log

View File

View File

@ -0,0 +1,127 @@
# Copyright (c) 2023 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 uuid
from oslo_log import log as logging
from oslo_service import service
from ovsdbapp.backend.ovs_idl import event as row_event
from neutron.agent.ovn.agent import ovsdb
from neutron.agent.ovn.extensions import extension_manager as ext_mgr
from neutron.common.ovn import constants as ovn_const
LOG = logging.getLogger(__name__)
OVN_MONITOR_UUID_NAMESPACE = uuid.UUID('fd7e0970-7164-11ed-80f0-00000003158a')
class SbGlobalUpdateEvent(row_event.RowEvent):
"""Row update event on SB_Global table.
This event will trigger the OVN Neutron Agent update of the
'neutron:ovn-neutron-agent-sb-cfg' key in 'SB_Global', that is used to
determine the agent status.
"""
def __init__(self, ovn_agent):
self.ovn_agent = ovn_agent
table = 'SB_Global'
events = (self.ROW_UPDATE, )
super().__init__(events, table, None)
self.event_name = self.__class__.__name__
def run(self, event, row, old):
ext_ids = {ovn_const.OVN_AGENT_NEUTRON_SB_CFG_KEY: str(row.nb_cfg)}
self.ovn_agent.sb_idl.db_set('Chassis_Private', self.ovn_agent.chassis,
('external_ids', ext_ids)).execute()
class OVNNeutronAgent(service.Service):
def __init__(self, conf):
super().__init__()
self._conf = conf
self.chassis = None
self.chassis_id = None
self.ovn_bridge = None
self.ext_manager_api = ext_mgr.OVNAgentExtensionAPI()
self.ext_manager = ext_mgr.OVNAgentExtensionManager(self._conf)
self.ext_manager.initialize(None, 'ovn', self.ext_manager_api)
@property
def sb_idl(self):
return self.ext_manager_api.sb_idl
def _load_config(self, ovs_idl):
self.chassis = ovsdb.get_own_chassis_name(ovs_idl)
try:
self.chassis_id = uuid.UUID(self.chassis)
except ValueError:
# OVS system-id could be a non UUID formatted string.
self.chassis_id = uuid.uuid5(OVN_MONITOR_UUID_NAMESPACE,
self.chassis)
self.ovn_bridge = ovsdb.get_ovn_bridge(ovs_idl)
LOG.info("Loaded chassis name %s (UUID: %s) and ovn bridge %s.",
self.chassis, self.chassis_id, self.ovn_bridge)
def _load_ovs_idl(self):
events = []
for extension in self.ext_manager:
events += extension.obj.ovs_idl_events
events = [e(self) for e in set(events)]
return ovsdb.MonitorAgentOvsIdl(set(events)).start()
def _load_nb_idl(self):
events = []
tables = []
for extension in self.ext_manager:
events += extension.obj.nb_idl_events
tables += extension.obj.nb_idl_tables
if not (tables or events):
# If there is no need to retrieve any table nor attend to any
# event, the IDL object is not created to save a DB connection.
return None
events = [e(self) for e in set(events)]
tables = set(tables)
return ovsdb.MonitorAgentOvnNbIdl(tables, events).start()
def _load_sb_idl(self):
events = [SbGlobalUpdateEvent]
tables = ['SB_Global']
for extension in self.ext_manager:
events += extension.obj.sb_idl_events
tables += extension.obj.sb_idl_tables
events = [e(self) for e in set(events)]
tables = set(tables)
return ovsdb.MonitorAgentOvnSbIdl(tables, events,
chassis=self.chassis).start()
def start(self):
self.ext_manager_api.ovs_idl = self._load_ovs_idl()
# Before executing "_load_config", it is needed to create the OVS IDL.
self._load_config(self.ext_manager_api.ovs_idl)
# Before executing "_load_sb_idl", is is needed to execute
# "_load_config" to populate self.chassis.
self.ext_manager_api.sb_idl = self._load_sb_idl()
self.ext_manager_api.nb_idl = self._load_nb_idl()
LOG.info('Starting OVN Neutron Agent')
def stop(self, graceful=True):
LOG.info('Stopping OVN Neutron Agent')
super().stop(graceful)

View File

@ -0,0 +1,142 @@
# Copyright 2023 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 ovsdbapp.backend.ovs_idl import connection
from ovsdbapp.backend.ovs_idl import idlutils
from ovsdbapp.schema.open_vswitch import impl_idl as impl_idl_ovs
from neutron.agent.ovsdb.native import connection as ovsdb_conn
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 MonitorAgentOvnSbIdl(ovsdb_monitor.OvnIdl):
SCHEMA = 'OVN_Southbound'
def __init__(self, tables, events, chassis=None):
connection_string = config.get_ovn_sb_connection()
ovsdb_monitor._check_and_set_ssl_files(self.SCHEMA)
helper = self._get_ovsdb_helper(connection_string)
for table in tables:
helper.register_table(table)
try:
super().__init__(None, connection_string, helper,
leader_only=False)
except TypeError:
# TODO(twilson) We can remove this when we require ovs>=2.12.0
super().__init__(None, connection_string, helper)
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 OVN monitor with retry')
conn = connection.Connection(
self, timeout=config.get_ovn_ovsdb_timeout())
return impl_idl_ovn.OvsdbSbOvnIdl(conn)
def post_connect(self):
pass
class MonitorAgentOvnNbIdl(ovsdb_monitor.OvnIdl):
SCHEMA = 'OVN_Northbound'
def __init__(self, tables, events):
connection_string = config.get_ovn_nb_connection()
ovsdb_monitor._check_and_set_ssl_files(self.SCHEMA)
helper = self._get_ovsdb_helper(connection_string)
for table in tables:
helper.register_table(table)
try:
super().__init__(None, connection_string, helper,
leader_only=False)
except TypeError:
# TODO(twilson) We can remove this when we require ovs>=2.12.0
super().__init__(None, connection_string, helper)
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 OvsdbNbOvnIdl for OVN monitor with retry')
conn = connection.Connection(
self, timeout=config.get_ovn_ovsdb_timeout())
return impl_idl_ovn.OvsdbNbOvnIdl(conn)
def post_connect(self):
pass
class MonitorAgentOvsIdl(ovsdb_conn.OvsIdl):
def __init__(self, events):
super().__init__()
if events:
self.notify_handler.watch_events(events)
@ovn_utils.retry()
def start(self):
LOG.info('Getting OvsdbIdl for OVN monitor with retry')
conn = connection.Connection(self,
timeout=config.get_ovn_ovsdb_timeout())
return impl_idl_ovs.OvsdbIdl(conn)
def post_connect(self):
pass
def get_ovn_bridge(ovs_idl):
"""Return the external_ids:ovn-bridge value of the Open_vSwitch table.
This is the OVS bridge used to plug the metadata ports to.
If the key doesn't exist, this method will return 'br-int' as default.
"""
ext_ids = ovs_idl.db_get('Open_vSwitch', '.', 'external_ids').execute()
try:
return ext_ids['ovn-bridge']
except KeyError:
LOG.warning("Can't read ovn-bridge external-id from OVSDB. Using "
"br-int instead.")
return 'br-int'
def get_own_chassis_name(ovs_idl):
"""Return the external_ids:system-id value of the Open_vSwitch table.
As long as ovn-controller is running on this node, the key is
guaranteed to exist and will include the chassis name.
"""
ext_ids = ovs_idl.db_get('Open_vSwitch', '.', 'external_ids').execute()
return ext_ids['system-id']

View File

View File

@ -0,0 +1,130 @@
# Copyright (c) 2023 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 abc
import threading
from neutron_lib.agent import extension
from neutron_lib import exceptions
from neutron._i18n import _
from neutron.agent import agent_extensions_manager as agent_ext_mgr
OVN_AGENT_EXT_MANAGER_NAMESPACE = 'neutron.agent.ovn.extensions'
class ConfigException(exceptions.NeutronException):
"""Misconfiguration of the OVN Neutron Agent"""
message = _('Error configuring the OVN Neutron Agent: %(description)s.')
class OVNAgentExtensionManager(agent_ext_mgr.AgentExtensionsManager):
def __init__(self, conf):
super().__init__(conf, OVN_AGENT_EXT_MANAGER_NAMESPACE)
for ext in self:
if not isinstance(ext.obj, OVNAgentExtension):
desc = ('Extension %s class is not inheriting from '
'"OVNAgentExtension"')
raise ConfigException(description=desc)
class OVNAgentExtension(extension.AgentExtension, metaclass=abc.ABCMeta):
def __init__(self):
super().__init__()
self.agent_api = None
def initialize(self, *args):
"""Initialize agent extension."""
self.agent_api = None
def consume_api(self, agent_api):
"""Configure the Agent API.
Allows an extension to gain access to resources internal to the
neutron agent and otherwise unavailable to the extension.
"""
self.agent_api = agent_api
@property
@abc.abstractmethod
def ovs_idl_events(self):
pass
@property
@abc.abstractmethod
def nb_idl_tables(self):
pass
@property
@abc.abstractmethod
def nb_idl_events(self):
pass
@property
@abc.abstractmethod
def sb_idl_tables(self):
pass
@property
@abc.abstractmethod
def sb_idl_events(self):
pass
class OVNAgentExtensionAPI(object):
"""Implements the OVN Neutron Agent API"""
def __init__(self):
self._nb_idl = None
self._sb_idl = None
self._has_chassis_private = None
self._ovs_idl = None
self.sb_post_fork_event = threading.Event()
self.sb_post_fork_event.clear()
self.nb_post_fork_event = threading.Event()
self.nb_post_fork_event.clear()
@property
def ovs_idl(self):
return self._ovs_idl
@ovs_idl.setter
def ovs_idl(self, val):
self._ovs_idl = val
@property
def nb_idl(self):
if not self._nb_idl:
self.nb_post_fork_event.wait()
return self._nb_idl
@nb_idl.setter
def nb_idl(self, val):
self.nb_post_fork_event.set()
self._nb_idl = val
@property
def sb_idl(self):
if not self._sb_idl:
self.sb_post_fork_event.wait()
return self._sb_idl
@sb_idl.setter
def sb_idl(self, val):
self.sb_post_fork_event.set()
self._sb_idl = val

View File

@ -0,0 +1,39 @@
# Copyright (c) 2023 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.
from neutron.agent.ovn.extensions import extension_manager
class NoopOVNAgentExtension(extension_manager.OVNAgentExtension):
@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 []
@property
def sb_idl_events(self):
return []

View File

@ -0,0 +1,43 @@
# Copyright (c) 2023 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 sys
from neutron.common import config
from neutron.common import utils
from oslo_config import cfg
from oslo_log import log as logging
from oslo_service import service
from neutron.agent.ovn.agent import ovn_neutron_agent
from neutron.conf.agent.ovn.ovn_neutron_agent import config as config_ovn_agent
LOG = logging.getLogger(__name__)
def main():
config.register_common_config_options()
config_ovn_agent.register_opts()
config.init(sys.argv[1:])
config.setup_logging()
utils.log_opt_values(LOG)
config_ovn_agent.setup_privsep()
ovn_agent = ovn_neutron_agent.OVNNeutronAgent(cfg.CONF)
LOG.info('OVN Neutron Agent initialized successfully, now running... ')
launcher = service.launch(cfg.CONF, ovn_agent, restart_method='mutate')
launcher.wait()

View File

@ -0,0 +1,26 @@
# 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 ovn_neutron_agent
# TODO(ralonsoh): move to ``neutron_lib.constants``.
AGENT_PROCESS_OVN_NEUTRON_AGENT = 'neutron-ovn-agent'
def main():
proctitle = "%s (%s)" % (AGENT_PROCESS_OVN_NEUTRON_AGENT,
setproctitle.getproctitle())
setproctitle.setproctitle(proctitle)
ovn_neutron_agent.main()

View File

@ -0,0 +1,56 @@
# Copyright (c) 2023 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 itertools
import shlex
from neutron.conf.agent import ovsdb_api
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf
from oslo_config import cfg
from oslo_privsep import priv_context
from neutron._i18n import _
OVS_OPTS = [
cfg.IntOpt(
'ovsdb_connection_timeout',
default=180,
help=_('Timeout in seconds for the OVSDB connection transaction'))
]
def list_ovn_neutron_agent_opts():
return [
('ovn', ovn_conf.ovn_opts),
('ovs', itertools.chain(OVS_OPTS,
ovsdb_api.API_OPTS,
)
),
]
def register_opts():
cfg.CONF.register_opts(ovn_conf.ovn_opts, group='ovn')
cfg.CONF.register_opts(OVS_OPTS, group='ovs')
cfg.CONF.register_opts(ovsdb_api.API_OPTS, group='ovs')
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)))

View File

@ -0,0 +1,75 @@
# Copyright (c) 2023 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.
from ovsdbapp.backend.ovs_idl import event as row_event
from neutron.agent.ovn.extensions import extension_manager as ext_mgr
class OVSInterfaceEvent(row_event.RowEvent):
def __init__(self, ovn_agent):
self.ovn_agent = ovn_agent
events = (self.ROW_CREATE, )
table = 'Interface'
super().__init__(events, table, None)
def run(self, event, row, old):
self.ovn_agent.test_ovs_idl = row.name
class OVNSBChassisEvent(row_event.RowEvent):
def __init__(self, ovn_agent):
self.ovn_agent = ovn_agent
events = (self.ROW_CREATE, )
table = 'Chassis'
super().__init__(events, table, None)
def run(self, event, row, old):
self.ovn_agent.test_ovn_sb_idl = row.name
class OVNNBLogicalSwitchEvent(row_event.RowEvent):
def __init__(self, ovn_agent):
self.ovn_agent = ovn_agent
events = (self.ROW_CREATE, )
table = 'Logical_Switch'
super().__init__(events, table, None)
def run(self, event, row, old):
self.ovn_agent.test_ovn_nb_idl = row.name
class FakeOVNAgentExtension(ext_mgr.OVNAgentExtension):
@property
def ovs_idl_events(self):
return [OVSInterfaceEvent]
@property
def nb_idl_tables(self):
return ['Logical_Switch']
@property
def nb_idl_events(self):
return [OVNNBLogicalSwitchEvent]
@property
def sb_idl_tables(self):
return ['Chassis', 'Chassis_Private']
@property
def sb_idl_events(self):
return [OVNSBChassisEvent]

View File

@ -0,0 +1,77 @@
# Copyright 2023 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.
from unittest import mock
from oslo_config import fixture as fixture_config
from oslo_utils import uuidutils
from neutron.agent.ovn.agent import ovn_neutron_agent
from neutron.agent.ovn.agent import ovsdb as agent_ovsdb
from neutron.common import utils as n_utils
from neutron.tests.common import net_helpers
from neutron.tests.functional import base
class TestOVNNeutronAgent(base.TestOVNFunctionalBase):
OVN_BRIDGE = 'br-int'
FAKE_CHASSIS_HOST = 'ovn-host-fake'
def setUp(self, **kwargs):
super().setUp(**kwargs)
self.mock_chassis_name = mock.patch.object(
agent_ovsdb, 'get_own_chassis_name').start()
self.ovn_agent = self._start_ovn_neutron_agent()
def _start_ovn_neutron_agent(self):
conf = self.useFixture(fixture_config.Config()).conf
conf.set_override('extensions', 'testing', group='agent')
ovn_nb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('nb')
conf.set_override('ovn_nb_connection', ovn_nb_db, group='ovn')
ovn_sb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('sb')
conf.set_override('ovn_sb_connection', ovn_sb_db, group='ovn')
self.chassis_name = self.add_fake_chassis(self.FAKE_CHASSIS_HOST)
self.mock_chassis_name.return_value = self.chassis_name
agt = ovn_neutron_agent.OVNNeutronAgent(conf)
agt.start()
self.addCleanup(agt.ext_manager_api.ovs_idl.ovsdb_connection.stop)
if agt.ext_manager_api.sb_idl:
self.addCleanup(agt.ext_manager_api.sb_idl.ovsdb_connection.stop)
if agt.ext_manager_api.nb_idl:
self.addCleanup(agt.ext_manager_api.nb_idl.ovsdb_connection.stop)
return agt
def test_ovs_and_ovs_events(self):
# Test the OVS IDL is attending the provided events.
bridge = self.useFixture(net_helpers.OVSBridgeFixture()).bridge
n_utils.wait_until_true(
lambda: bridge.br_name == self.ovn_agent.test_ovs_idl,
timeout=10)
# Test the OVN SB IDL is attending the provided events. The chassis is
# created before the OVN SB IDL connection is created but the creation
# event is received during the subscription.
n_utils.wait_until_true(
lambda: self.chassis_name == self.ovn_agent.test_ovn_sb_idl,
timeout=10)
# Test the OVN SN IDL is attending the provided events.
lswitch_name = 'ovn-' + uuidutils.generate_uuid()
self.nb_api.ls_add(lswitch_name).execute(check_error=True)
n_utils.wait_until_true(
lambda: lswitch_name == self.ovn_agent.test_ovn_nb_idl,
timeout=10)

View File

@ -0,0 +1,8 @@
---
features:
- |
Added a new agent: the OVN Agent. This new agent will run on a compute or
a controller node using OVN as network backend, similar to other ML2
mechanism drivers as ML2/OVS or ML2/SRIOV. This new agent will perform
those actions that the ovn-controller service cannot execute. The agent
functionality will be plugable and added via configuration knob.

View File

@ -57,6 +57,7 @@ console_scripts =
neutron-sriov-nic-agent = neutron.cmd.eventlet.plugins.sriov_nic_neutron_agent:main
neutron-sanity-check = neutron.cmd.sanity_check:main
neutron-status = neutron.cmd.status:main
neutron-ovn-agent = neutron.cmd.eventlet.agents.ovn_neutron_agent:main
neutron-ovn-metadata-agent = neutron.cmd.eventlet.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
@ -140,6 +141,9 @@ neutron.agent.l3.extensions =
snat_log = neutron.agent.l3.extensions.snat_log:SNATLoggingExtension
conntrack_helper = neutron.agent.l3.extensions.conntrack_helper:ConntrackHelperAgentExtension
ndp_proxy = neutron.agent.l3.extensions.ndp_proxy:NDPProxyAgentExtension
neutron.agent.ovn.extensions =
noop = neutron.agent.ovn.extensions.noop:NoopOVNAgentExtension
testing = neutron.tests.functional.agent.ovn.agent.fake_ovn_agent_extension:FakeOVNAgentExtension
neutron.services.logapi.drivers =
ovs = neutron.services.logapi.drivers.openvswitch.ovs_firewall_log:OVSFirewallLoggingDriver
neutron.qos.agent_drivers =
@ -170,6 +174,7 @@ oslo.config.opts =
neutron.ml2.ovn = neutron.conf.plugins.ml2.drivers.ovn.ovn_conf:list_opts
neutron.ml2.ovs.agent = neutron.opts:list_ovs_opts
neutron.ml2.sriov.agent = neutron.opts:list_sriov_agent_opts
neutron.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