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