Implement initial Python bindings for Open vSwitch database.
[sliver-openvswitch.git] / tests / test-ovsdb.py
diff --git a/tests/test-ovsdb.py b/tests/test-ovsdb.py
new file mode 100644 (file)
index 0000000..863bcb8
--- /dev/null
@@ -0,0 +1,372 @@
+# Copyright (c) 2009, 2010 Nicira Networks
+#
+# 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 codecs
+import getopt
+import re
+import os
+import signal
+import sys
+
+from ovs.db import error
+import ovs.db.idl
+import ovs.db.schema
+from ovs.db import data
+from ovs.db import types
+import ovs.ovsuuid
+import ovs.poller
+import ovs.util
+
+def unbox_json(json):
+    if type(json) == list and len(json) == 1:
+        return json[0]
+    else:
+        return json
+
+def do_default_atoms():
+    for type in types.ATOMIC_TYPES:
+        if type == types.VoidType:
+            continue
+
+        sys.stdout.write("%s: " % type.to_string())
+
+        atom = data.Atom.default(type)
+        if atom != data.Atom.default(type):
+            sys.stdout.write("wrong\n")
+            sys.exit(1)
+
+        sys.stdout.write("OK\n")
+
+def do_default_data():
+    any_errors = False
+    for n_min in 0, 1:
+        for key in types.ATOMIC_TYPES:
+            if key == types.VoidType:
+                continue
+            for value in types.ATOMIC_TYPES:
+                if value == types.VoidType:
+                    valueBase = None
+                else:
+                    valueBase = types.BaseType(value)
+                type = types.Type(types.BaseType(key), valueBase, n_min, 1)
+                assert type.is_valid()
+
+                sys.stdout.write("key %s, value %s, n_min %d: "
+                                 % (key.to_string(), value.to_string(), n_min))
+
+                datum = data.Datum.default(type)
+                if datum != data.Datum.default(type):
+                    sys.stdout.write("wrong\n")
+                    any_errors = True
+                else:
+                    sys.stdout.write("OK\n")
+    if any_errors:
+        sys.exit(1)
+
+def do_parse_atomic_type(type_string):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    atomic_type = types.AtomicType.from_json(type_json)
+    print ovs.json.to_string(atomic_type.to_json(), sort_keys=True)
+
+def do_parse_base_type(type_string):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    base_type = types.BaseType.from_json(type_json)
+    print ovs.json.to_string(base_type.to_json(), sort_keys=True)
+
+def do_parse_type(type_string):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    type = types.Type.from_json(type_json)
+    print ovs.json.to_string(type.to_json(), sort_keys=True)
+
+def do_parse_atoms(type_string, *atom_strings):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    base = types.BaseType.from_json(type_json)
+    for atom_string in atom_strings:
+        atom_json = unbox_json(ovs.json.from_string(atom_string))
+        try:
+            atom = data.Atom.from_json(base, atom_json)
+            print ovs.json.to_string(atom.to_json())
+        except error.Error, e:
+            print e
+
+def do_parse_data(type_string, *data_strings):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    type = types.Type.from_json(type_json)
+    for datum_string in data_strings:
+        datum_json = unbox_json(ovs.json.from_string(datum_string))
+        datum = data.Datum.from_json(type, datum_json)
+        print ovs.json.to_string(datum.to_json())
+
+def do_sort_atoms(type_string, atom_strings):
+    type_json = unbox_json(ovs.json.from_string(type_string))
+    base = types.BaseType.from_json(type_json)
+    atoms = [data.Atom.from_json(base, atom_json)
+             for atom_json in unbox_json(ovs.json.from_string(atom_strings))]
+    print ovs.json.to_string([data.Atom.to_json(atom)
+                              for atom in sorted(atoms)])
+
+def do_parse_column(name, column_string):
+    column_json = unbox_json(ovs.json.from_string(column_string))
+    column = ovs.db.schema.ColumnSchema.from_json(column_json, name)
+    print ovs.json.to_string(column.to_json(), sort_keys=True)
+
+def do_parse_table(name, table_string):
+    table_json = unbox_json(ovs.json.from_string(table_string))
+    table = ovs.db.schema.TableSchema.from_json(table_json, name)
+    print ovs.json.to_string(table.to_json(), sort_keys=True)
+
+def do_parse_rows(table_string, *rows):
+    table_json = unbox_json(ovs.json.from_string(table_string))
+    table = ovs.db.schema.TableSchema.from_json(table_json, name)
+
+def do_parse_schema(schema_string):
+    schema_json = unbox_json(ovs.json.from_string(schema_string))
+    schema = ovs.db.schema.DbSchema.from_json(schema_json)
+    print ovs.json.to_string(schema.to_json(), sort_keys=True)
+
+def print_idl(idl, step):
+    n = 0
+    for uuid, row in idl.data["simple"].iteritems():
+        s = ("%03d: i=%s r=%s b=%s s=%s u=%s "
+             "ia=%s ra=%s ba=%s sa=%s ua=%s uuid=%s"
+             % (step, row.i, row.r, row.b, row.s, row.u,
+                row.ia, row.ra, row.ba, row.sa, row.ua, uuid))
+        print(re.sub('""|,', "", s))
+        n += 1
+    if not n:
+        print("%03d: empty" % step)
+
+def substitute_uuids(json, symtab):
+    if type(json) in [str, unicode]:
+        symbol = symtab.get(json)
+        if symbol:
+            return str(symbol)
+    elif type(json) == list:
+        return [substitute_uuids(element, symtab) for element in json]
+    elif type(json) == dict:
+        d = {}
+        for key, value in json.iteritems():
+            d[key] = substitute_uuids(value, symtab)
+        return d
+    return json
+
+def parse_uuids(json, symtab):
+    if type(json) in [str, unicode] and ovs.ovsuuid.UUID.is_valid_string(json):
+        name = "#%d#" % len(symtab)
+        sys.stderr.write("%s = %s\n" % (name, json))
+        symtab[name] = json
+    elif type(json) == list:
+        for element in json:
+            parse_uuids(element, symtab)
+    elif type(json) == dict:
+        for value in json.itervalues():
+            parse_uuids(value, symtab)
+
+def do_idl(remote, *commands):
+    idl = ovs.db.idl.Idl(remote, "idltest")
+
+    if commands:
+        error, stream = ovs.stream.Stream.open_block(
+            ovs.stream.Stream.open(remote))
+        if error:
+            sys.stderr.write("failed to connect to \"%s\"" % remote)
+            sys.exit(1)
+        rpc = ovs.jsonrpc.Connection(stream)
+    else:
+        rpc = None
+
+    symtab = {}
+    seqno = 0
+    step = 0
+    for command in commands:
+        if command.startswith("+"):
+            # The previous transaction didn't change anything.
+            command = command[1:]
+        else:
+            # Wait for update.
+            while idl.get_seqno() == seqno and not idl.run():
+                rpc.run()
+
+                poller = ovs.poller.Poller()
+                idl.wait(poller)
+                rpc.wait(poller)
+                poller.block()
+                
+            print_idl(idl, step)
+            step += 1
+
+        seqno = idl.get_seqno()
+
+        if command == "reconnect":
+            print("%03d: reconnect" % step)
+            step += 1
+            idl.force_reconnect()
+        elif not command.startswith("["):
+            idl_set(idl, command, step)
+            step += 1
+        else:
+            json = ovs.json.from_string(command)
+            if type(json) in [str, unicode]:
+                sys.stderr.write("\"%s\": %s\n" % (command, json))
+                sys.exit(1)
+            json = substitute_uuids(json, symtab)
+            request = ovs.jsonrpc.Message.create_request("transact", json)
+            error, reply = rpc.transact_block(request)
+            if error:
+                sys.stderr.write("jsonrpc transaction failed: %s"
+                                 % os.strerror(error))
+                sys.exit(1)
+            sys.stdout.write("%03d: " % step)
+            sys.stdout.flush()
+            step += 1
+            if reply.result is not None:
+                parse_uuids(reply.result, symtab)
+            reply.id = None
+            sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
+
+    if rpc:
+        rpc.close()
+    while idl.get_seqno() == seqno and not idl.run():
+        poller = ovs.poller.Poller()
+        idl.wait(poller)
+        poller.block()
+    print_idl(idl, step)
+    step += 1
+    idl.close()
+    print("%03d: done" % step)
+
+def usage():
+    print """\
+%(program_name)s: test utility for Open vSwitch database Python bindings
+usage: %(program_name)s [OPTIONS] COMMAND ARG...
+
+The following commands are supported:
+default-atoms
+  test ovsdb_atom_default()
+default-data
+  test ovsdb_datum_default()
+parse-atomic-type TYPE
+  parse TYPE as OVSDB atomic type, and re-serialize
+parse-base-type TYPE
+  parse TYPE as OVSDB base type, and re-serialize
+parse-type JSON
+  parse JSON as OVSDB type, and re-serialize
+parse-atoms TYPE ATOM...
+  parse JSON ATOMs as atoms of TYPE, and re-serialize
+parse-atom-strings TYPE ATOM...
+  parse string ATOMs as atoms of given TYPE, and re-serialize
+sort-atoms TYPE ATOM...
+  print JSON ATOMs in sorted order
+parse-data TYPE DATUM...
+  parse JSON DATUMs as data of given TYPE, and re-serialize
+parse-column NAME OBJECT
+  parse column NAME with info OBJECT, and re-serialize
+parse-table NAME OBJECT
+  parse table NAME with info OBJECT
+parse-schema JSON
+  parse JSON as an OVSDB schema, and re-serialize
+idl SERVER [TRANSACTION...]
+  connect to SERVER and dump the contents of the database
+  as seen initially by the IDL implementation and after
+  executing each TRANSACTION.  (Each TRANSACTION must modify
+  the database or this command will hang.)
+
+The following options are also available:
+  -t, --timeout=SECS          give up after SECS seconds
+  -h, --help                  display this help message\
+""" % {'program_name': ovs.util.PROGRAM_NAME}
+    sys.exit(0)
+
+def main(argv):
+    # Make stdout and stderr UTF-8, even if they are redirected to a file.
+    sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
+    sys.stderr = codecs.getwriter("utf-8")(sys.stderr)
+
+    try:
+        options, args = getopt.gnu_getopt(argv[1:], 't:h',
+                                          ['timeout',
+                                           'help'])
+    except getopt.GetoptError, geo:
+        sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
+        sys.exit(1)
+
+    for key, value in options:
+        if key in ['-h', '--help']:
+            usage()
+        elif key in ['-t', '--timeout']:
+            try:
+                timeout = int(value)
+                if timeout < 1:
+                    raise TypeError
+            except TypeError:
+                raise error.Error("value %s on -t or --timeout is not at "
+                                  "least 1" % value)
+            signal.alarm(timeout)
+        else:
+            sys.exit(0)
+
+    optKeys = [key for key, value in options]
+
+    if not args:
+        sys.stderr.write("%s: missing command argument "
+                         "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
+        sys.exit(1)
+
+    commands = {"default-atoms": (do_default_atoms, 0),
+                "default-data": (do_default_data, 0),
+                "parse-atomic-type": (do_parse_atomic_type, 1),
+                "parse-base-type": (do_parse_base_type, 1),
+                "parse-type": (do_parse_type, 1),
+                "parse-atoms": (do_parse_atoms, (2,)),
+                "parse-data": (do_parse_data, (2,)),
+                "sort-atoms": (do_sort_atoms, 2),
+                "parse-column": (do_parse_column, 2),
+                "parse-table": (do_parse_table, 2),
+                "parse-schema": (do_parse_schema, 1),
+                "idl": (do_idl, (1,))}
+
+    command_name = args[0]
+    args = args[1:]
+    if not command_name in commands:
+        sys.stderr.write("%s: unknown command \"%s\" "
+                         "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
+                                                      command_name))
+        sys.exit(1)
+
+    func, n_args = commands[command_name]
+    if type(n_args) == tuple:
+        if len(args) < n_args[0]:
+            sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
+                             "only %d provided\n"
+                             % (ovs.util.PROGRAM_NAME, command_name,
+                                n_args, len(args)))
+            sys.exit(1)
+    elif type(n_args) == int:
+        if len(args) != n_args:
+            sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
+                             "provided\n"
+                             % (ovs.util.PROGRAM_NAME, command_name,
+                                n_args, len(args)))
+            sys.exit(1)
+    else:
+        assert False
+
+    func(*args)
+
+if __name__ == '__main__':
+    try:
+        main(sys.argv)
+    except error.Error, e:
+        sys.stderr.write("%s\n" % e)
+        sys.exit(1)