[ovs-dev] [PATCH v2] python: Send notifications after the transaction ends

Brian Haley haleyb.dev at gmail.com
Wed Mar 3 20:36:04 UTC 2021


On 3/3/21 3:18 PM, Terry Wilson wrote:
> On Wed, Mar 3, 2021 at 1:47 PM Brian Haley <haleyb.dev at gmail.com> wrote:
>>
>> Hi Terry,
>>
>> Thanks for the patch, comments below.
>>
>> On 3/3/21 9:24 AM, Terry Wilson wrote:
>>> The Python IDL notification mechanism was sending a notification
>>> for each processed update in a transaction as it was processed.
>>> This causes isseues with multi-row change that contain references
>>> to each other.
>>>
>>> For example, if a Logical_Router_Port is created along with a
>>> Gateway_Chassis, and the LRP.gateway_chassis set to that GC, then
>>> when the notify() passes the CREATE event for the LRP, the GC will
>>> not yet have been processed, so __getattr__ when _uuid_to_row fails
>>> to find the GC, will return the default value for LRP.gateway_chassis
>>> which is [].
>>>
>>> This patch has the process_update methods return the notifications
>>> that would be produced when a row changes, so they can be queued
>>> and sent after all rows have been processed.
>>>
>>> Fixes: d7d417fcddf9 ("Allow subclasses of Idl to define a notification hook")
>>> Signed-off-by: Terry Wilson <twilson at redhat.com>
>>> ---
>>>    python/ovs/db/idl.py | 41 ++++++++++++++++++++++++-----------------
>>>    tests/ovsdb-idl.at   | 22 ++++++++++++++++++++++
>>>    tests/test-ovsdb.py  |  7 +++++--
>>>    3 files changed, 51 insertions(+), 19 deletions(-)
>>>
>>> diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
>>> index 5850ac7ab..4b441bd85 100644
>>> --- a/python/ovs/db/idl.py
>>> +++ b/python/ovs/db/idl.py
>> <snip>
>>>        def __process_update2(self, table, uuid, row_update):
>>> +        """Returns Notice if a column changed, False otherwise."""
>>>            row = table.rows.get(uuid)
>>> -        changed = False
>>>            if "delete" in row_update:
>>>                if row:
>>>                    del table.rows[uuid]
>>> -                self.notify(ROW_DELETE, row)
>>> -                changed = True
>>> +                return Notice(ROW_DELETE, row)
>>>                else:
>>>                    # XXX rate-limit
>>>                    vlog.warn("cannot delete missing row %s from table"
>>> @@ -680,30 +691,27 @@ class Idl(object):
>>>                self.__add_default(table, row_update)
>>>                changed = self.__row_update(table, row, row_update)
>>>                table.rows[uuid] = row
>>> -            if changed:
>>> -                self.notify(ROW_CREATE, row)
>>> +            return Notice(ROW_CREATE, row) if changed else False
>>
>> I think this should still be under the 'if changed' since it's based on
>> the return value of the self.__row_update() call.  See below.

If I had read to the end of the line I would have noticed you didn't 
change the behavior, I think my eyes were drawn to the difference of 
this change and the one below, which falls-through to the 'return 
False'.  So my comment has morphed into I think we should keep the flow 
the same in both, however you decide to do it, if possible.

-Brian


>>>            elif "modify" in row_update:
>>>                if not row:
>>>                    raise error.Error('Modify non-existing row')
>>>
>>>                old_row = self.__apply_diff(table, row, row_update['modify'])
>>> -            self.notify(ROW_UPDATE, row, Row(self, table, uuid, old_row))
>>> -            changed = True
>>> +            return Notice(ROW_UPDATE, row, Row(self, table, uuid, old_row))
>>>            else:
>>>                raise error.Error('<row-update> unknown operation',
>>>                                  row_update)
>>> -        return changed
>>> +        return False
>>>
>>>        def __process_update(self, table, uuid, old, new):
>>> -        """Returns True if a column changed, False otherwise."""
>>> +        """Returns Notice if a column changed, False otherwise."""
>>>            row = table.rows.get(uuid)
>>>            changed = False
>>>            if not new:
>>>                # Delete row.
>>>                if row:
>>>                    del table.rows[uuid]
>>> -                changed = True
>>> -                self.notify(ROW_DELETE, row)
>>> +                return Notice(ROW_DELETE, row)
>>>                else:
>>>                    # XXX rate-limit
>>>                    vlog.warn("cannot delete missing row %s from table %s"
>>> @@ -722,8 +730,7 @@ class Idl(object):
>>>                changed |= self.__row_update(table, row, new)
>>>                if op == ROW_CREATE:
>>>                    table.rows[uuid] = row
>>> -            if changed:
>>> -                self.notify(ROW_CREATE, row)
>>> +            return Notice(ROW_CREATE, row) if changed else False
>>>            else:
>>>                op = ROW_UPDATE
>>>                if not row:
>>> @@ -737,8 +744,8 @@ class Idl(object):
>>>                if op == ROW_CREATE:
>>>                    table.rows[uuid] = row
>>>                if changed:
>>> -                self.notify(op, row, Row.from_json(self, table, uuid, old))
>>> -        return changed
>>> +                return Notice(op, row, Row.from_json(self, table, uuid, old))
>>
>> This is a similar call as above, but the return stayed under the 'if
>> changed' block.
>>
>> -Brian
> 
> If I'm reading your comment right, the one above it is also "under an
> if changed block", it's just in-line with return Notice(ROW_CREATE,
> row) if changed else False so that it returns one way or another there
> instead of falling through. I changed all of the fall-throughs to the
> final "return changed" because changed was boolean and for the True
> case, we're now returning a Notice and the '|=' operations don't work
> with the different types and there was no common code outside the
> if/else trees except that return.
> 


More information about the dev mailing list