Allow subclasses of Idl to define a notification hook
[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                 upcol = updates._data.keys()[0] if updates else None
244                 events.append("%s|%s|%s" % (event, row.i, upcol))
245                 idl.notify = old_notify
246
247             idl.notify = notify
248
249         if name == "set":
250             if len(args) != 3:
251                 sys.stderr.write('"set" command requires 3 arguments\n')
252                 sys.exit(1)
253
254             s = idltest_find_simple(idl, int(args[0]))
255             if not s:
256                 sys.stderr.write('"set" command asks for nonexistent i=%d\n'
257                                  % int(args[0]))
258                 sys.exit(1)
259
260             if args[1] == "b":
261                 s.b = args[2] == "1"
262             elif args[1] == "s":
263                 s.s = args[2]
264             elif args[1] == "u":
265                 s.u = uuid.UUID(args[2])
266             elif args[1] == "r":
267                 s.r = float(args[2])
268             else:
269                 sys.stderr.write('"set" comamnd asks for unknown column %s\n'
270                                  % args[2])
271                 sys.stderr.exit(1)
272         elif name == "insert":
273             if len(args) != 1:
274                 sys.stderr.write('"set" command requires 1 argument\n')
275                 sys.exit(1)
276
277             s = txn.insert(idl.tables["simple"])
278             s.i = int(args[0])
279         elif name == "delete":
280             if len(args) != 1:
281                 sys.stderr.write('"delete" command requires 1 argument\n')
282                 sys.exit(1)
283
284             s = idltest_find_simple(idl, int(args[0]))
285             if not s:
286                 sys.stderr.write('"delete" command asks for nonexistent i=%d\n'
287                                  % int(args[0]))
288                 sys.exit(1)
289             s.delete()
290         elif name == "verify":
291             if len(args) != 2:
292                 sys.stderr.write('"verify" command requires 2 arguments\n')
293                 sys.exit(1)
294
295             s = idltest_find_simple(idl, int(args[0]))
296             if not s:
297                 sys.stderr.write('"verify" command asks for nonexistent i=%d\n'
298                                  % int(args[0]))
299                 sys.exit(1)
300
301             if args[1] in ("i", "b", "s", "u", "r"):
302                 s.verify(args[1])
303             else:
304                 sys.stderr.write('"verify" command asks for unknown column '
305                                  '"%s"\n' % args[1])
306                 sys.exit(1)
307         elif name == "increment":
308             if len(args) != 1:
309                 sys.stderr.write('"increment" command requires 1 argument\n')
310                 sys.exit(1)
311
312             s = idltest_find_simple(idl, int(args[0]))
313             if not s:
314                 sys.stderr.write('"set" command asks for nonexistent i=%d\n'
315                                  % int(args[0]))
316                 sys.exit(1)
317
318             s.increment("i")
319             increment = True
320         elif name == "abort":
321             txn.abort()
322             break
323         elif name == "destroy":
324             print "%03d: destroy" % step
325             sys.stdout.flush()
326             txn.abort()
327             return
328         elif name == "linktest":
329             l1_0 = txn.insert(idl.tables["link1"])
330             l1_0.i = 1
331             l1_0.k = [l1_0]
332             l1_0.ka = [l1_0]
333             l1_1 = txn.insert(idl.tables["link1"])
334             l1_1.i = 2
335             l1_1.k = [l1_0]
336             l1_1.ka = [l1_0, l1_1]
337         elif name == 'getattrtest':
338             l1 = txn.insert(idl.tables["link1"])
339             i = getattr(l1, 'i', 1)
340             assert i == 1
341             l1.i = 2
342             i = getattr(l1, 'i', 1)
343             assert i == 2
344             l1.k = [l1]
345         else:
346             sys.stderr.write("unknown command %s\n" % name)
347             sys.exit(1)
348
349     status = txn.commit_block()
350     sys.stdout.write("%03d: commit, status=%s"
351                      % (step, ovs.db.idl.Transaction.status_to_string(status)))
352     if increment and status == ovs.db.idl.Transaction.SUCCESS:
353         sys.stdout.write(", increment=%d" % txn.get_increment_new_value())
354     if events:
355         # Event notifications from operations in a single transaction are
356         # not in a gauranteed order due to update messages being dicts
357         sys.stdout.write(", events=" + ", ".join(sorted(events)))
358     sys.stdout.write("\n")
359     sys.stdout.flush()
360
361
362 def do_idl(schema_file, remote, *commands):
363     schema_helper = ovs.db.idl.SchemaHelper(schema_file)
364     schema_helper.register_all()
365     idl = ovs.db.idl.Idl(remote, schema_helper)
366
367     if commands:
368         error, stream = ovs.stream.Stream.open_block(
369             ovs.stream.Stream.open(remote))
370         if error:
371             sys.stderr.write("failed to connect to \"%s\"" % remote)
372             sys.exit(1)
373         rpc = ovs.jsonrpc.Connection(stream)
374     else:
375         rpc = None
376
377     symtab = {}
378     seqno = 0
379     step = 0
380     for command in commands:
381         if command.startswith("+"):
382             # The previous transaction didn't change anything.
383             command = command[1:]
384         else:
385             # Wait for update.
386             while idl.change_seqno == seqno and not idl.run():
387                 rpc.run()
388
389                 poller = ovs.poller.Poller()
390                 idl.wait(poller)
391                 rpc.wait(poller)
392                 poller.block()
393
394             print_idl(idl, step)
395             step += 1
396
397         seqno = idl.change_seqno
398
399         if command == "reconnect":
400             print("%03d: reconnect" % step)
401             sys.stdout.flush()
402             step += 1
403             idl.force_reconnect()
404         elif not command.startswith("["):
405             idl_set(idl, command, step)
406             step += 1
407         else:
408             json = ovs.json.from_string(command)
409             if type(json) in [str, unicode]:
410                 sys.stderr.write("\"%s\": %s\n" % (command, json))
411                 sys.exit(1)
412             json = substitute_uuids(json, symtab)
413             request = ovs.jsonrpc.Message.create_request("transact", json)
414             error, reply = rpc.transact_block(request)
415             if error:
416                 sys.stderr.write("jsonrpc transaction failed: %s"
417                                  % os.strerror(error))
418                 sys.exit(1)
419             elif reply.error is not None:
420                 sys.stderr.write("jsonrpc transaction failed: %s"
421                                  % reply.error)
422                 sys.exit(1)
423
424             sys.stdout.write("%03d: " % step)
425             sys.stdout.flush()
426             step += 1
427             if reply.result is not None:
428                 parse_uuids(reply.result, symtab)
429             reply.id = None
430             sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
431             sys.stdout.flush()
432
433     if rpc:
434         rpc.close()
435     while idl.change_seqno == seqno and not idl.run():
436         poller = ovs.poller.Poller()
437         idl.wait(poller)
438         poller.block()
439     print_idl(idl, step)
440     step += 1
441     idl.close()
442     print("%03d: done" % step)
443
444
445 def usage():
446     print """\
447 %(program_name)s: test utility for Open vSwitch database Python bindings
448 usage: %(program_name)s [OPTIONS] COMMAND ARG...
449
450 The following commands are supported:
451 default-atoms
452   test ovsdb_atom_default()
453 default-data
454   test ovsdb_datum_default()
455 parse-atomic-type TYPE
456   parse TYPE as OVSDB atomic type, and re-serialize
457 parse-base-type TYPE
458   parse TYPE as OVSDB base type, and re-serialize
459 parse-type JSON
460   parse JSON as OVSDB type, and re-serialize
461 parse-atoms TYPE ATOM...
462   parse JSON ATOMs as atoms of TYPE, and re-serialize
463 parse-atom-strings TYPE ATOM...
464   parse string ATOMs as atoms of given TYPE, and re-serialize
465 sort-atoms TYPE ATOM...
466   print JSON ATOMs in sorted order
467 parse-data TYPE DATUM...
468   parse JSON DATUMs as data of given TYPE, and re-serialize
469 parse-column NAME OBJECT
470   parse column NAME with info OBJECT, and re-serialize
471 parse-table NAME OBJECT [DEFAULT-IS-ROOT]
472   parse table NAME with info OBJECT
473 parse-schema JSON
474   parse JSON as an OVSDB schema, and re-serialize
475 idl SCHEMA SERVER [TRANSACTION...]
476   connect to SERVER (which has the specified SCHEMA) and dump the
477   contents of the database as seen initially by the IDL implementation
478   and after executing each TRANSACTION.  (Each TRANSACTION must modify
479   the database or this command will hang.)
480
481 The following options are also available:
482   -t, --timeout=SECS          give up after SECS seconds
483   -h, --help                  display this help message\
484 """ % {'program_name': ovs.util.PROGRAM_NAME}
485     sys.exit(0)
486
487
488 def main(argv):
489     try:
490         options, args = getopt.gnu_getopt(argv[1:], 't:h',
491                                           ['timeout',
492                                            'help'])
493     except getopt.GetoptError, geo:
494         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
495         sys.exit(1)
496
497     for key, value in options:
498         if key in ['-h', '--help']:
499             usage()
500         elif key in ['-t', '--timeout']:
501             try:
502                 timeout = int(value)
503                 if timeout < 1:
504                     raise TypeError
505             except TypeError:
506                 raise error.Error("value %s on -t or --timeout is not at "
507                                   "least 1" % value)
508             signal.alarm(timeout)
509         else:
510             sys.exit(0)
511
512     if not args:
513         sys.stderr.write("%s: missing command argument "
514                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
515         sys.exit(1)
516
517     commands = {"default-atoms": (do_default_atoms, 0),
518                 "default-data": (do_default_data, 0),
519                 "parse-atomic-type": (do_parse_atomic_type, 1),
520                 "parse-base-type": (do_parse_base_type, 1),
521                 "parse-type": (do_parse_type, 1),
522                 "parse-atoms": (do_parse_atoms, (2,)),
523                 "parse-data": (do_parse_data, (2,)),
524                 "sort-atoms": (do_sort_atoms, 2),
525                 "parse-column": (do_parse_column, 2),
526                 "parse-table": (do_parse_table, (2, 3)),
527                 "parse-schema": (do_parse_schema, 1),
528                 "idl": (do_idl, (2,))}
529
530     command_name = args[0]
531     args = args[1:]
532     if not command_name in commands:
533         sys.stderr.write("%s: unknown command \"%s\" "
534                          "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
535                                                       command_name))
536         sys.exit(1)
537
538     func, n_args = commands[command_name]
539     if type(n_args) == tuple:
540         if len(args) < n_args[0]:
541             sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
542                              "only %d provided\n"
543                              % (ovs.util.PROGRAM_NAME, command_name,
544                                 n_args, len(args)))
545             sys.exit(1)
546     elif type(n_args) == int:
547         if len(args) != n_args:
548             sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
549                              "provided\n"
550                              % (ovs.util.PROGRAM_NAME, command_name,
551                                 n_args, len(args)))
552             sys.exit(1)
553     else:
554         assert False
555
556     func(*args)
557
558
559 if __name__ == '__main__':
560     try:
561         main(sys.argv)
562     except error.Error, e:
563         sys.stderr.write("%s\n" % e)
564         sys.exit(1)