ovsdb-idl: Support for readonly columns that are fetched on-demand
[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         monitor = {}
393         readonly = {}
394         for x in commands[0][1:].split("?"):
395             readonly = []
396             table, columns = x.split(":")
397             columns = columns.split(",")
398             for index, column in enumerate(columns):
399                 if column[-1] == '!':
400                     columns[index] = columns[index][:-1]
401                     readonly.append(columns[index])
402             schema_helper.register_columns(table, columns, readonly)
403         commands = commands[1:]
404     else:
405         schema_helper.register_all()
406     idl = ovs.db.idl.Idl(remote, schema_helper)
407
408     if commands:
409         error, stream = ovs.stream.Stream.open_block(
410             ovs.stream.Stream.open(remote))
411         if error:
412             sys.stderr.write("failed to connect to \"%s\"" % remote)
413             sys.exit(1)
414         rpc = ovs.jsonrpc.Connection(stream)
415     else:
416         rpc = None
417
418     symtab = {}
419     seqno = 0
420     step = 0
421     for command in commands:
422         if command.startswith("+"):
423             # The previous transaction didn't change anything.
424             command = command[1:]
425         else:
426             # Wait for update.
427             while idl.change_seqno == seqno and not idl.run():
428                 rpc.run()
429
430                 poller = ovs.poller.Poller()
431                 idl.wait(poller)
432                 rpc.wait(poller)
433                 poller.block()
434
435             print_idl(idl, step)
436             step += 1
437
438         seqno = idl.change_seqno
439
440         if command == "reconnect":
441             print("%03d: reconnect" % step)
442             sys.stdout.flush()
443             step += 1
444             idl.force_reconnect()
445         elif not command.startswith("["):
446             idl_set(idl, command, step)
447             step += 1
448         else:
449             json = ovs.json.from_string(command)
450             if type(json) in [str, unicode]:
451                 sys.stderr.write("\"%s\": %s\n" % (command, json))
452                 sys.exit(1)
453             json = substitute_uuids(json, symtab)
454             request = ovs.jsonrpc.Message.create_request("transact", json)
455             error, reply = rpc.transact_block(request)
456             if error:
457                 sys.stderr.write("jsonrpc transaction failed: %s"
458                                  % os.strerror(error))
459                 sys.exit(1)
460             elif reply.error is not None:
461                 sys.stderr.write("jsonrpc transaction failed: %s"
462                                  % reply.error)
463                 sys.exit(1)
464
465             sys.stdout.write("%03d: " % step)
466             sys.stdout.flush()
467             step += 1
468             if reply.result is not None:
469                 parse_uuids(reply.result, symtab)
470             reply.id = None
471             sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
472             sys.stdout.flush()
473
474     if rpc:
475         rpc.close()
476     while idl.change_seqno == seqno and not idl.run():
477         poller = ovs.poller.Poller()
478         idl.wait(poller)
479         poller.block()
480     print_idl(idl, step)
481     step += 1
482     idl.close()
483     print("%03d: done" % step)
484
485
486 def usage():
487     print """\
488 %(program_name)s: test utility for Open vSwitch database Python bindings
489 usage: %(program_name)s [OPTIONS] COMMAND ARG...
490
491 The following commands are supported:
492 default-atoms
493   test ovsdb_atom_default()
494 default-data
495   test ovsdb_datum_default()
496 parse-atomic-type TYPE
497   parse TYPE as OVSDB atomic type, and re-serialize
498 parse-base-type TYPE
499   parse TYPE as OVSDB base type, and re-serialize
500 parse-type JSON
501   parse JSON as OVSDB type, and re-serialize
502 parse-atoms TYPE ATOM...
503   parse JSON ATOMs as atoms of TYPE, and re-serialize
504 parse-atom-strings TYPE ATOM...
505   parse string ATOMs as atoms of given TYPE, and re-serialize
506 sort-atoms TYPE ATOM...
507   print JSON ATOMs in sorted order
508 parse-data TYPE DATUM...
509   parse JSON DATUMs as data of given TYPE, and re-serialize
510 parse-column NAME OBJECT
511   parse column NAME with info OBJECT, and re-serialize
512 parse-table NAME OBJECT [DEFAULT-IS-ROOT]
513   parse table NAME with info OBJECT
514 parse-schema JSON
515   parse JSON as an OVSDB schema, and re-serialize
516 idl SCHEMA SERVER [?T1:C1,C2...[?T2:C1,C2,...]...] [TRANSACTION...]
517   connect to SERVER (which has the specified SCHEMA) and dump the
518   contents of the database as seen initially by the IDL implementation
519   and after executing each TRANSACTION.  (Each TRANSACTION must modify
520   the database or this command will hang.)
521   By default, all columns of all tables are monitored. The "?" option
522   can be used to monitor specific Table:Column(s). The table and their
523   columns are listed as a string of the form starting with "?":
524       ?<table-name>:<column-name>,<column-name>,...
525   e.g.:
526       ?simple:b - Monitor column "b" in table "simple"
527   Entries for multiple tables are seperated by "?":
528       ?<table-name>:<column-name>,...?<table-name>:<column-name>,...
529   e.g.:
530       ?simple:b?link1:i,k - Monitor column "b" in table "simple",
531                             and column "i", "k" in table "link1"
532   Readonly columns: Suffixing a "!" after a column indicates that the
533   column is to be registered "readonly".
534   e.g.:
535       ?simple:i,b!  - Register interest in column "i" (monitoring) and
536                       column "b" (readonly).
537
538
539 The following options are also available:
540   -t, --timeout=SECS          give up after SECS seconds
541   -h, --help                  display this help message\
542 """ % {'program_name': ovs.util.PROGRAM_NAME}
543     sys.exit(0)
544
545
546 def main(argv):
547     try:
548         options, args = getopt.gnu_getopt(argv[1:], 't:h',
549                                           ['timeout',
550                                            'help'])
551     except getopt.GetoptError, geo:
552         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
553         sys.exit(1)
554
555     for key, value in options:
556         if key in ['-h', '--help']:
557             usage()
558         elif key in ['-t', '--timeout']:
559             try:
560                 timeout = int(value)
561                 if timeout < 1:
562                     raise TypeError
563             except TypeError:
564                 raise error.Error("value %s on -t or --timeout is not at "
565                                   "least 1" % value)
566             signal.alarm(timeout)
567         else:
568             sys.exit(0)
569
570     if not args:
571         sys.stderr.write("%s: missing command argument "
572                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
573         sys.exit(1)
574
575     commands = {"default-atoms": (do_default_atoms, 0),
576                 "default-data": (do_default_data, 0),
577                 "parse-atomic-type": (do_parse_atomic_type, 1),
578                 "parse-base-type": (do_parse_base_type, 1),
579                 "parse-type": (do_parse_type, 1),
580                 "parse-atoms": (do_parse_atoms, (2,)),
581                 "parse-data": (do_parse_data, (2,)),
582                 "sort-atoms": (do_sort_atoms, 2),
583                 "parse-column": (do_parse_column, 2),
584                 "parse-table": (do_parse_table, (2, 3)),
585                 "parse-schema": (do_parse_schema, 1),
586                 "idl": (do_idl, (2,))}
587
588     command_name = args[0]
589     args = args[1:]
590     if not command_name in commands:
591         sys.stderr.write("%s: unknown command \"%s\" "
592                          "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
593                                                       command_name))
594         sys.exit(1)
595
596     func, n_args = commands[command_name]
597     if type(n_args) == tuple:
598         if len(args) < n_args[0]:
599             sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
600                              "only %d provided\n"
601                              % (ovs.util.PROGRAM_NAME, command_name,
602                                 n_args, len(args)))
603             sys.exit(1)
604     elif type(n_args) == int:
605         if len(args) != n_args:
606             sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
607                              "provided\n"
608                              % (ovs.util.PROGRAM_NAME, command_name,
609                                 n_args, len(args)))
610             sys.exit(1)
611     else:
612         assert False
613
614     func(*args)
615
616
617 if __name__ == '__main__':
618     try:
619         main(sys.argv)
620     except error.Error, e:
621         sys.stderr.write("%s\n" % e)
622         sys.exit(1)