From bc004045cd30a708eadd64ded1af1b30ef1d1918 Mon Sep 17 00:00:00 2001 From: Rodolfo Alonso Hernandez Date: Thu, 8 Apr 2021 13:41:21 +0000 Subject: [PATCH] Add an active wait in the "Backend.lookup" Added an active wait in the method "Backend.lookup". If a timeout and a RowEventHandler instance, listening on the same IDL connection, are passed, if the "lookup" method does not find the requested register, the method creates a WaitEvent instance related to the table and register requested. If the wait event returns, the expected record will be stored in the event "result" member and will be the value returned by this method. If the wait event timeout expires, the method will throw an ``idlutils.RowNotFound`` exception. Closes-Bug: #1922934 Change-Id: I580bfb640d8157b4e749d7b1fe5de7568e99943d --- ovsdbapp/backend/ovs_idl/__init__.py | 48 +++++++++++++- .../backend/ovs_idl/test_backend.py | 63 +++++++++++++++++++ 2 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 ovsdbapp/tests/functional/backend/ovs_idl/test_backend.py diff --git a/ovsdbapp/backend/ovs_idl/__init__.py b/ovsdbapp/backend/ovs_idl/__init__.py index 64fd3f70..e4d0cde1 100644 --- a/ovsdbapp/backend/ovs_idl/__init__.py +++ b/ovsdbapp/backend/ovs_idl/__init__.py @@ -14,6 +14,7 @@ import logging import uuid from ovsdbapp.backend.ovs_idl import command as cmd +from ovsdbapp.backend.ovs_idl import event from ovsdbapp.backend.ovs_idl import idlutils from ovsdbapp.backend.ovs_idl import transaction from ovsdbapp import exceptions @@ -22,6 +23,27 @@ LOG = logging.getLogger(__name__) _NO_DEFAULT = object() +class LookupWaitEvent(event.WaitEvent): + + def __init__(self, backend, table, record, timeout): + events = (self.ROW_CREATE, self.ROW_UPDATE) + super().__init__(events, table, None, timeout=timeout) + self.backend = backend + self.record = record + self.event_name = 'LookupWaitEvent_%s' % table + self.result = None + + def match_fn(self, event, row, old): + try: + # Normally, we would use run() to do things on match, but in this + # case, that would mean we'd have to run lookup() again. + with self.backend.ovsdb_connection.lock: + self.result = self.backend.lookup(self.table, self.record) + return bool(self.result) + except idlutils.RowNotFound: + return False + + class Backend(object): lookup_table = {} _ovsdb_connection = None @@ -171,14 +193,36 @@ class Backend(object): return cmd.DbRemoveCommand(self, table, record, column, *values, **keyvalues) - def lookup(self, table, record, default=_NO_DEFAULT): + def lookup(self, table, record, default=_NO_DEFAULT, timeout=None, + notify_handler=None): + """Search for a record in a table + + If timeout and notify_handler of type ``row_event.RowEventHandler`` + are passed, in case the record is not present in the selected table, + the method creates an event, waiting for this record (UUID), on this + table and events CREATE and UPDATE. The event returns with the record + memoized if the record was created or updated. + """ try: with self.ovsdb_connection.lock: return self._lookup(table, record) except idlutils.RowNotFound: if default is not _NO_DEFAULT: return default - raise + if not notify_handler: + notify_handler = getattr(self, 'notify_handler', None) + if not (timeout and notify_handler): + raise + + wait_event = LookupWaitEvent(self, table, record, timeout) + notify_handler.watch_event(wait_event) + if not wait_event.wait(): + LOG.info('Record %s from table %s was not registered in the ' + 'IDL DB cache after %d seconds', record, table, + timeout) + notify_handler.unwatch_event(wait_event) + raise + return wait_event.result def _lookup(self, table, record): if record == "": diff --git a/ovsdbapp/tests/functional/backend/ovs_idl/test_backend.py b/ovsdbapp/tests/functional/backend/ovs_idl/test_backend.py new file mode 100644 index 00000000..ea598c0c --- /dev/null +++ b/ovsdbapp/tests/functional/backend/ovs_idl/test_backend.py @@ -0,0 +1,63 @@ +# 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 threading +import time +import uuid + +from ovsdbapp.backend.ovs_idl import event +from ovsdbapp.backend.ovs_idl import idlutils +from ovsdbapp.schema.ovn_northbound import impl_idl +from ovsdbapp.tests.functional import base + + +class TestOvnNbIndex(base.FunctionalTestCase): + schemas = ['OVN_Northbound'] + + def setUp(self): + super(TestOvnNbIndex, self).setUp() + self.api = impl_idl.OvnNbApiIdlImpl(self.connection) + self.lsp_name = str(uuid.uuid4()) + self.a = None + + def _create_ls(self): + time.sleep(1) # Wait a bit to allow a first unsuccessful lookup(). + self.api.db_create('Logical_Switch', name=self.lsp_name).execute() + + def test_lookup_with_timeout_and_notify_handler(self): + notify_handler = event.RowEventHandler() + self.api.idl.notify = notify_handler.notify + t_create = threading.Thread(target=self._create_ls, args=()) + t_create.start() + ret = self.api.lookup('Logical_Switch', self.lsp_name, timeout=3, + notify_handler=notify_handler) + self.assertEqual(self.lsp_name, ret.name) + t_create.join() + + def _test_lookup_exception(self, timeout, notify_handler): + if notify_handler: + self.api.idl.notify = notify_handler.notify + t_create = threading.Thread(target=self._create_ls, args=()) + t_create.start() + self.assertRaises(idlutils.RowNotFound, self.api.lookup, + 'Logical_Switch', self.lsp_name, timeout=timeout, + notify_handler=notify_handler) + t_create.join() + + def test_lookup_without_timeout(self): + self._test_lookup_exception(0, event.RowEventHandler()) + + def test_lookup_without_event_handler(self): + self._test_lookup_exception(3, None) + + def test_lookup_without_timeout_and_event_handler(self): + self._test_lookup_exception(0, None)