It can be useful to be able to send raw transaction operations through the Idl's connection. For example, to clean up MAC_Binding entries for floating IPs without having to monitor the MAC_Binding table which can be quite large.
Signed-off-by: Terry Wilson <twil...@redhat.com> --- NEWS | 2 ++ python/ovs/db/idl.py | 18 ++++++++++++++- tests/ovsdb-idl.at | 27 ++++++++++++++++++++++ tests/test-ovsdb.py | 55 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+), 1 deletion(-) diff --git a/NEWS b/NEWS index b92cec532..f30b859fd 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,8 @@ Post-v3.3.0 - The primary development branch has been renamed from 'master' to 'main'. The OVS tree remains hosted on GitHub. https://github.com/openvswitch/ovs.git + - Python: + * Add custom transaction support to the Idl via add_op(). v3.3.0 - 16 Feb 2024 diff --git a/python/ovs/db/idl.py b/python/ovs/db/idl.py index a80da84e7..1220e4cc6 100644 --- a/python/ovs/db/idl.py +++ b/python/ovs/db/idl.py @@ -1703,6 +1703,8 @@ class Transaction(object): self._inserted_rows = {} # Map from UUID to _InsertedRow + self._operations = [] + def add_comment(self, comment): """Appends 'comment' to the comments that will be passed to the OVSDB server when this transaction is committed. (The comment will be @@ -1838,7 +1840,7 @@ class Transaction(object): "rows": [rows]}) # Add updates. - any_updates = False + any_updates = bool(self._operations) for row in self._txn_rows.values(): if row._changes is None: if row._table.is_root: @@ -1977,6 +1979,7 @@ class Transaction(object): if self.dry_run: operations.append({"op": "abort"}) + operations += self._operations if not any_updates: self._status = Transaction.UNCHANGED else: @@ -1991,6 +1994,19 @@ class Transaction(object): self.__disassemble() return self._status + def add_op(self, op): + """Add a raw OVSDB operation to the transaction + + This can be useful for re-using the existing Idl connection to take + actions that are difficult or expensive to do with the Idl itself. e.g. + deleting a bunch of rows on the server that you don't want to store + in memory. + + :param op: An "op" for an OVSDB "transact" request (rfc 7047 Sec 5.2) + :type op: dict + """ + self._operations.append(op) + def commit_block(self): """Attempts to commit this transaction, blocking until the commit either succeeds or fails. Returns the final commit status, which may diff --git a/tests/ovsdb-idl.at b/tests/ovsdb-idl.at index fb568dd82..487545a13 100644 --- a/tests/ovsdb-idl.at +++ b/tests/ovsdb-idl.at @@ -2758,6 +2758,33 @@ OVSDB_CHECK_IDL_PERS_UUID_INSERT([simple idl, persistent uuid insert], [['This UUID would duplicate a UUID already present within the table or deleted within the same transaction']]) +OVSDB_CHECK_IDL_PY([simple idl, python, add_op], + [], + [['insert 1, insert 2, insert 3, insert 1' \ + 'add_op {"op": "delete", "table": "simple", "where": [["i", "==", 1]]}' \ + 'add_op {"op": "insert", "table": "simple", "row": {"i": 2}}, delete 3' \ + 'insert 2, add_op {"op": "update", "table": "simple", "row": {"i": 1}, "where": [["i", "==", 2]]}' + ]], + [[000: empty +001: commit, status=success +002: table simple: i=1 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<1> +002: table simple: i=1 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<2> +002: table simple: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<3> +002: table simple: i=3 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<4> +003: commit, status=success +004: table simple: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<3> +004: table simple: i=3 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<4> +005: commit, status=success +006: table simple: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<3> +006: table simple: i=2 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<5> +007: commit, status=success +008: table simple: i=1 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<3> +008: table simple: i=1 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<5> +008: table simple: i=1 r=0 b=false s= u=<0> ia=[] ra=[] ba=[] sa=[] ua=[] uuid=<6> +009: done +]],[],sort) + + m4_define([OVSDB_CHECK_IDL_CHANGE_AWARE], [AT_SETUP([simple idl, database change aware, online conversion - $1]) AT_KEYWORDS([ovsdb server idl db_change_aware conversion $1]) diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py index 48f8ee2d7..58829f520 100644 --- a/tests/test-ovsdb.py +++ b/tests/test-ovsdb.py @@ -36,6 +36,53 @@ vlog.set_levels_from_string("console:dbg") vlog.init(None) +OBJ_STR = "_OBJECT_{}_" + + +def substitute_object_text(data, quotechar='"', obj_chars=("{}", "[]")): + obj_chars = dict(obj_chars) + in_quote = False + in_object = [] + removed_text = [] + output = "" + start = end = 0 + for i, s in enumerate(data): + if not in_object: + if not in_quote and s in obj_chars: + in_object.append(s) + start = i + else: + output += s + if s == quotechar: + in_quote = not in_quote + elif not in_quote: # unquoted object + if s == in_object[0]: + in_object.append(s) + elif s == obj_chars[in_object[0]]: + in_object.pop() + if not in_object: + end = i + 1 + removed_text.append(data[start:end]) + output += OBJ_STR.format(len(removed_text) - 1) + return output, removed_text + + +def recover_object_text_from_list(words, json): + if not json: + return words + results = [] + i = 0 + for word in words: + while json: + if OBJ_STR.format(i) not in word: + results.append(word) + break + replacement = json.pop(0) + results.append(word.replace(OBJ_STR.format(i), replacement)) + i += 1 + return results + + def unbox_json(json): if type(json) is list and len(json) == 1: return json[0] @@ -377,8 +424,10 @@ def idl_set(idl, commands, step): increment = False fetch_cmds = [] events = [] + commands, data = substitute_object_text(commands) for command in commands.split(','): words = command.split() + words = recover_object_text_from_list(words, data) name = words[0] args = words[1:] @@ -437,6 +486,12 @@ def idl_set(idl, commands, step): s = txn.insert(idl.tables["simple"], new_uuid=args[0], persist_uuid=True) s.i = int(args[1]) + elif name == "add_op": + if len(args) != 1: + sys.stderr.write('"add_op" command requires 1 argument\n') + sys.stderr.write(f"args={args}\n") + sys.exit(1) + txn.add_op(ovs.json.from_string(args[0])) elif name == "delete": if len(args) != 1: sys.stderr.write('"delete" command requires 1 argument\n') -- 2.34.3 _______________________________________________ dev mailing list d...@openvswitch.org https://mail.openvswitch.org/mailman/listinfo/ovs-dev