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