python: Fix print function compatibility.
[cascardo/ovs.git] / tests / test-ovsdb.py
1 # Copyright (c) 2009, 2010, 2011, 2012 Nicira, Inc.
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 from __future__ import print_function
16
17 import getopt
18 import re
19 import os
20 import signal
21 import sys
22 import uuid
23
24 from ovs.db import error
25 import ovs.db.idl
26 import ovs.db.schema
27 from ovs.db import data
28 import ovs.db.types
29 import ovs.ovsuuid
30 import ovs.poller
31 import ovs.util
32
33
34 def unbox_json(json):
35     if type(json) == list and len(json) == 1:
36         return json[0]
37     else:
38         return json
39
40
41 def do_default_atoms():
42     for type_ in ovs.db.types.ATOMIC_TYPES:
43         if type_ == ovs.db.types.VoidType:
44             continue
45
46         sys.stdout.write("%s: " % type_.to_string())
47
48         atom = data.Atom.default(type_)
49         if atom != data.Atom.default(type_):
50             sys.stdout.write("wrong\n")
51             sys.exit(1)
52
53         sys.stdout.write("OK\n")
54
55
56 def do_default_data():
57     any_errors = False
58     for n_min in 0, 1:
59         for key in ovs.db.types.ATOMIC_TYPES:
60             if key == ovs.db.types.VoidType:
61                 continue
62             for value in ovs.db.types.ATOMIC_TYPES:
63                 if value == ovs.db.types.VoidType:
64                     valueBase = None
65                 else:
66                     valueBase = ovs.db.types.BaseType(value)
67                 type_ = ovs.db.types.Type(ovs.db.types.BaseType(key),
68                                           valueBase, n_min, 1)
69                 assert type_.is_valid()
70
71                 sys.stdout.write("key %s, value %s, n_min %d: "
72                                  % (key.to_string(), value.to_string(), n_min))
73
74                 datum = data.Datum.default(type_)
75                 if datum != data.Datum.default(type_):
76                     sys.stdout.write("wrong\n")
77                     any_errors = True
78                 else:
79                     sys.stdout.write("OK\n")
80     if any_errors:
81         sys.exit(1)
82
83
84 def do_parse_atomic_type(type_string):
85     type_json = unbox_json(ovs.json.from_string(type_string))
86     atomic_type = ovs.db.types.AtomicType.from_json(type_json)
87     print(ovs.json.to_string(atomic_type.to_json(), sort_keys=True))
88
89
90 def do_parse_base_type(type_string):
91     type_json = unbox_json(ovs.json.from_string(type_string))
92     base_type = ovs.db.types.BaseType.from_json(type_json)
93     print(ovs.json.to_string(base_type.to_json(), sort_keys=True))
94
95
96 def do_parse_type(type_string):
97     type_json = unbox_json(ovs.json.from_string(type_string))
98     type_ = ovs.db.types.Type.from_json(type_json)
99     print(ovs.json.to_string(type_.to_json(), sort_keys=True))
100
101
102 def do_parse_atoms(type_string, *atom_strings):
103     type_json = unbox_json(ovs.json.from_string(type_string))
104     base = ovs.db.types.BaseType.from_json(type_json)
105     for atom_string in atom_strings:
106         atom_json = unbox_json(ovs.json.from_string(atom_string))
107         try:
108             atom = data.Atom.from_json(base, atom_json)
109             print(ovs.json.to_string(atom.to_json()))
110         except error.Error as e:
111             print(e.args[0].encode("utf8"))
112
113
114 def do_parse_data(type_string, *data_strings):
115     type_json = unbox_json(ovs.json.from_string(type_string))
116     type_ = ovs.db.types.Type.from_json(type_json)
117     for datum_string in data_strings:
118         datum_json = unbox_json(ovs.json.from_string(datum_string))
119         datum = data.Datum.from_json(type_, datum_json)
120         print(ovs.json.to_string(datum.to_json()))
121
122
123 def do_sort_atoms(type_string, atom_strings):
124     type_json = unbox_json(ovs.json.from_string(type_string))
125     base = ovs.db.types.BaseType.from_json(type_json)
126     atoms = [data.Atom.from_json(base, atom_json)
127              for atom_json in unbox_json(ovs.json.from_string(atom_strings))]
128     print(ovs.json.to_string([data.Atom.to_json(atom)
129                               for atom in sorted(atoms)]))
130
131
132 def do_parse_column(name, column_string):
133     column_json = unbox_json(ovs.json.from_string(column_string))
134     column = ovs.db.schema.ColumnSchema.from_json(column_json, name)
135     print(ovs.json.to_string(column.to_json(), sort_keys=True))
136
137
138 def do_parse_table(name, table_string, default_is_root_string='false'):
139     default_is_root = default_is_root_string == 'true'
140     table_json = unbox_json(ovs.json.from_string(table_string))
141     table = ovs.db.schema.TableSchema.from_json(table_json, name)
142     print(ovs.json.to_string(table.to_json(default_is_root), sort_keys=True))
143
144
145 def do_parse_schema(schema_string):
146     schema_json = unbox_json(ovs.json.from_string(schema_string))
147     schema = ovs.db.schema.DbSchema.from_json(schema_json)
148     print(ovs.json.to_string(schema.to_json(), sort_keys=True))
149
150
151 def print_idl(idl, step):
152     n = 0
153     if "simple" in idl.tables:
154         simple_columns = ["i", "r", "b", "s", "u", "ia",
155                           "ra", "ba", "sa", "ua", "uuid"]
156         simple = idl.tables["simple"].rows
157         for row in simple.itervalues():
158             s = "%03d:" % step
159             for column in simple_columns:
160                 if hasattr(row, column) and not (type(getattr(row, column))
161                                                  is ovs.db.data.Atom):
162                     s += " %s=%s" % (column, getattr(row, column))
163             s = re.sub('""|,|u?\'', "", s)
164             s = re.sub('UUID\(([^)]+)\)', r'\1', s)
165             s = re.sub('False', 'false', s)
166             s = re.sub('True', 'true', s)
167             s = re.sub(r'(ba)=([^[][^ ]*) ', r'\1=[\2] ', s)
168             print(s)
169             n += 1
170
171     if "link1" in idl.tables:
172         l1 = idl.tables["link1"].rows
173         for row in l1.itervalues():
174             s = ["%03d: i=%s k=" % (step, row.i)]
175             if hasattr(row, "k") and row.k:
176                 s.append(str(row.k.i))
177             if hasattr(row, "ka"):
178                 s.append(" ka=[")
179                 s.append(' '.join(sorted(str(ka.i) for ka in row.ka)))
180                 s.append("] l2=")
181             if hasattr(row, "l2") and row.l2:
182                 s.append(str(row.l2[0].i))
183             if hasattr(row, "uuid"):
184                 s.append(" uuid=%s" % row.uuid)
185             print(''.join(s))
186             n += 1
187
188     if "link2" in idl.tables:
189         l2 = idl.tables["link2"].rows
190         for row in l2.itervalues():
191             s = ["%03d:" % step]
192             s.append(" i=%s l1=" % row.i)
193             if hasattr(row, "l1") and row.l1:
194                 s.append(str(row.l1[0].i))
195             if hasattr(row, "uuid"):
196                 s.append(" uuid=%s" % row.uuid)
197             print(''.join(s))
198             n += 1
199
200     if not n:
201         print("%03d: empty" % step)
202     sys.stdout.flush()
203
204
205 def substitute_uuids(json, symtab):
206     if type(json) in [str, unicode]:
207         symbol = symtab.get(json)
208         if symbol:
209             return str(symbol)
210     elif type(json) == list:
211         return [substitute_uuids(element, symtab) for element in json]
212     elif type(json) == dict:
213         d = {}
214         for key, value in json.iteritems():
215             d[key] = substitute_uuids(value, symtab)
216         return d
217     return json
218
219
220 def parse_uuids(json, symtab):
221     if type(json) in [str, unicode] and ovs.ovsuuid.is_valid_string(json):
222         name = "#%d#" % len(symtab)
223         sys.stderr.write("%s = %s\n" % (name, json))
224         symtab[name] = json
225     elif type(json) == list:
226         for element in json:
227             parse_uuids(element, symtab)
228     elif type(json) == dict:
229         for value in json.itervalues():
230             parse_uuids(value, symtab)
231
232
233 def idltest_find_simple(idl, i):
234     for row in idl.tables["simple"].rows.itervalues():
235         if row.i == i:
236             return row
237     return None
238
239
240 def idl_set(idl, commands, step):
241     txn = ovs.db.idl.Transaction(idl)
242     increment = False
243     fetch_cmds = []
244     events = []
245     for command in commands.split(','):
246         words = command.split()
247         name = words[0]
248         args = words[1:]
249
250         if name == "notifytest":
251             name = args[0]
252             args = args[1:]
253             old_notify = idl.notify
254
255             def notify(event, row, updates=None):
256                 if updates:
257                     upcol = updates._data.keys()[0]
258                 else:
259                     upcol = None
260                 events.append("%s|%s|%s" % (event, row.i, upcol))
261                 idl.notify = old_notify
262
263             idl.notify = notify
264
265         if name == "set":
266             if len(args) != 3:
267                 sys.stderr.write('"set" command requires 3 arguments\n')
268                 sys.exit(1)
269
270             s = idltest_find_simple(idl, int(args[0]))
271             if not s:
272                 sys.stderr.write('"set" command asks for nonexistent i=%d\n'
273                                  % int(args[0]))
274                 sys.exit(1)
275
276             if args[1] == "b":
277                 s.b = args[2] == "1"
278             elif args[1] == "s":
279                 s.s = args[2]
280             elif args[1] == "u":
281                 s.u = uuid.UUID(args[2])
282             elif args[1] == "r":
283                 s.r = float(args[2])
284             else:
285                 sys.stderr.write('"set" comamnd asks for unknown column %s\n'
286                                  % args[2])
287                 sys.stderr.exit(1)
288         elif name == "insert":
289             if len(args) != 1:
290                 sys.stderr.write('"set" command requires 1 argument\n')
291                 sys.exit(1)
292
293             s = txn.insert(idl.tables["simple"])
294             s.i = int(args[0])
295         elif name == "delete":
296             if len(args) != 1:
297                 sys.stderr.write('"delete" command requires 1 argument\n')
298                 sys.exit(1)
299
300             s = idltest_find_simple(idl, int(args[0]))
301             if not s:
302                 sys.stderr.write('"delete" command asks for nonexistent i=%d\n'
303                                  % int(args[0]))
304                 sys.exit(1)
305             s.delete()
306         elif name == "verify":
307             if len(args) != 2:
308                 sys.stderr.write('"verify" command requires 2 arguments\n')
309                 sys.exit(1)
310
311             s = idltest_find_simple(idl, int(args[0]))
312             if not s:
313                 sys.stderr.write('"verify" command asks for nonexistent i=%d\n'
314                                  % int(args[0]))
315                 sys.exit(1)
316
317             if args[1] in ("i", "b", "s", "u", "r"):
318                 s.verify(args[1])
319             else:
320                 sys.stderr.write('"verify" command asks for unknown column '
321                                  '"%s"\n' % args[1])
322                 sys.exit(1)
323         elif name == "fetch":
324             if len(args) != 2:
325                 sys.stderr.write('"fetch" command requires 2 argument\n')
326                 sys.exit(1)
327
328             row = idltest_find_simple(idl, int(args[0]))
329             if not row:
330                 sys.stderr.write('"fetch" command asks for nonexistent i=%d\n'
331                                  % int(args[0]))
332                 sys.exit(1)
333
334             column = args[1]
335             row.fetch(column)
336             fetch_cmds.append([row, column])
337         elif name == "increment":
338             if len(args) != 1:
339                 sys.stderr.write('"increment" command requires 1 argument\n')
340                 sys.exit(1)
341
342             s = idltest_find_simple(idl, int(args[0]))
343             if not s:
344                 sys.stderr.write('"set" command asks for nonexistent i=%d\n'
345                                  % int(args[0]))
346                 sys.exit(1)
347
348             s.increment("i")
349             increment = True
350         elif name == "abort":
351             txn.abort()
352             break
353         elif name == "destroy":
354             print("%03d: destroy" % step)
355             sys.stdout.flush()
356             txn.abort()
357             return
358         elif name == "linktest":
359             l1_0 = txn.insert(idl.tables["link1"])
360             l1_0.i = 1
361             l1_0.k = [l1_0]
362             l1_0.ka = [l1_0]
363             l1_1 = txn.insert(idl.tables["link1"])
364             l1_1.i = 2
365             l1_1.k = [l1_0]
366             l1_1.ka = [l1_0, l1_1]
367         elif name == 'getattrtest':
368             l1 = txn.insert(idl.tables["link1"])
369             i = getattr(l1, 'i', 1)
370             assert i == 1
371             l1.i = 2
372             i = getattr(l1, 'i', 1)
373             assert i == 2
374             l1.k = [l1]
375         else:
376             sys.stderr.write("unknown command %s\n" % name)
377             sys.exit(1)
378
379     status = txn.commit_block()
380     sys.stdout.write("%03d: commit, status=%s"
381                      % (step, ovs.db.idl.Transaction.status_to_string(status)))
382     if increment and status == ovs.db.idl.Transaction.SUCCESS:
383         sys.stdout.write(", increment=%d" % txn.get_increment_new_value())
384     if events:
385         # Event notifications from operations in a single transaction are
386         # not in a gauranteed order due to update messages being dicts
387         sys.stdout.write(", events=" + ", ".join(sorted(events)))
388     sys.stdout.write("\n")
389     sys.stdout.flush()
390
391
392 def do_idl(schema_file, remote, *commands):
393     schema_helper = ovs.db.idl.SchemaHelper(schema_file)
394     if commands and commands[0].startswith("?"):
395         readonly = {}
396         for x in commands[0][1:].split("?"):
397             readonly = []
398             table, columns = x.split(":")
399             columns = columns.split(",")
400             for index, column in enumerate(columns):
401                 if column[-1] == '!':
402                     columns[index] = columns[index][:-1]
403                     readonly.append(columns[index])
404             schema_helper.register_columns(table, columns, readonly)
405         commands = commands[1:]
406     else:
407         schema_helper.register_all()
408     idl = ovs.db.idl.Idl(remote, schema_helper)
409
410     if commands:
411         error, stream = ovs.stream.Stream.open_block(
412             ovs.stream.Stream.open(remote))
413         if error:
414             sys.stderr.write("failed to connect to \"%s\"" % remote)
415             sys.exit(1)
416         rpc = ovs.jsonrpc.Connection(stream)
417     else:
418         rpc = None
419
420     symtab = {}
421     seqno = 0
422     step = 0
423     for command in commands:
424         if command.startswith("+"):
425             # The previous transaction didn't change anything.
426             command = command[1:]
427         else:
428             # Wait for update.
429             while idl.change_seqno == seqno and not idl.run():
430                 rpc.run()
431
432                 poller = ovs.poller.Poller()
433                 idl.wait(poller)
434                 rpc.wait(poller)
435                 poller.block()
436
437             print_idl(idl, step)
438             step += 1
439
440         seqno = idl.change_seqno
441
442         if command == "reconnect":
443             print("%03d: reconnect" % step)
444             sys.stdout.flush()
445             step += 1
446             idl.force_reconnect()
447         elif not command.startswith("["):
448             idl_set(idl, command, step)
449             step += 1
450         else:
451             json = ovs.json.from_string(command)
452             if type(json) in [str, unicode]:
453                 sys.stderr.write("\"%s\": %s\n" % (command, json))
454                 sys.exit(1)
455             json = substitute_uuids(json, symtab)
456             request = ovs.jsonrpc.Message.create_request("transact", json)
457             error, reply = rpc.transact_block(request)
458             if error:
459                 sys.stderr.write("jsonrpc transaction failed: %s"
460                                  % os.strerror(error))
461                 sys.exit(1)
462             elif reply.error is not None:
463                 sys.stderr.write("jsonrpc transaction failed: %s"
464                                  % reply.error)
465                 sys.exit(1)
466
467             sys.stdout.write("%03d: " % step)
468             sys.stdout.flush()
469             step += 1
470             if reply.result is not None:
471                 parse_uuids(reply.result, symtab)
472             reply.id = None
473             sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
474             sys.stdout.flush()
475
476     if rpc:
477         rpc.close()
478     while idl.change_seqno == seqno and not idl.run():
479         poller = ovs.poller.Poller()
480         idl.wait(poller)
481         poller.block()
482     print_idl(idl, step)
483     step += 1
484     idl.close()
485     print("%03d: done" % step)
486
487
488 def usage():
489     print("""\
490 %(program_name)s: test utility for Open vSwitch database Python bindings
491 usage: %(program_name)s [OPTIONS] COMMAND ARG...
492
493 The following commands are supported:
494 default-atoms
495   test ovsdb_atom_default()
496 default-data
497   test ovsdb_datum_default()
498 parse-atomic-type TYPE
499   parse TYPE as OVSDB atomic type, and re-serialize
500 parse-base-type TYPE
501   parse TYPE as OVSDB base type, and re-serialize
502 parse-type JSON
503   parse JSON as OVSDB type, and re-serialize
504 parse-atoms TYPE ATOM...
505   parse JSON ATOMs as atoms of TYPE, and re-serialize
506 parse-atom-strings TYPE ATOM...
507   parse string ATOMs as atoms of given TYPE, and re-serialize
508 sort-atoms TYPE ATOM...
509   print JSON ATOMs in sorted order
510 parse-data TYPE DATUM...
511   parse JSON DATUMs as data of given TYPE, and re-serialize
512 parse-column NAME OBJECT
513   parse column NAME with info OBJECT, and re-serialize
514 parse-table NAME OBJECT [DEFAULT-IS-ROOT]
515   parse table NAME with info OBJECT
516 parse-schema JSON
517   parse JSON as an OVSDB schema, and re-serialize
518 idl SCHEMA SERVER [?T1:C1,C2...[?T2:C1,C2,...]...] [TRANSACTION...]
519   connect to SERVER (which has the specified SCHEMA) and dump the
520   contents of the database as seen initially by the IDL implementation
521   and after executing each TRANSACTION.  (Each TRANSACTION must modify
522   the database or this command will hang.)
523   By default, all columns of all tables are monitored. The "?" option
524   can be used to monitor specific Table:Column(s). The table and their
525   columns are listed as a string of the form starting with "?":
526       ?<table-name>:<column-name>,<column-name>,...
527   e.g.:
528       ?simple:b - Monitor column "b" in table "simple"
529   Entries for multiple tables are seperated by "?":
530       ?<table-name>:<column-name>,...?<table-name>:<column-name>,...
531   e.g.:
532       ?simple:b?link1:i,k - Monitor column "b" in table "simple",
533                             and column "i", "k" in table "link1"
534   Readonly columns: Suffixing a "!" after a column indicates that the
535   column is to be registered "readonly".
536   e.g.:
537       ?simple:i,b!  - Register interest in column "i" (monitoring) and
538                       column "b" (readonly).
539
540
541 The following options are also available:
542   -t, --timeout=SECS          give up after SECS seconds
543   -h, --help                  display this help message\
544 """ % {'program_name': ovs.util.PROGRAM_NAME})
545     sys.exit(0)
546
547
548 def main(argv):
549     try:
550         options, args = getopt.gnu_getopt(argv[1:], 't:h',
551                                           ['timeout',
552                                            'help'])
553     except getopt.GetoptError as geo:
554         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
555         sys.exit(1)
556
557     for key, value in options:
558         if key in ['-h', '--help']:
559             usage()
560         elif key in ['-t', '--timeout']:
561             try:
562                 timeout = int(value)
563                 if timeout < 1:
564                     raise TypeError
565             except TypeError:
566                 raise error.Error("value %s on -t or --timeout is not at "
567                                   "least 1" % value)
568             signal.alarm(timeout)
569         else:
570             sys.exit(0)
571
572     if not args:
573         sys.stderr.write("%s: missing command argument "
574                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
575         sys.exit(1)
576
577     commands = {"default-atoms": (do_default_atoms, 0),
578                 "default-data": (do_default_data, 0),
579                 "parse-atomic-type": (do_parse_atomic_type, 1),
580                 "parse-base-type": (do_parse_base_type, 1),
581                 "parse-type": (do_parse_type, 1),
582                 "parse-atoms": (do_parse_atoms, (2,)),
583                 "parse-data": (do_parse_data, (2,)),
584                 "sort-atoms": (do_sort_atoms, 2),
585                 "parse-column": (do_parse_column, 2),
586                 "parse-table": (do_parse_table, (2, 3)),
587                 "parse-schema": (do_parse_schema, 1),
588                 "idl": (do_idl, (2,))}
589
590     command_name = args[0]
591     args = args[1:]
592     if command_name not in commands:
593         sys.stderr.write("%s: unknown command \"%s\" "
594                          "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
595                                                       command_name))
596         sys.exit(1)
597
598     func, n_args = commands[command_name]
599     if type(n_args) == tuple:
600         if len(args) < n_args[0]:
601             sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
602                              "only %d provided\n"
603                              % (ovs.util.PROGRAM_NAME, command_name,
604                                 n_args, len(args)))
605             sys.exit(1)
606     elif type(n_args) == int:
607         if len(args) != n_args:
608             sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
609                              "provided\n"
610                              % (ovs.util.PROGRAM_NAME, command_name,
611                                 n_args, len(args)))
612             sys.exit(1)
613     else:
614         assert False
615
616     func(*args)
617
618
619 if __name__ == '__main__':
620     try:
621         main(sys.argv)
622     except error.Error as e:
623         sys.stderr.write("%s\n" % e)
624         sys.exit(1)