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