summaryrefslogtreecommitdiff
path: root/examples
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2020-11-03 13:47:00 +0100
committerThomas Haller <thaller@redhat.com>2020-11-09 17:53:15 +0100
commit7e39e23f64184ddd12a7bb4d34b4d4ae616c059d (patch)
treef3da4bf63d2d6b1fd01323443e5ad58a2c6b7af9 /examples
parent6100b52e5ccc80b9c11e35907c0537d930f50da6 (diff)
downloadNetworkManager-7e39e23f64184ddd12a7bb4d34b4d4ae616c059d.tar.gz
examples: add "ovs-external-ids.py" example script
And example script for getting and setting OVS external-ids. Since currently there is no nmcli support for these properties yet, the script becomes more interesting. This "example" is rather long, and it showcases less the usage of libnm (which is rather trivial, with respect to configuring NMSettingOvsExternalIDs). Instead, it aims to provide a useful command line tool for debugging. Hence, it's mostly concerned with an elaborate command line syntax and useful print output.
Diffstat (limited to 'examples')
-rwxr-xr-xexamples/python/gi/ovs-external-ids.py425
1 files changed, 425 insertions, 0 deletions
diff --git a/examples/python/gi/ovs-external-ids.py b/examples/python/gi/ovs-external-ids.py
new file mode 100755
index 0000000000..e80da51120
--- /dev/null
+++ b/examples/python/gi/ovs-external-ids.py
@@ -0,0 +1,425 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: GPL-2.0+
+#
+# Copyright (C) 2017, 2020 Red Hat, Inc.
+#
+
+#
+# set and show OVS external-ids for a connection:
+#
+
+import sys
+import os
+import re
+import pprint
+
+import gi
+
+gi.require_version("NM", "1.0")
+from gi.repository import GLib, NM
+
+MODE_GET = "get"
+MODE_SET = "set"
+
+
+def pr(v):
+ pprint.pprint(v, indent=4, depth=5, width=60)
+
+
+HAS_LIBNM_DEBUG = os.getenv("LIBNM_CLIENT_DEBUG") is not None
+
+
+def _print(msg=""):
+ if HAS_LIBNM_DEBUG:
+ # we want to use the same logging mechanism as libnm's debug
+ # logging with "LIBNM_CLIENT_DEBUG=trace,stdout".
+ NM.utils_print(0, msg + "\n")
+ return
+ print(msg)
+
+
+def mainloop_run(timeout_msec=0, mainloop=None):
+ if mainloop is None:
+ mainloop = GLib.MainLoop()
+
+ timeout_id = None
+ timeout_reached = []
+
+ if timeout_msec > 0:
+
+ def _timeout_cb(unused):
+ # it can happen that the caller already quit the mainloop
+ # otherwise. In that case, we don't want to signal a timeout.
+ if mainloop.is_running():
+ timeout_reached.append(1)
+ mainloop.quit()
+ return True
+
+ timeout_id = GLib.timeout_add(timeout_msec, _timeout_cb, None)
+
+ mainloop.run()
+ if timeout_id:
+ GLib.source_remove(timeout_id)
+ return not timeout_reached
+
+
+def usage():
+ _print("%s g[et] PROFILE [ GETTER ]" % (sys.argv[0]))
+ _print("%s s[et] [--test] PROFILE SETTER" % (sys.argv[0]))
+ _print(
+ " PROFILE := [id | uuid | type] STRING | [ ~id | ~type ] REGEX_STRING | STRING"
+ )
+ _print(" GETTER := ( KEY | ~REGEX_KEY ) [... GETTER]")
+ _print(" SETTER := ( + | - | -KEY | [+]KEY VALUE ) [... SETTER]")
+
+
+def die(msg, show_usage=False):
+ _print("FAILED: %s" % (msg))
+ if show_usage:
+ usage()
+ sys.exit(1)
+
+
+def die_usage(msg):
+ die(msg, show_usage=True)
+
+
+def parse_args(argv):
+ had_dash_dash = False
+ args = {
+ "mode": MODE_GET,
+ "profile_arg": None,
+ "ids_arg": [],
+ "do_test": False,
+ }
+ i = 1
+ while i < len(argv):
+ a = argv[i]
+
+ if i == 1:
+ if a in ["s", "set"]:
+ args["mode"] = MODE_SET
+ elif a in ["g", "get"]:
+ args["mode"] = MODE_GET
+ else:
+ die_usage("unexpected mode argument '%s'" % (a))
+ i += 1
+ continue
+
+ if a == "--test":
+ args["do_test"] = True
+ i += 1
+ continue
+
+ if args["profile_arg"] is None:
+ if a in ["id", "~id", "uuid", "type", "~type"]:
+ if i + 1 >= len(argv):
+ die_usage("'%s' requires an argument'" % (a))
+ args["profile_arg"] = (a, argv[i + 1])
+ i += 2
+ continue
+
+ if a == "*":
+ a = None
+ args["profile_arg"] = ("*", a)
+ i += 1
+ continue
+
+ if args["mode"] == MODE_GET:
+ args["ids_arg"].append(a)
+ i += 1
+ continue
+
+ if not a:
+ die_usage("argument should specify a external-id but is empty string")
+
+ if a[0] == "-":
+ v = (a, None)
+ i += 1
+ elif a == "+":
+ v = (a, None)
+ i += 1
+ else:
+ if a[0] != "+":
+ a = "+" + a
+ if i + 1 >= len(argv):
+ die_usage("'%s' requires an argument'" % (a))
+ v = (a, argv[i + 1])
+ i += 2
+
+ args["ids_arg"].append(v)
+
+ if args["mode"] == MODE_SET:
+ if not args["ids_arg"]:
+ die_usage("Requires one or more external-ids to set or delete")
+
+ return args
+
+
+def connection_to_str(connection, show_type=False):
+ if show_type:
+ return "%s (%s, %s)" % (
+ connection.get_id(),
+ connection.get_uuid(),
+ connection.get_connection_type(),
+ )
+ return "%s (%s)" % (connection.get_id(), connection.get_uuid())
+
+
+def connections_filter(connections, profile_arg):
+ connections = list(sorted(connections, key=connection_to_str))
+ if not profile_arg:
+ return connections
+ # we preserve the order of the selected connections. And
+ # if connections are selected multiple times, we return
+ # them multiple times.
+ l = []
+ f = profile_arg
+ for c in connections:
+ if f[0] == "id":
+ if f[1] == c.get_id():
+ l.append(c)
+ elif f[0] == "~id":
+ if re.match(f[1], c.get_id()):
+ l.append(c)
+ elif f[0] == "uuid":
+ if f[1] == c.get_uuid():
+ l.append(c)
+ elif f[0] == "type":
+ if f[1] == c.get_connection_type():
+ l.append(c)
+ elif f[0] == "~type":
+ if re.match(f[1], c.get_connection_type()):
+ l.append(c)
+ else:
+ assert f[0] == "*"
+ if f[1] is None:
+ l.append(c)
+ else:
+ if f[1] in [c.get_uuid(), c.get_id()]:
+ l.append(c)
+ return l
+
+
+def ids_select(ids, mode, ids_arg):
+ ids = list(ids)
+ if not ids_arg:
+ return (ids, [])
+
+ keys = set()
+ requested = []
+ for d in ids_arg:
+ if mode == MODE_GET:
+ if d[0] == "~":
+ r = re.compile(d[1:])
+ keys.update([k for k in ids if r.match(k)])
+ else:
+ keys.update([k for k in ids if k == d])
+ if d not in requested:
+ requested.append(d)
+ else:
+ d2 = d[0]
+ assert d2[0] in ["-", "+"]
+ d3 = d2[1:]
+ if d3 in ids:
+ keys.add(d3)
+ return (list([k for k in ids if k in keys]), requested)
+
+
+def connection_print(connection, mode, ids_arg, dbus_path, prefix=""):
+ sett = connection.get_setting(NM.SettingOvsExternalIDs)
+
+ if sett is not None:
+ all_ids = list(sett.get_data_keys())
+ keys, requested = ids_select(all_ids, mode, ids_arg)
+ num_str = "%s" % (len(all_ids))
+ else:
+ keys = []
+ requested = []
+ num_str = "none"
+
+ _print(
+ "%s%s [%s]" % (prefix, connection_to_str(connection, show_type=True), num_str)
+ )
+ _print("%s %s" % (prefix, dbus_path))
+ if sett is not None:
+ dd = sett.get_property(NM.SETTING_OVS_EXTERNAL_IDS_DATA)
+ else:
+ dd = {}
+ for k in keys:
+ v = sett.get_data(k)
+ assert v is not None
+ assert v == dd.get(k, None)
+ _print('%s "%s" = "%s"' % (prefix, k, v))
+ for k in requested:
+ _print('%s "%s" = <unset>' % (prefix, k))
+
+
+def do_get(connections, ids_arg):
+ first_line = True
+ for c in connections:
+ if first_line:
+ first_line = False
+ else:
+ _print()
+ connection_print(c, MODE_GET, ids_arg, dbus_path=c.get_path())
+
+
+def do_set(nmc, connection, ids_arg, do_test):
+
+ remote_connection = connection
+ connection = NM.SimpleConnection.new_clone(remote_connection)
+
+ connection_print(
+ connection, MODE_SET, [], remote_connection.get_path(), prefix="BEFORE: "
+ )
+ _print()
+
+ sett = connection.get_setting(NM.SettingOvsExternalIDs)
+
+ for d in ids_arg:
+ op = d[0][0]
+ key = d[0][1:]
+ val = d[1]
+
+ oldval = None
+ if sett is not None:
+ oldval = sett.get_data(key)
+
+ if op == "-":
+ assert val is None
+ if key == "":
+ if sett is None:
+ _print(" DEL: setting (ovs-external-ids group was not present)")
+ else:
+ connection.remove_setting(NM.SettingOvsExternalIDs)
+ sett = None
+ _print(" DEL: setting")
+ continue
+
+ if sett is None:
+ _print(' DEL: "%s" (ovs-external-ids group was not present)' % (key))
+ continue
+ if oldval is None:
+ _print(' DEL: "%s" (id was unset)' % (key))
+ continue
+ _print(' DEL: "%s" (id was set to"%s")' % (key, oldval))
+ sett.set_data(key, None)
+ continue
+
+ if key == "":
+ assert val is None
+ if sett is None:
+ sett = NM.SettingOvsExternalIDs.new()
+ connection.add_setting(sett)
+ _print(" SET: setting (external-ids group was added)")
+ continue
+
+ _print(" SET: setting (external-ids group was present)")
+ continue
+
+ assert val is not None
+
+ if sett is None:
+ sett = NM.SettingOvsExternalIDs.new()
+ connection.add_setting(sett)
+ _print(
+ ' SET: "%s" = "%s" (external-ids group was not present)' % (key, val)
+ )
+ elif oldval is None:
+ _print(' SET: "%s" = "%s" (new)' % (key, val))
+ elif oldval != val:
+ _print(' SET: "%s" = "%s" (was "%s")' % (key, val, oldval))
+ else:
+ _print(' SET: "%s" = "%s" (unchanged)' % (key, val))
+ sett.set_data(key, val)
+
+ if do_test:
+ _print()
+ _print("Only show. Run without --test to set")
+ return
+
+ mainloop = GLib.MainLoop()
+ result_error = []
+
+ def callback(c, result):
+ try:
+ c.update2_finish(result)
+ except Exception as e:
+ result_error.append(e)
+ mainloop.quit()
+
+ remote_connection.update2(
+ connection.to_dbus(NM.ConnectionSerializationFlags.ALL),
+ NM.SettingsUpdate2Flags.NO_REAPPLY,
+ None,
+ None,
+ callback,
+ )
+
+ mainloop_run(mainloop=mainloop)
+
+ if result_error:
+ _print()
+ _print("FAILURE to commit connection: %s" % (result_error[0]))
+ return
+
+ # NMClient received the completion of Update2() call. It also received
+ # a property changed signal that the profile changed, and it is about
+ # to fetch the new value. However, that value is not yet here.
+ #
+ # libnm should provide a better API for this. For example, not signal
+ # completion of update2() until the profile was refetched. Or, indicate
+ # that the settings are dirty, so we would know how long to wait.
+ #
+ # Add an ugly workaround here and wait a bit.
+ _print()
+ _print("WORKAROUND: wait for connection to change")
+ mainloop_run(timeout_msec=500)
+
+ if remote_connection is not nmc.get_object_by_path(remote_connection.get_path()):
+ _print()
+ _print(
+ "Connection %s no longer exists after commit"
+ % (remote_connection.get_path())
+ )
+ return
+
+ _print()
+ connection_print(
+ remote_connection, MODE_SET, [], remote_connection.get_path(), prefix="AFTER: "
+ )
+
+ _print()
+ if remote_connection.compare(connection, NM.SettingCompareFlags.EXACT):
+ _print("resulting connection is as expected")
+ else:
+ _print("WARNING: resulting connection is not as expected")
+
+
+###############################################################################
+
+if __name__ == "__main__":
+
+ args = parse_args(sys.argv)
+
+ nmc = NM.Client.new(None)
+
+ connections = connections_filter(nmc.get_connections(), args["profile_arg"])
+
+ if args["mode"] == MODE_SET:
+ if len(connections) != 1:
+ _print(
+ "To set the external-ids of a connection, exactly one connection must be selected via id|uuid. Instead, %s connection matched ([%s])"
+ % (
+ len(connections),
+ ", ".join([connection_to_str(c) for c in connections]),
+ )
+ )
+ die_usage("Select unique connection to set")
+ do_set(nmc, connections[0], args["ids_arg"], do_test=args["do_test"])
+ else:
+ if len(connections) < 1:
+ _print("No connection selected for printing the external ids")
+ die_usage("Select connection to get")
+ do_get(connections, args["ids_arg"])