Browse Source

[OVN] Add OVN functional tests - part 1

This patch adds functional tests for:
  * base
  * ovn metadata agent
  * ovn mechanism driver
  * impl_idl
  * maintenance task
and needed resources to run the tests.

Previous paths in networking-ovn tree:
./networking_ovn/tests/functional/base.py ->
  ./neutron/tests/functional/base.py
./networking_ovn/tests/functional/resources ->
  ./neutron/tests/functional/resources
./networking_ovn/tests/functional/test_metadata_agent.py ->
  ./neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py
./networking_ovn/tests/functional/test_mech_driver.py ->
  ./neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py
./networking_ovn/tests/functional/test_impl_idl.py ->
  ./neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py
./networking_ovn/tests/functional/test_maintenance.py ->
  ./neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

Co-Authored-By: Amitabha Biswas <abiswas@us.ibm.com>
Co-Authored-By: Andrew Austin <aaustin@redhat.com>
Co-Authored-By: Armando Migliaccio <armamig@gmail.com>
Co-Authored-By: Boden R <bodenvmw@gmail.com>
Co-Authored-By: Brian Haley <bhaley@redhat.com>
Co-Authored-By: Cao Xuan Hoang <hoangcx@vn.fujitsu.com>
Co-Authored-By: Daniel Alvarez <dalvarez@redhat.com>
Co-Authored-By: Dong Jun <dongj@dtdream.com>
Co-Authored-By: Doug Wiegley <dougw@a10networks.com>
Co-Authored-By: Flavio Fernandes <flaviof@redhat.com>
Co-Authored-By: Gary Kotton <gkotton@vmware.com>
Co-Authored-By: Guoshuai Li <ligs@dtdream.com>
Co-Authored-By: Hong Hui Xiao <honghui_xiao@yeah.net>
Co-Authored-By: Ihar Hrachyshka <ihrachys@redhat.com>
Co-Authored-By: Jakub Libosvar <libosvar@redhat.com>
Co-Authored-By: Kamil Sambor <ksambor@redhat.com>
Co-Authored-By: Lucas Alvares Gomes <lucasagomes@gmail.com>
Co-Authored-By: Numan Siddique <nusiddiq@redhat.com>
Co-Authored-By: Reedip <rbanerje@redhat.com>
Co-Authored-By: Richard Theis <rtheis@us.ibm.com>
Co-Authored-By: Rodolfo Alonso Hernandez <ralonsoh@redhat.com>
Co-Authored-By: Terry Wilson <twilson@redhat.com>
Co-Authored-By: bailinzhang <zhang.bailin@zte.com.cn>
Co-Authored-By: reedip <rbanerje@redhat.com>
Co-Authored-By: venkata anil <anilvenkata@redhat.com>

Depends-On: https://review.opendev.org/#/c/703883/

Change-Id: Ic45ab26c28fdb757d1ee8a9e0c774a1543fb4bff
Related-Blueprint: neutron-ovn-merge
changes/33/701733/41
Maciej Józefczyk 2 years ago
parent
commit
03203cc4c8
  1. 0
      neutron/tests/functional/agent/ovn/__init__.py
  2. 0
      neutron/tests/functional/agent/ovn/metadata/__init__.py
  3. 194
      neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py
  4. 258
      neutron/tests/functional/base.py
  5. 0
      neutron/tests/functional/plugins/ml2/drivers/ovn/__init__.py
  6. 0
      neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/__init__.py
  7. 0
      neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/__init__.py
  8. 149
      neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py
  9. 800
      neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py
  10. 432
      neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py
  11. 2
      neutron/tests/functional/requirements.txt
  12. 0
      neutron/tests/functional/resources/__init__.py
  13. 0
      neutron/tests/functional/resources/ovsdb/__init__.py
  14. 65
      neutron/tests/functional/resources/ovsdb/events.py
  15. 33
      neutron/tests/functional/resources/ovsdb/fixtures.py
  16. 240
      neutron/tests/functional/resources/process.py
  17. 3
      neutron/tests/unit/db/test_db_base_plugin_v2.py

0
neutron/tests/functional/agent/ovn/__init__.py

0
neutron/tests/functional/agent/ovn/metadata/__init__.py

194
neutron/tests/functional/agent/ovn/metadata/test_metadata_agent.py

@ -0,0 +1,194 @@
# 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 mock
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
from neutron.agent.ovn.metadata import agent
from neutron.agent.ovn.metadata import ovsdb
from neutron.agent.ovn.metadata import server 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.functional import base
class MetadataAgentHealthEvent(event.WaitEvent):
event_name = 'MetadataAgentHealthEvent'
def __init__(self, chassis, sb_cfg, timeout=5):
self.chassis = chassis
self.sb_cfg = sb_cfg
super(MetadataAgentHealthEvent, self).__init__(
(self.ROW_UPDATE,), 'Chassis', (('name', '=', self.chassis),),
timeout=timeout)
def matches(self, event, row, old=None):
if not super(MetadataAgentHealthEvent, self).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 TestMetadataAgent(base.TestOVNFunctionalBase):
OVN_BRIDGE = 'br-int'
FAKE_CHASSIS_HOST = 'ovn-host-fake'
def setUp(self):
super(TestMetadataAgent, self).setUp()
self.handler = self.sb_api.idl.notify_handler
# We only have OVN NB and OVN SB running for functional tests
mock.patch.object(ovsdb, 'MetadataAgentOvsIdl').start()
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()
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')
# We don't need the HA proxy server running for now
p = mock.patch.object(metadata_server, 'UnixDomainMetadataProxy')
p.start()
self.addCleanup(p.stop)
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)
agt.start()
# 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):
chassis_row = self.sb_api.db_find(
'Chassis', ('name', '=', self.chassis_name)).execute(
check_error=True)[0]
# Assert that, prior to creating a resource the metadata agent
# didn't populate the external_ids from the Chassis
self.assertNotIn(ovn_const.OVN_AGENT_METADATA_SB_CFG_KEY,
chassis_row['external_ids'])
# 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):
mdt_port_name = 'ovn-mdt-' + uuidutils.generate_uuid()
txn.add(
self.nb_api.lsp_add(
lswitch_name,
mdt_port_name,
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'}))
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)
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)
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))
self._create_metadata_port(txn, lswitch_name)
self.assertTrue(pb_event.wait())
# Trigger PortBindingChassisEvent
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_registration_at_chassis_create_event(self):
chassis = self.sb_api.lookup('Chassis', self.chassis_name)
self.assertIn(ovn_const.OVN_AGENT_METADATA_ID_KEY,
chassis.external_ids)
# Delete Chassis and assert
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)
def check_for_metadata():
chassis = self.sb_api.lookup('Chassis', self.chassis_name)
return ovn_const.OVN_AGENT_METADATA_ID_KEY in chassis.external_ids
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)

258
neutron/tests/functional/base.py

@ -13,22 +13,46 @@
# License for the specific language governing permissions and limitations
# under the License.
from datetime import datetime
import errno
import os
import shutil
import warnings
import fixtures
import mock
from neutron_lib import fixture
from neutron_lib.plugins import constants
from neutron_lib.plugins import directory
from oslo_concurrency import lockutils
from oslo_config import cfg
from oslo_db import exception as os_db_exc
from oslo_db.sqlalchemy import provision
from oslo_log import log
from oslo_utils import uuidutils
from neutron.agent.linux import utils
from neutron.api import extensions as exts
from neutron.conf.agent import common as config
from neutron.conf.agent import ovs_conf
from neutron.conf.plugins.ml2 import config as ml2_config
# Load all the models to register them into SQLAlchemy metadata before using
# the SqlFixture
from neutron.db import models # noqa
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import worker
from neutron.plugins.ml2.drivers import type_geneve # noqa
from neutron.tests import base
from neutron.tests.common import base as common_base
from neutron.tests.common import helpers
from neutron.tests.functional.resources import process
from neutron.tests.unit.plugins.ml2 import test_plugin
LOG = log.getLogger(__name__)
# This is the directory from which infra fetches log files for functional tests
DEFAULT_LOG_DIR = os.path.join(helpers.get_test_log_path(),
'dsvm-functional-logs')
SQL_FIXTURE_LOCK = 'sql_fixture_lock'
def config_decorator(method_to_decorate, config_tuples):
@ -99,3 +123,237 @@ class BaseSudoTestCase(BaseLoggingTestCase):
ovs_conf.register_ovs_agent_opts, ovs_agent_opts)
mock.patch.object(ovs_conf, 'register_ovs_agent_opts',
new=ovs_agent_decorator).start()
class OVNSqlFixture(fixture.StaticSqlFixture):
@classmethod
@lockutils.synchronized(SQL_FIXTURE_LOCK)
def _init_resources(cls):
cls.schema_resource = provision.SchemaResource(
provision.DatabaseResource("sqlite"),
cls._generate_schema, teardown=False)
dependency_resources = {}
for name, resource in cls.schema_resource.resources:
dependency_resources[name] = resource.getResource()
cls.schema_resource.make(dependency_resources)
cls.engine = dependency_resources['database'].engine
def _delete_from_schema(self, engine):
try:
super(OVNSqlFixture, self)._delete_from_schema(engine)
except os_db_exc.DBNonExistentTable:
pass
class TestOVNFunctionalBase(test_plugin.Ml2PluginV2TestCase,
BaseLoggingTestCase):
OVS_DISTRIBUTION = 'openvswitch'
OVN_DISTRIBUTION = 'ovn'
OVN_SCHEMA_FILES = ['ovn-nb.ovsschema', 'ovn-sb.ovsschema']
_mechanism_drivers = ['logger', 'ovn']
_extension_drivers = ['port_security']
_counter = 0
l3_plugin = 'neutron.services.ovn_l3.plugin.OVNL3RouterPlugin'
def setUp(self, maintenance_worker=False):
ml2_config.cfg.CONF.set_override('extension_drivers',
self._extension_drivers,
group='ml2')
ml2_config.cfg.CONF.set_override('tenant_network_types',
['geneve'],
group='ml2')
ml2_config.cfg.CONF.set_override('vni_ranges',
['1:65536'],
group='ml2_type_geneve')
self.addCleanup(exts.PluginAwareExtensionManager.clear_instance)
super(TestOVNFunctionalBase, self).setUp()
self.test_log_dir = os.path.join(DEFAULT_LOG_DIR, self.id())
base.setup_test_logging(
cfg.CONF, self.test_log_dir, "testrun.txt")
mm = directory.get_plugin().mechanism_manager
self.mech_driver = mm.mech_drivers['ovn'].obj
self.l3_plugin = directory.get_plugin(constants.L3)
self.ovsdb_server_mgr = None
self.ovn_northd_mgr = None
self.maintenance_worker = maintenance_worker
self.temp_dir = self.useFixture(fixtures.TempDir()).path
self._start_ovsdb_server_and_idls()
self._start_ovn_northd()
def _get_install_share_path(self):
lookup_paths = set()
for installation in ['local', '']:
for distribution in [self.OVN_DISTRIBUTION, self.OVS_DISTRIBUTION]:
exists = True
for ovn_file in self.OVN_SCHEMA_FILES:
path = os.path.join(os.path.sep, 'usr', installation,
'share', distribution, ovn_file)
exists &= os.path.isfile(path)
lookup_paths.add(os.path.dirname(path))
if exists:
return os.path.dirname(path)
msg = 'Either ovn-nb.ovsschema and/or ovn-sb.ovsschema are missing. '
msg += 'Looked for schemas in paths:' + ', '.join(sorted(lookup_paths))
raise FileNotFoundError(
errno.ENOENT, os.strerror(errno.ENOENT), msg)
# FIXME(lucasagomes): Workaround for
# https://bugs.launchpad.net/networking-ovn/+bug/1808146. We should
# investigate and properly fix the problem. This method is just a
# workaround to alleviate the gate for now and should not be considered
# a proper fix.
def _setup_database_fixtures(self):
fixture = OVNSqlFixture()
self.useFixture(fixture)
self.engine = fixture.engine
def get_additional_service_plugins(self):
p = super(TestOVNFunctionalBase, self).get_additional_service_plugins()
p.update({'revision_plugin_name': 'revisions'})
return p
@property
def _ovsdb_protocol(self):
return self.get_ovsdb_server_protocol()
def get_ovsdb_server_protocol(self):
return 'unix'
def _start_ovn_northd(self):
if not self.ovsdb_server_mgr:
return
ovn_nb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('nb')
ovn_sb_db = self.ovsdb_server_mgr.get_ovsdb_connection_path('sb')
self.ovn_northd_mgr = self.useFixture(
process.OvnNorthd(self.temp_dir,
ovn_nb_db, ovn_sb_db,
protocol=self._ovsdb_protocol))
def _start_ovsdb_server_and_idls(self):
# Start 2 ovsdb-servers one each for OVN NB DB and OVN SB DB
# ovsdb-server with OVN SB DB can be used to test the chassis up/down
# events.
install_share_path = self._get_install_share_path()
self.ovsdb_server_mgr = self.useFixture(
process.OvsdbServer(self.temp_dir, install_share_path,
ovn_nb_db=True, ovn_sb_db=True,
protocol=self._ovsdb_protocol))
set_cfg = cfg.CONF.set_override
set_cfg('ovn_nb_connection',
self.ovsdb_server_mgr.get_ovsdb_connection_path(), 'ovn')
set_cfg('ovn_sb_connection',
self.ovsdb_server_mgr.get_ovsdb_connection_path(
db_type='sb'), 'ovn')
set_cfg('ovn_nb_private_key', self.ovsdb_server_mgr.private_key, 'ovn')
set_cfg('ovn_nb_certificate', self.ovsdb_server_mgr.certificate, 'ovn')
set_cfg('ovn_nb_ca_cert', self.ovsdb_server_mgr.ca_cert, 'ovn')
set_cfg('ovn_sb_private_key', self.ovsdb_server_mgr.private_key, 'ovn')
set_cfg('ovn_sb_certificate', self.ovsdb_server_mgr.certificate, 'ovn')
set_cfg('ovn_sb_ca_cert', self.ovsdb_server_mgr.ca_cert, 'ovn')
# 5 seconds should be more than enough for the transaction to complete
# for the test cases.
# This also fixes the bug #1607639.
cfg.CONF.set_override(
'ovsdb_connection_timeout', 5,
'ovn')
class TriggerCls(mock.MagicMock):
def trigger(self):
pass
trigger_cls = TriggerCls()
if self.maintenance_worker:
trigger_cls.trigger.__self__.__class__ = worker.MaintenanceWorker
cfg.CONF.set_override('neutron_sync_mode', 'off', 'ovn')
self.addCleanup(self._collect_processes_logs)
self.addCleanup(self.stop)
# mech_driver.post_fork_initialize creates the IDL connections
self.mech_driver.post_fork_initialize(
mock.ANY, mock.ANY, trigger_cls.trigger)
self.nb_api = self.mech_driver._nb_ovn
self.sb_api = self.mech_driver._sb_ovn
def _collect_processes_logs(self):
for database in ("nb", "sb"):
for file_suffix in ("log", "db"):
src_filename = "ovn_%(db)s.%(suffix)s" % {
'db': database,
'suffix': file_suffix
}
dst_filename = "ovn_%(db)s-%(timestamp)s.%(suffix)s" % {
'db': database,
'suffix': file_suffix,
'timestamp': datetime.now().strftime('%y-%m-%d_%H-%M-%S'),
}
filepath = os.path.join(self.temp_dir, src_filename)
shutil.copyfile(
filepath, os.path.join(self.test_log_dir, dst_filename))
def stop(self):
if self.maintenance_worker:
self.mech_driver.nb_synchronizer.stop()
self.mech_driver.sb_synchronizer.stop()
self.mech_driver._nb_ovn.ovsdb_connection.stop()
self.mech_driver._sb_ovn.ovsdb_connection.stop()
def restart(self):
self.stop()
# The OVN sync test starts its own synchronizers...
self.l3_plugin._nb_ovn_idl.ovsdb_connection.stop()
self.l3_plugin._sb_ovn_idl.ovsdb_connection.stop()
# Stop our monitor connections
self.nb_api.ovsdb_connection.stop()
self.sb_api.ovsdb_connection.stop()
if self.ovsdb_server_mgr:
self.ovsdb_server_mgr.stop()
if self.ovn_northd_mgr:
self.ovn_northd_mgr.stop()
self.mech_driver._nb_ovn = None
self.mech_driver._sb_ovn = None
self.l3_plugin._nb_ovn_idl = None
self.l3_plugin._sb_ovn_idl = None
self.nb_api.ovsdb_connection = None
self.sb_api.ovsdb_connection = None
self.ovsdb_server_mgr.delete_dbs()
self._start_ovsdb_server_and_idls()
self._start_ovn_northd()
def add_fake_chassis(self, host, physical_nets=None, external_ids=None,
name=None):
physical_nets = physical_nets or []
external_ids = external_ids or {}
bridge_mapping = ",".join(["%s:br-provider%s" % (phys_net, i)
for i, phys_net in enumerate(physical_nets)])
if name is None:
name = uuidutils.generate_uuid()
external_ids['ovn-bridge-mappings'] = bridge_mapping
# We'll be using different IP addresses every time for the Encap of
# the fake chassis as the SB schema doesn't allow to have two entries
# with same (ip,type) pairs as of OVS 2.11. This shouldn't have any
# impact as the tunnels won't get created anyways since ovn-controller
# is not running. Ideally we shouldn't be creating more than 255
# fake chassis but from the SB db point of view, 'ip' column can be
# any string so we could add entries with ip='172.24.4.1000'.
self._counter += 1
self.sb_api.chassis_add(
name, ['geneve'], '172.24.4.%d' % self._counter,
external_ids=external_ids, hostname=host).execute(check_error=True)
return name
def del_fake_chassis(self, chassis, if_exists=True):
self.sb_api.chassis_del(
chassis, if_exists=if_exists).execute(check_error=True)

0
neutron/tests/functional/plugins/ml2/drivers/ovn/__init__.py

0
neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/__init__.py

0
neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/__init__.py

149
neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_impl_idl.py

@ -0,0 +1,149 @@
#
# 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 ovsdbapp import event as ovsdb_event
from ovsdbapp.tests.functional import base
from ovsdbapp.tests import utils
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb \
import impl_idl_ovn as impl
from neutron.tests.functional import base as n_base
from neutron.tests.functional.resources.ovsdb import events
class TestSbApi(base.FunctionalTestCase,
n_base.BaseLoggingTestCase):
schemas = ['OVN_Southbound', 'OVN_Northbound']
def setUp(self):
super(TestSbApi, self).setUp()
self.data = {
'chassis': [
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex,private:br-0'}},
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex,public2:br-ex'}},
{'external_ids': {'ovn-bridge-mappings':
'public:br-ex'}},
]
}
self.api = impl.OvsdbSbOvnIdl(self.connection['OVN_Southbound'])
self.nbapi = impl.OvsdbNbOvnIdl(self.connection['OVN_Northbound'])
self.load_test_data()
self.handler = ovsdb_event.RowEventHandler()
self.api.idl.notify = self.handler.notify
def load_test_data(self):
with self.api.transaction(check_error=True) as txn:
for chassis in self.data['chassis']:
chassis['name'] = utils.get_rand_device_name('chassis')
chassis['hostname'] = '%s.localdomain.com' % chassis['name']
txn.add(self.api.chassis_add(
chassis['name'], ['geneve'], chassis['hostname'],
hostname=chassis['hostname'],
external_ids=chassis['external_ids']))
def test_get_chassis_hostname_and_physnets(self):
mapping = self.api.get_chassis_hostname_and_physnets()
self.assertLessEqual(len(self.data['chassis']), len(mapping))
self.assertGreaterEqual(set(mapping.keys()),
{c['hostname'] for c in self.data['chassis']})
def test_get_all_chassis(self):
chassis_list = set(self.api.get_all_chassis())
our_chassis = {c['name'] for c in self.data['chassis']}
self.assertLessEqual(our_chassis, chassis_list)
def test_get_chassis_data_for_ml2_bind_port(self):
host = self.data['chassis'][0]['hostname']
dp, iface, phys = self.api.get_chassis_data_for_ml2_bind_port(host)
self.assertEqual('', dp)
self.assertEqual('', iface)
self.assertItemsEqual(phys, ['private', 'public'])
def test_chassis_exists(self):
self.assertTrue(self.api.chassis_exists(
self.data['chassis'][0]['hostname']))
self.assertFalse(self.api.chassis_exists("nochassishere"))
def test_get_chassis_and_physnets(self):
mapping = self.api.get_chassis_and_physnets()
self.assertLessEqual(len(self.data['chassis']), len(mapping))
self.assertGreaterEqual(set(mapping.keys()),
{c['name'] for c in self.data['chassis']})
def _add_switch_port(self, chassis_name, type='localport'):
sname, pname = (utils.get_rand_device_name(prefix=p)
for p in ('switch', 'port'))
chassis = self.api.lookup('Chassis', chassis_name)
row_event = events.WaitForCreatePortBindingEvent(pname)
self.handler.watch_event(row_event)
with self.nbapi.transaction(check_error=True) as txn:
switch = txn.add(self.nbapi.ls_add(sname))
port = txn.add(self.nbapi.lsp_add(sname, pname, type=type))
row_event.wait()
return chassis, switch.result, port.result, row_event.row
def test_get_metadata_port_network(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
result = self.api.get_metadata_port_network(str(binding.datapath.uuid))
self.assertEqual(binding, result)
self.assertEqual(binding.datapath.external_ids['logical-switch'],
str(switch.uuid))
def test_get_metadata_port_network_missing(self):
val = str(uuid.uuid4())
self.assertIsNone(self.api.get_metadata_port_network(val))
def test_set_get_chassis_metadata_networks(self):
name = self.data['chassis'][0]['name']
nets = [str(uuid.uuid4()) for _ in range(3)]
self.api.set_chassis_metadata_networks(name, nets).execute(
check_error=True)
self.assertEqual(nets, self.api.get_chassis_metadata_networks(name))
def test_get_network_port_bindings_by_ip(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
mac = 'de:ad:be:ef:4d:ad'
ipaddr = '192.0.2.1'
mac_ip = '%s %s' % (mac, ipaddr)
pb_update_event = events.WaitForUpdatePortBindingEvent(
port.name, mac=[mac_ip])
self.handler.watch_event(pb_update_event)
self.nbapi.lsp_set_addresses(
port.name, [mac_ip]).execute(check_error=True)
self.assertTrue(pb_update_event.wait())
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
result = self.api.get_network_port_bindings_by_ip(
str(binding.datapath.uuid), ipaddr)
self.assertIn(binding, result)
def test_get_ports_on_chassis(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
self.assertEqual([binding],
self.api.get_ports_on_chassis(chassis.name))
def test_get_logical_port_chassis_and_datapath(self):
chassis, switch, port, binding = self._add_switch_port(
self.data['chassis'][0]['name'])
self.api.lsp_bind(port.name, chassis.name).execute(check_error=True)
self.assertEqual(
(chassis.name, str(binding.datapath.uuid)),
self.api.get_logical_port_chassis_and_datapath(port.name))

800
neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/ovsdb/test_maintenance.py

@ -0,0 +1,800 @@
# 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 mock
from futurist import periodics
from neutron_lib.api.definitions import external_net as extnet_apidef
from neutron_lib import constants as n_const
from neutron_lib import context as n_context
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.conf.plugins.ml2.drivers.ovn import ovn_conf as ovn_config
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.plugins.ml2.drivers.ovn.mech_driver.ovsdb import maintenance
from neutron.tests.functional import base
from neutron.tests.unit.api import test_extensions
from neutron.tests.unit.extensions import test_extraroute
class _TestMaintenanceHelper(base.TestOVNFunctionalBase):
"""A helper class to keep the code more organized."""
def setUp(self):
super(_TestMaintenanceHelper, self).setUp()
self._ovn_client = self.mech_driver._ovn_client
self._l3_ovn_client = self.l3_plugin._ovn_client
ext_mgr = test_extraroute.ExtraRouteTestExtensionManager()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
self.maint = maintenance.DBInconsistenciesPeriodics(self._ovn_client)
self.context = n_context.get_admin_context()
# Always verify inconsistencies for all objects.
db_rev.INCONSISTENCIES_OLDER_THAN = -1
def _find_network_row_by_name(self, name):
for row in self.nb_api._tables['Logical_Switch'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_NETWORK_NAME_EXT_ID_KEY) == name):
return row
def _create_network(self, name, external=False):
data = {'network': {'name': name, 'tenant_id': self._tenant_id,
extnet_apidef.EXTERNAL: external}}
req = self.new_create_request('networks', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['network']
def _update_network_name(self, net_id, new_name):
data = {'network': {'name': new_name}}
req = self.new_update_request('networks', data, net_id, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['network']
def _create_port(self, name, net_id, security_groups=None,
device_owner=None):
data = {'port': {'name': name,
'tenant_id': self._tenant_id,
'network_id': net_id}}
if security_groups is not None:
data['port']['security_groups'] = security_groups
if device_owner is not None:
data['port']['device_owner'] = device_owner
req = self.new_create_request('ports', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['port']
def _update_port_name(self, port_id, new_name):
data = {'port': {'name': new_name}}
req = self.new_update_request('ports', data, port_id, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['port']
def _find_port_row_by_name(self, name):
for row in self.nb_api._tables['Logical_Switch_Port'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_PORT_NAME_EXT_ID_KEY) == name):
return row
def _set_global_dhcp_opts(self, ip_version, opts):
opt_string = ','.join(['{0}:{1}'.format(key, value)
for key, value
in opts.items()])
if ip_version == 6:
ovn_config.cfg.CONF.set_override('ovn_dhcp6_global_options',
opt_string,
group='ovn')
if ip_version == 4:
ovn_config.cfg.CONF.set_override('ovn_dhcp4_global_options',
opt_string,
group='ovn')
def _unset_global_dhcp_opts(self, ip_version):
if ip_version == 6:
ovn_config.cfg.CONF.clear_override('ovn_dhcp6_global_options',
group='ovn')
if ip_version == 4:
ovn_config.cfg.CONF.clear_override('ovn_dhcp4_global_options',
group='ovn')
def _create_subnet(self, name, net_id, ip_version=4):
data = {'subnet': {'name': name,
'tenant_id': self._tenant_id,
'network_id': net_id,
'ip_version': ip_version,
'enable_dhcp': True}}
if ip_version == 4:
data['subnet']['cidr'] = '10.0.0.0/24'
else:
data['subnet']['cidr'] = 'eef0::/64'
req = self.new_create_request('subnets', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['subnet']
def _update_subnet_enable_dhcp(self, subnet_id, value):
data = {'subnet': {'enable_dhcp': value}}
req = self.new_update_request('subnets', data, subnet_id, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['subnet']
def _find_subnet_row_by_id(self, subnet_id):
for row in self.nb_api._tables['DHCP_Options'].rows.values():
if (row.external_ids.get('subnet_id') == subnet_id and
not row.external_ids.get('port_id')):
return row
def _create_router(self, name, external_gateway_info=None):
data = {'router': {'name': name, 'tenant_id': self._tenant_id}}
if external_gateway_info is not None:
data['router']['external_gateway_info'] = external_gateway_info
req = self.new_create_request('routers', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['router']
def _update_router_name(self, net_id, new_name):
data = {'router': {'name': new_name}}
req = self.new_update_request('routers', data, net_id, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['router']
def _find_router_row_by_name(self, name):
for row in self.nb_api._tables['Logical_Router'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_ROUTER_NAME_EXT_ID_KEY) == name):
return row
def _create_security_group(self):
data = {'security_group': {'name': 'sgtest',
'tenant_id': self._tenant_id,
'description': 'SpongeBob Rocks!'}}
req = self.new_create_request('security-groups', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['security_group']
def _find_security_group_row_by_id(self, sg_id):
if self.nb_api.is_port_groups_supported():
for row in self.nb_api._tables['Port_Group'].rows.values():
if row.name == utils.ovn_port_group_name(sg_id):
return row
else:
for row in self.nb_api._tables['Address_Set'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_SG_EXT_ID_KEY) == sg_id):
return row
def _create_security_group_rule(self, sg_id):
data = {'security_group_rule': {'security_group_id': sg_id,
'direction': 'ingress',
'protocol': n_const.PROTO_NAME_TCP,
'ethertype': n_const.IPv4,
'port_range_min': 22,
'port_range_max': 22,
'tenant_id': self._tenant_id}}
req = self.new_create_request('security-group-rules', data, self.fmt)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)['security_group_rule']
def _find_security_group_rule_row_by_id(self, sgr_id):
for row in self.nb_api._tables['ACL'].rows.values():
if (row.external_ids.get(
ovn_const.OVN_SG_RULE_EXT_ID_KEY) == sgr_id):
return row
def _process_router_interface(self, action, router_id, subnet_id):
req = self.new_action_request(
'routers', {'subnet_id': subnet_id}, router_id,
'%s_router_interface' % action)
res = req.get_response(self.api)
return self.deserialize(self.fmt, res)
def _add_router_interface(self, router_id, subnet_id):
return self._process_router_interface('add', router_id, subnet_id)
def _remove_router_interface(self, router_id, subnet_id):
return self._process_router_interface('remove', router_id, subnet_id)
def _find_router_port_row_by_port_id(self, port_id):
for row in self.nb_api._tables['Logical_Router_Port'].rows.values():
if row.name == utils.ovn_lrouter_port_name(port_id):
return row
class TestMaintenance(_TestMaintenanceHelper):
def test_network(self):
net_name = 'networktest'
with mock.patch.object(self._ovn_client, 'create_network'):
neutron_obj = self._create_network(net_name)
# Assert the network doesn't exist in OVN
self.assertIsNone(self._find_network_row_by_name(net_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the network was now created
ovn_obj = self._find_network_row_by_name(net_name)
self.assertIsNotNone(ovn_obj)
self.assertEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Update
new_obj_name = 'networktest_updated'
with mock.patch.object(self._ovn_client, 'update_network'):
new_neutron_obj = self._update_network_name(neutron_obj['id'],
new_obj_name)
# Assert the revision numbers are out-of-sync
ovn_obj = self._find_network_row_by_name(net_name)
self.assertNotEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the old name doesn't exist anymore in the OVNDB
self.assertIsNone(self._find_network_row_by_name(net_name))
# Assert the network is now in sync
ovn_obj = self._find_network_row_by_name(new_obj_name)
self.assertEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Delete
with mock.patch.object(self._ovn_client, 'delete_network'):
self._delete('networks', new_neutron_obj['id'])
# Assert the network still exists in OVNDB
self.assertIsNotNone(self._find_network_row_by_name(new_obj_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the network is now deleted from OVNDB
self.assertIsNone(self._find_network_row_by_name(new_obj_name))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
new_neutron_obj['id']))
def test_port(self):
obj_name = 'porttest'
neutron_net = self._create_network('network1')
with mock.patch.object(self._ovn_client, 'create_port'):
neutron_obj = self._create_port(obj_name, neutron_net['id'])
# Assert the port doesn't exist in OVN
self.assertIsNone(self._find_port_row_by_name(obj_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the port was now created
ovn_obj = self._find_port_row_by_name(obj_name)
self.assertIsNotNone(ovn_obj)
self.assertEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Update
new_obj_name = 'porttest_updated'
with mock.patch.object(self._ovn_client, 'update_port'):
new_neutron_obj = self._update_port_name(neutron_obj['id'],
new_obj_name)
# Assert the revision numbers are out-of-sync
ovn_obj = self._find_port_row_by_name(obj_name)
self.assertNotEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the old name doesn't exist anymore in the OVNDB
self.assertIsNone(self._find_port_row_by_name(obj_name))
# Assert the port is now in sync. Note that for ports we are
# fetching it again from the Neutron database prior to comparison
# because of the monitor code that can update the ports again upon
# changes to it.
ovn_obj = self._find_port_row_by_name(new_obj_name)
new_neutron_obj = self._ovn_client._plugin.get_port(
self.context, neutron_obj['id'])
self.assertEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Delete
with mock.patch.object(self._ovn_client, 'delete_port'):
self._delete('ports', new_neutron_obj['id'])
# Assert the port still exists in OVNDB
self.assertIsNotNone(self._find_port_row_by_name(new_obj_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the port is now deleted from OVNDB
self.assertIsNone(self._find_port_row_by_name(new_obj_name))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
neutron_obj['id']))
def test_subnet_global_dhcp4_opts(self):
obj_name = 'globaltestsubnet'
options = {'ntp_server': '1.2.3.4'}
neutron_net = self._create_network('network1')
# Create a subnet without global options
neutron_sub = self._create_subnet(obj_name, neutron_net['id'])
# Assert that the option is not set
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertIsNone(ovn_obj.options.get('ntp_server', None))
# Set some global DHCP Options
self._set_global_dhcp_opts(ip_version=4, opts=options)
# Run the maintenance task to add the new options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was added
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertEqual(
ovn_obj.options.get('ntp_server', None),
'1.2.3.4')
# Change the global option
new_options = {'ntp_server': '4.3.2.1'}
self._set_global_dhcp_opts(ip_version=4, opts=new_options)
# Run the maintenance task to update the options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was changed
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertEqual(
ovn_obj.options.get('ntp_server', None),
'4.3.2.1')
# Change the global option to null
new_options = {'ntp_server': ''}
self._set_global_dhcp_opts(ip_version=4, opts=new_options)
# Run the maintenance task to update the options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was removed
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertIsNone(ovn_obj.options.get('ntp_server', None))
def test_subnet_global_dhcp6_opts(self):
obj_name = 'globaltestsubnet'
options = {'ntp_server': '1.2.3.4'}
neutron_net = self._create_network('network1')
# Create a subnet without global options
neutron_sub = self._create_subnet(obj_name, neutron_net['id'], 6)
# Assert that the option is not set
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertIsNone(ovn_obj.options.get('ntp_server', None))
# Set some global DHCP Options
self._set_global_dhcp_opts(ip_version=6, opts=options)
# Run the maintenance task to add the new options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was added
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertEqual(
ovn_obj.options.get('ntp_server', None),
'1.2.3.4')
# Change the global option
new_options = {'ntp_server': '4.3.2.1'}
self._set_global_dhcp_opts(ip_version=6, opts=new_options)
# Run the maintenance task to update the options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was changed
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertEqual(
ovn_obj.options.get('ntp_server', None),
'4.3.2.1')
# Change the global option to null
new_options = {'ntp_server': ''}
self._set_global_dhcp_opts(ip_version=6, opts=new_options)
# Run the maintenance task to update the options
self.assertRaises(periodics.NeverAgain,
self.maint.check_global_dhcp_opts)
# Assert that the option was removed
ovn_obj = self._find_subnet_row_by_id(neutron_sub['id'])
self.assertIsNone(ovn_obj.options.get('ntp_server', None))
def test_subnet(self):
obj_name = 'subnettest'
neutron_net = self._create_network('network1')
with mock.patch.object(self._ovn_client, 'create_subnet'):
neutron_obj = self._create_subnet(obj_name, neutron_net['id'])
# Assert the subnet doesn't exist in OVN
self.assertIsNone(self._find_subnet_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the subnet was now created
ovn_obj = self._find_subnet_row_by_id(neutron_obj['id'])
self.assertIsNotNone(ovn_obj)
self.assertEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Update
with mock.patch.object(self._ovn_client, 'update_subnet'):
neutron_obj = self._update_subnet_enable_dhcp(
neutron_obj['id'], False)
# Assert the revision numbers are out-of-sync
ovn_obj = self._find_subnet_row_by_id(neutron_obj['id'])
self.assertNotEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the old name doesn't exist anymore in the OVNDB. When
# the subnet's enable_dhcp's is set to False, OVN will remove the
# DHCP_Options entry related to that subnet.
self.assertIsNone(self._find_subnet_row_by_id(neutron_obj['id']))
# Re-enable the DHCP for the subnet and check if the maintenance
# thread will re-create it in OVN
with mock.patch.object(self._ovn_client, 'update_subnet'):
neutron_obj = self._update_subnet_enable_dhcp(
neutron_obj['id'], True)
# Assert the DHCP_Options still doesn't exist in OVNDB
self.assertIsNone(self._find_subnet_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the subnet is now in sync
ovn_obj = self._find_subnet_row_by_id(neutron_obj['id'])
self.assertEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Delete
with mock.patch.object(self._ovn_client, 'delete_subnet'):
self._delete('subnets', neutron_obj['id'])
# Assert the subnet still exists in OVNDB
self.assertIsNotNone(self._find_subnet_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the subnet is now deleted from OVNDB
self.assertIsNone(self._find_subnet_row_by_id(neutron_obj['id']))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
neutron_obj['id']))
def test_router(self):
obj_name = 'routertest'
with mock.patch.object(self._l3_ovn_client, 'create_router'):
neutron_obj = self._create_router(obj_name)
# Assert the router doesn't exist in OVN
self.assertIsNone(self._find_router_row_by_name(obj_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the router was now created
ovn_obj = self._find_router_row_by_name(obj_name)
self.assertIsNotNone(ovn_obj)
self.assertEqual(
neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Update
new_obj_name = 'routertest_updated'
with mock.patch.object(self._l3_ovn_client, 'update_router'):
new_neutron_obj = self._update_router_name(neutron_obj['id'],
new_obj_name)
# Assert the revision numbers are out-of-sync
ovn_obj = self._find_router_row_by_name(obj_name)
self.assertNotEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the old name doesn't exist anymore in the OVNDB
self.assertIsNone(self._find_router_row_by_name(obj_name))
# Assert the router is now in sync
ovn_obj = self._find_router_row_by_name(new_obj_name)
self.assertEqual(
new_neutron_obj['revision_number'],
int(ovn_obj.external_ids[ovn_const.OVN_REV_NUM_EXT_ID_KEY]))
# > Delete
with mock.patch.object(self._l3_ovn_client, 'delete_router'):
self._delete('routers', new_neutron_obj['id'])
# Assert the router still exists in OVNDB
self.assertIsNotNone(self._find_router_row_by_name(new_obj_name))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the router is now deleted from OVNDB
self.assertIsNone(self._find_router_row_by_name(new_obj_name))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
new_neutron_obj['id']))
def test_security_group(self):
with mock.patch.object(self._ovn_client, 'create_security_group'):
neutron_obj = self._create_security_group()
# Assert the sg doesn't exist in OVN
self.assertIsNone(
self._find_security_group_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the sg was now created. We don't save the revision number
# in the Security Group because OVN doesn't support updating it,
# all we care about is whether it exists or not.
self.assertIsNotNone(
self._find_security_group_row_by_id(neutron_obj['id']))
# > Delete
with mock.patch.object(self._ovn_client, 'delete_security_group'):
self._delete('security-groups', neutron_obj['id'])
# Assert the sg still exists in OVNDB
self.assertIsNotNone(
self._find_security_group_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the sg is now deleted from OVNDB
self.assertIsNone(
self._find_security_group_row_by_id(neutron_obj['id']))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
neutron_obj['id']))
def test_security_group_rule(self):
neutron_sg = self._create_security_group()
neutron_net = self._create_network('network1')
self._create_port('portsgtest', neutron_net['id'],
security_groups=[neutron_sg['id']])
with mock.patch.object(self._ovn_client, 'create_security_group_rule'):
neutron_obj = self._create_security_group_rule(neutron_sg['id'])
# Assert the sg rule doesn't exist in OVN
self.assertIsNone(
self._find_security_group_rule_row_by_id(neutron_obj['id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the sg rule was now created. We don't save the revision number
# in the Security Group because OVN doesn't support updating it,
# all we care about is whether it exists or not.
self.assertIsNotNone(
self._find_security_group_rule_row_by_id(neutron_obj['id']))
# > Delete
# FIXME(lucasagomes): Maintenance thread fixing deleted
# security group rules is currently broken due to:
# https://bugs.launchpad.net/networking-ovn/+bug/1756123
def test_router_port(self):
neutron_net = self._create_network('networktest', external=True)
neutron_subnet = self._create_subnet('subnettest', neutron_net['id'])
neutron_router = self._create_router('routertest')
with mock.patch.object(self._l3_ovn_client, 'create_router_port'):
with mock.patch('neutron.db.ovn_revision_numbers_db.'
'bump_revision'):
neutron_obj = self._add_router_interface(neutron_router['id'],
neutron_subnet['id'])
# Assert the router port doesn't exist in OVN
self.assertIsNone(
self._find_router_port_row_by_port_id(neutron_obj['port_id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the router port was now created
self.assertIsNotNone(
self._find_router_port_row_by_port_id(neutron_obj['port_id']))
# > Delete
with mock.patch.object(self._l3_ovn_client, 'delete_router_port'):
self._remove_router_interface(neutron_router['id'],
neutron_subnet['id'])
# Assert the router port still exists in OVNDB
self.assertIsNotNone(
self._find_router_port_row_by_port_id(neutron_obj['port_id']))
# Call the maintenance thread to fix the problem
self.maint.check_for_inconsistencies()
# Assert the router port is now deleted from OVNDB
self.assertIsNone(
self._find_router_port_row_by_port_id(neutron_obj['port_id']))
# Assert the revision number no longer exists
self.assertIsNone(db_rev.get_revision_row(
self.context,
neutron_obj['port_id']))
def test_check_metadata_ports(self):
ovn_config.cfg.CONF.set_override('ovn_metadata_enabled', True,
group='ovn')
neutron_net = self._create_network('network1')
metadata_port = self._ovn_client._find_metadata_port(
self.context, neutron_net['id'])
# Assert the metadata port exists
self.assertIsNotNone(metadata_port)
# Delete the metadata port
self._delete('ports', metadata_port['id'])
metadata_port = self._ovn_client._find_metadata_port(
self.context, neutron_net['id'])
# Assert the metadata port is gone
self.assertIsNone(metadata_port)
# Call the maintenance thread to fix the problem, it will raise
# NeverAgain so that the job only runs once at startup
self.assertRaises(periodics.NeverAgain,
self.maint.check_metadata_ports)
metadata_port = self._ovn_client._find_metadata_port(
self.context, neutron_net['id'])
# Assert the metadata port was re-created
self.assertIsNotNone(metadata_port)
def test_check_metadata_ports_not_enabled(self):
ovn_config.cfg.CONF.set_override('ovn_metadata_enabled', False,
group='ovn')
with mock.patch.object(self._ovn_client,
'create_metadata_port') as mock_create_port:
self.assertRaises(periodics.NeverAgain,
self.maint.check_metadata_ports)
# Assert create_metadata_port() wasn't called since metadata
# is not enabled
self.assertFalse(mock_create_port.called)
def test_check_for_port_security_unknown_address(self):
neutron_net = self._create_network('network1')
neutron_port = self._create_port('port1', neutron_net['id'])
# Let's force disabling port security for the LSP
self.nb_api.lsp_set_port_security(neutron_port['id'], []).execute(
check_error=True)
ovn_port = self.nb_api.db_find(
'Logical_Switch_Port', ('name', '=', neutron_port['id'])).execute(
check_error=True)[0]
# Assert that port security is now disabled but the 'unknown'
# is not set in the addresses column
self.assertFalse(ovn_port['port_security'])
self.assertNotIn('unknown', ovn_port['addresses'])
# Call the maintenance task to fix the problem. Note that
# NeverAgain is raised so it only runs once at start up
self.assertRaises(periodics.NeverAgain,
self.maint.check_for_port_security_unknown_address)
ovn_port = self.nb_api.db_find(
'Logical_Switch_Port', ('name', '=', neutron_port['id'])).execute(
check_error=True)[0]
# Assert that 'unknown' was set in the addresses column for
# the port
self.assertFalse(ovn_port['port_security'])
self.assertIn('unknown', ovn_port['addresses'])
# Now the other way around, let's set port_security in the OVN
# table while the 'unknown' address is set in the addresses column
self.nb_api.lsp_set_port_security(
neutron_port['id'], ovn_port['addresses']).execute(
check_error=True)
ovn_port = self.nb_api.db_find(
'Logical_Switch_Port', ('name', '=', neutron_port['id'])).execute(
check_error=True)[0]
self.assertTrue(ovn_port['port_security'])
self.assertIn('unknown', ovn_port['addresses'])
# Call the maintenance task to fix the problem. Note that
# NeverAgain is raised so it only runs once at start up
self.assertRaises(periodics.NeverAgain,
self.maint.check_for_port_security_unknown_address)
ovn_port = self.nb_api.db_find(
'Logical_Switch_Port', ('name', '=', neutron_port['id'])).execute(
check_error=True)[0]
# Assert that 'unknown' was removed from the addresses column
# for the port
self.assertTrue(ovn_port['port_security'])
self.assertNotIn('unknown', ovn_port['addresses'])

432
neutron/tests/functional/plugins/ml2/drivers/ovn/mech_driver/test_mech_driver.py

@ -0,0 +1,432 @@
# Copyright 2016 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 functools
import mock
from oslo_config import cfg
from oslo_utils import uuidutils
from neutron.common.ovn import constants as ovn_const
from neutron.common.ovn import utils
from neutron.common import utils as n_utils
from neutron.db import ovn_revision_numbers_db as db_rev
from neutron.tests.functional import base
class TestPortBinding(base.TestOVNFunctionalBase):
def setUp(self):
super(TestPortBinding, self).setUp()
self.ovs_host = 'ovs-host'
self.dpdk_host = 'dpdk-host'
self.invalid_dpdk_host = 'invalid-host'
self.vhu_mode = 'server'
self.add_fake_chassis(self.ovs_host)
self.add_fake_chassis(
self.dpdk_host,
external_ids={'datapath-type': 'netdev',
'iface-types': 'dummy,dummy-internal,dpdkvhostuser'})
self.add_fake_chassis(
self.invalid_dpdk_host,
external_ids={'datapath-type': 'netdev',
'iface-types': 'dummy,dummy-internal,geneve,vxlan'})
self.n1 = self._make_network(self.fmt, 'n1', True)
res = self._create_subnet(self.fmt, self.n1['network']['id'],
'10.0.0.0/24')
self.deserialize(self.fmt, res)
def _create_or_update_port(self, port_id=None, hostname=None):
if port_id is None:
port_data = {
'port': {'network_id': self.n1['network']['id'],