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