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