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
This commit is contained in:
Rodolfo Alonso Hernandez 2021-04-08 13:41:21 +00:00 committed by Rodolfo Alonso
parent 627b14e2a7
commit bc004045cd
2 changed files with 109 additions and 2 deletions

View File

@ -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 == "":

View File

@ -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)