[OVN] Implementation of OVN Neutron Agent
This patch implements the OVN Neutron Agent executable, the extension manager engine, the agent extension abstract class and the configuration section. Related-Bug: #1998608 Change-Id: I94bb98217e03f9ac314cb9723da277a23368649c
This commit is contained in:
parent
65bd4a24f5
commit
d0c7bb653a
6
etc/oslo-config-generator/ovn_agent.ini
Normal file
6
etc/oslo-config-generator/ovn_agent.ini
Normal file
@ -0,0 +1,6 @@
|
||||
[DEFAULT]
|
||||
output_file = etc/ovn_agent.ini.sample
|
||||
wrap_width = 79
|
||||
|
||||
namespace = neutron.ml2.ovn.agent
|
||||
namespace = oslo.log
|
0
neutron/agent/ovn/agent/__init__.py
Normal file
0
neutron/agent/ovn/agent/__init__.py
Normal file
127
neutron/agent/ovn/agent/ovn_neutron_agent.py
Normal file
127
neutron/agent/ovn/agent/ovn_neutron_agent.py
Normal 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)
|
142
neutron/agent/ovn/agent/ovsdb.py
Normal file
142
neutron/agent/ovn/agent/ovsdb.py
Normal 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']
|
0
neutron/agent/ovn/extensions/__init__.py
Normal file
0
neutron/agent/ovn/extensions/__init__.py
Normal file
130
neutron/agent/ovn/extensions/extension_manager.py
Normal file
130
neutron/agent/ovn/extensions/extension_manager.py
Normal 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
|
39
neutron/agent/ovn/extensions/noop.py
Normal file
39
neutron/agent/ovn/extensions/noop.py
Normal 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 []
|
43
neutron/agent/ovn/ovn_neutron_agent.py
Normal file
43
neutron/agent/ovn/ovn_neutron_agent.py
Normal 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()
|
26
neutron/cmd/eventlet/agents/ovn_neutron_agent.py
Normal file
26
neutron/cmd/eventlet/agents/ovn_neutron_agent.py
Normal 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()
|
56
neutron/conf/agent/ovn/ovn_neutron_agent/config.py
Normal file
56
neutron/conf/agent/ovn/ovn_neutron_agent/config.py
Normal 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)))
|
@ -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]
|
@ -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)
|
8
releasenotes/notes/ovn-agent-added-84fc31c0fba02be9.yaml
Normal file
8
releasenotes/notes/ovn-agent-added-84fc31c0fba02be9.yaml
Normal 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.
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user