On 4/12/22 21:52, Terry Wilson wrote:
> Before 46d44cf3b, it was technically possible to assign a monitor
> condition directly to Idl.tables[table_name].condition. If done
> before the connection was established, it would successfully apply
> the condition (where cond_change() actually would fail).
> 
> Although this wasn't meant to be supported, several OpenStack
> projects made use of this. After 46d44cf3b, .condition is no
> longer a list, but a ConditionState. Assigning a list to it breaks
> the Idl.
> 
> The Neutron and ovsdbapp projects have patches in-flight to
> use Idl.cond_change() if ConditionState exists, as it now works
> before connection as well, but here could be other users that also
> start failing when upgrading to OVS 2.17.
> 
> Instead of directly adding attributes to TableSchema/ColumnSchema,
> this adds the IdlTable/IdlColumn objects which hold Idl-specific
> data and adds a 'condition' property to TableSchema that maintains
> the old interface. Row._uuid_to_row is added to allow looking up
> ref_table rows.
> 
> Fixes: 46d44cf3b ("python: idl: Add monitor_cond_since support")
> Signed-off-by: Terry Wilson <[email protected]>
> ---

Hi Terry,

Thanks for this revision!

There's one small issue reported in CI:

 ../../python/ovs/db/idl.py:175:1: H238: old style class declaration,
use new style (inherit from `object`)

ovsrobot is complaining about this too (unfortunately automated emails
with test results are not being sent out at the moment):

https://github.com/ovsrobot/ovs/runs/5996694299?check_suite_focus=true#step:12:4197

CI passes after fixing the flake8 reported issue:
https://github.com/dceara/ovs/actions/runs/2159668320

I have one question and a minor comment below.

>  python/ovs/db/idl.py | 95 +++++++++++++++++++++++++++++++-------------
>  1 file changed, 67 insertions(+), 28 deletions(-)
> 
> diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py
> index 83a634dd0..6ae31d106 100644
> --- a/python/ovs/db/idl.py
> +++ b/python/ovs/db/idl.py
> @@ -140,6 +140,47 @@ class ConditionState(object):
>          return False
>  
>  
> +class IdlTable:
> +    def __init__(self, idl, table):
> +        assert(isinstance(table, ovs.db.schema.TableSchema))
> +        self._table = table
> +        self.need_table = False
> +        self.rows = custom_index.IndexedRows(self)
> +        self.idl = idl
> +        self._condition_state = ConditionState()
> +        self.columns = {k: IdlColumn(v) for k, v in table.columns.items()}
> +
> +    def __getattr__(self, attr):
> +        return getattr(self._table, attr)
> +
> +    @property
> +    def condition_state(self):
> +        # read-only, no setter
> +        return self._condition_state
> +
> +    @property
> +    def condition(self):
> +        return self.condition_state.latest
> +
> +    @condition.setter
> +    def condition(self, condition):
> +        assert(isinstance(condition, list))
> +        self.idl.cond_change(self.name, condition)
> +
> +    @classmethod
> +    def schema_tables(cls, idl, schema):
> +        return {k: cls(idl, v) for k, v in schema.tables.items()}
> +
> +
> +class IdlColumn:
> +    def __init__(self, column):
> +        self._column = column
> +        self.alert = True
> +

Just to make sure, a user of the IDL can still instantiate the IDL and
then selectively disable alerting for some columns, right?  The
interface won't change and something like the following will still work:

idl.tables["table-name"].columns["column-name"].alert = False

Ideally we should add a test for this.  OTOH we don't have a test for
the C implementation either so this is maybe something we should do as a
follow up after your patch is accepted.

> +    def __getattr__(self, attr):
> +        return getattr(self._column, attr)
> +
> +
>  class Idl(object):
>      """Open vSwitch Database Interface Definition Language (OVSDB IDL).
>  
> @@ -241,7 +282,7 @@ class Idl(object):
>          assert isinstance(schema_helper, SchemaHelper)
>          schema = schema_helper.get_idl_schema()
>  
> -        self.tables = schema.tables
> +        self.tables = IdlTable.schema_tables(self, schema)
>          self.readonly = schema.readonly
>          self._db = schema
>          remotes = self._parse_remotes(remote)
> @@ -282,15 +323,6 @@ class Idl(object):
>          self.cond_changed = False
>          self.cond_seqno = 0
>  
> -        for table in schema.tables.values():
> -            for column in table.columns.values():
> -                if not hasattr(column, 'alert'):
> -                    column.alert = True
> -            table.need_table = False
> -            table.rows = custom_index.IndexedRows(table)
> -            table.idl = self
> -            table.condition = ConditionState()
> -
>      def _parse_remotes(self, remote):
>          # If remote is -
>          # "tcp:10.0.0.1:6641,unix:/tmp/db.sock,t,s,tcp:10.0.0.2:6642"
> @@ -330,7 +362,7 @@ class Idl(object):
>      def ack_conditions(self):
>          """Mark all requested table conditions as acked"""
>          for table in self.tables.values():
> -            table.condition.ack()
> +            table.condition_state.ack()
>  
>      def sync_conditions(self):
>          """Synchronize condition state when the FSM is restarted
> @@ -361,10 +393,10 @@ class Idl(object):
>  
>          for table in self.tables.values():
>              if ack_all:
> -                table.condition.request()
> -                table.condition.ack()
> +                table.condition_state.request()
> +                table.condition_state.ack()
>              else:
> -                if table.condition.reset():
> +                if table.condition_state.reset():
>                      self.last_id = str(uuid.UUID(int=0))
>                      self.cond_changed = True
>  
> @@ -485,7 +517,7 @@ class Idl(object):
>                      sh.register_table(self._server_db_table)
>                      schema = sh.get_idl_schema()
>                      self._server_db = schema
> -                    self.server_tables = schema.tables
> +                    self.server_tables = IdlTable.schema_tables(self, schema)
>                      self.__send_server_monitor_request()
>                  except error.Error as e:
>                      vlog.err("%s: error receiving server schema: %s"
> @@ -591,10 +623,10 @@ class Idl(object):
>          for table in self.tables.values():
>              # Always use the most recent conditions set by the IDL client 
> when
>              # requesting monitor_cond_change
> -            if table.condition.new is not None:
> +            if table.condition_state.new is not None:
>                  change_requests[table.name] = [
> -                    {"where": table.condition.new}]
> -                table.condition.request()
> +                    {"where": table.condition_state.new}]
> +                table.condition_state.request()
>  
>          if not change_requests:
>              return
> @@ -630,19 +662,20 @@ class Idl(object):
>              cond = [False]
>  
>          # Compare the new condition to the last known condition
> -        if table.condition.latest != cond:
> -            table.condition.init(cond)
> +        if table.condition_state.latest != cond:
> +            table.condition_state.init(cond)
>              self.cond_changed = True
>  
>          # New condition will be sent out after all already requested ones
>          # are acked.
> -        if table.condition.new:
> -            any_reqs = any(t.condition.request for t in self.tables.values())
> +        if table.condition_state.new:
> +            any_reqs = any(t.condition_state.request
> +                           for t in self.tables.values())
>              return self.cond_seqno + int(any_reqs) + 1
>  
>          # Already requested conditions should be up to date at
>          # self.cond_seqno + 1 while acked conditions are already up to date
> -        return self.cond_seqno + int(bool(table.condition.requested))
> +        return self.cond_seqno + int(bool(table.condition_state.requested))
>  
>      def wait(self, poller):
>          """Arranges for poller.block() to wake up when self.run() has 
> something
> @@ -814,8 +847,8 @@ class Idl(object):
>                      columns.append(column)
>              monitor_request = {"columns": columns}
>              if method in ("monitor_cond", "monitor_cond_since") and (
> -                    not ConditionState.is_true(table.condition.acked)):
> -                monitor_request["where"] = table.condition.acked
> +                    not ConditionState.is_true(table.condition_state.acked)):
> +                monitor_request["where"] = table.condition_state.acked
>              monitor_requests[table.name] = [monitor_request]
>  
>          args = [self._db.name, str(self.uuid), monitor_requests]
> @@ -1271,6 +1304,12 @@ class Row(object):
>              data=", ".join("{col}={val}".format(col=c, val=getattr(self, c))
>                             for c in sorted(self._table.columns)))
>  
> +    def _uuid_to_row(self, atom, base):
> +        if base.ref_table:
> +            return self._idl.tables[base.ref_table.name].rows.get(atom)
> +        else:
> +            return atom
> +

I think we can safely remove the top-level _uuid_to_row() now.

>      def __getattr__(self, column_name):
>          assert self._changes is not None
>          assert self._mutations is not None
> @@ -1312,7 +1351,7 @@ class Row(object):
>                      datum = data.Datum.from_python(column.type, dlist,
>                                                     _row_to_uuid)
>                  elif column.type.is_map():
> -                    dmap = datum.to_python(_uuid_to_row)
> +                    dmap = datum.to_python(self._uuid_to_row)
>                      if inserts is not None:
>                          dmap.update(inserts)
>                      if removes is not None:
> @@ -1329,7 +1368,7 @@ class Row(object):
>                  else:
>                      datum = inserts
>  
> -        return datum.to_python(_uuid_to_row)
> +        return datum.to_python(self._uuid_to_row)
>  
>      def __setattr__(self, column_name, value):
>          assert self._changes is not None
> @@ -1413,7 +1452,7 @@ class Row(object):
>          if value:
>              try:
>                  old_value = data.Datum.to_python(self._data[column_name],
> -                                                 _uuid_to_row)
> +                                                 self._uuid_to_row)
>              except error.Error:
>                  return
>              if key not in old_value:

With the above minor comments addressed, feel free to add my ack to the
next revision:

Acked-by: Dumitru Ceara <[email protected]>

Thanks!

_______________________________________________
dev mailing list
[email protected]
https://mail.openvswitch.org/mailman/listinfo/ovs-dev

Reply via email to