python: Implement write support in Python IDL for OVSDB.
[sliver-openvswitch.git] / tests / test-ovsdb.py
1 # Copyright (c) 2009, 2010, 2011 Nicira Networks
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 import idltest
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 def do_default_atoms():
39     for type_ in types.ATOMIC_TYPES:
40         if type_ == types.VoidType:
41             continue
42
43         sys.stdout.write("%s: " % type_.to_string())
44
45         atom = data.Atom.default(type_)
46         if atom != data.Atom.default(type_):
47             sys.stdout.write("wrong\n")
48             sys.exit(1)
49
50         sys.stdout.write("OK\n")
51
52 def do_default_data():
53     any_errors = False
54     for n_min in 0, 1:
55         for key in types.ATOMIC_TYPES:
56             if key == types.VoidType:
57                 continue
58             for value in types.ATOMIC_TYPES:
59                 if value == types.VoidType:
60                     valueBase = None
61                 else:
62                     valueBase = types.BaseType(value)
63                 type_ = types.Type(types.BaseType(key), valueBase, n_min, 1)
64                 assert type_.is_valid()
65
66                 sys.stdout.write("key %s, value %s, n_min %d: "
67                                  % (key.to_string(), value.to_string(), n_min))
68
69                 datum = data.Datum.default(type_)
70                 if datum != data.Datum.default(type_):
71                     sys.stdout.write("wrong\n")
72                     any_errors = True
73                 else:
74                     sys.stdout.write("OK\n")
75     if any_errors:
76         sys.exit(1)
77
78 def do_parse_atomic_type(type_string):
79     type_json = unbox_json(ovs.json.from_string(type_string))
80     atomic_type = types.AtomicType.from_json(type_json)
81     print ovs.json.to_string(atomic_type.to_json(), sort_keys=True)
82
83 def do_parse_base_type(type_string):
84     type_json = unbox_json(ovs.json.from_string(type_string))
85     base_type = types.BaseType.from_json(type_json)
86     print ovs.json.to_string(base_type.to_json(), sort_keys=True)
87
88 def do_parse_type(type_string):
89     type_json = unbox_json(ovs.json.from_string(type_string))
90     type_ = types.Type.from_json(type_json)
91     print ovs.json.to_string(type_.to_json(), sort_keys=True)
92
93 def do_parse_atoms(type_string, *atom_strings):
94     type_json = unbox_json(ovs.json.from_string(type_string))
95     base = types.BaseType.from_json(type_json)
96     for atom_string in atom_strings:
97         atom_json = unbox_json(ovs.json.from_string(atom_string))
98         try:
99             atom = data.Atom.from_json(base, atom_json)
100             print ovs.json.to_string(atom.to_json())
101         except error.Error, e:
102             print unicode(e)
103
104 def do_parse_data(type_string, *data_strings):
105     type_json = unbox_json(ovs.json.from_string(type_string))
106     type_ = types.Type.from_json(type_json)
107     for datum_string in data_strings:
108         datum_json = unbox_json(ovs.json.from_string(datum_string))
109         datum = data.Datum.from_json(type_, datum_json)
110         print ovs.json.to_string(datum.to_json())
111
112 def do_sort_atoms(type_string, atom_strings):
113     type_json = unbox_json(ovs.json.from_string(type_string))
114     base = types.BaseType.from_json(type_json)
115     atoms = [data.Atom.from_json(base, atom_json)
116              for atom_json in unbox_json(ovs.json.from_string(atom_strings))]
117     print ovs.json.to_string([data.Atom.to_json(atom)
118                               for atom in sorted(atoms)])
119
120 def do_parse_column(name, column_string):
121     column_json = unbox_json(ovs.json.from_string(column_string))
122     column = ovs.db.schema.ColumnSchema.from_json(column_json, name)
123     print ovs.json.to_string(column.to_json(), sort_keys=True)
124
125 def do_parse_table(name, table_string, default_is_root_string='false'):
126     default_is_root = default_is_root_string == 'true'
127     table_json = unbox_json(ovs.json.from_string(table_string))
128     table = ovs.db.schema.TableSchema.from_json(table_json, name)
129     print ovs.json.to_string(table.to_json(default_is_root), sort_keys=True)
130
131 def do_parse_schema(schema_string):
132     schema_json = unbox_json(ovs.json.from_string(schema_string))
133     schema = ovs.db.schema.DbSchema.from_json(schema_json)
134     print ovs.json.to_string(schema.to_json(), sort_keys=True)
135
136 def print_idl(idl, step):
137     simple = idl.tables["simple"].rows
138     l1 = idl.tables["link1"].rows
139     l2 = idl.tables["link2"].rows
140
141     n = 0
142     for row in simple.itervalues():
143         s = ("%03d: i=%s r=%s b=%s s=%s u=%s "
144              "ia=%s ra=%s ba=%s sa=%s ua=%s uuid=%s"
145              % (step, row.i, row.r, row.b, row.s, row.u,
146                 row.ia, row.ra, row.ba, row.sa, row.ua, row.uuid))
147         s = re.sub('""|,|u?\'', "", s)
148         s = re.sub('UUID\(([^)]+)\)', r'\1', s)
149         s = re.sub('False', 'false', s)
150         s = re.sub('True', 'true', s)
151         s = re.sub(r'(ba)=([^[][^ ]*) ', r'\1=[\2] ', s)
152         print(s)
153         n += 1
154
155     for row in l1.itervalues():
156         s = ["%03d: i=%s k=" % (step, row.i)]
157         if row.k:
158             s.append(str(row.k.i))
159         s.append(" ka=[")
160         s.append(' '.join(sorted(str(ka.i) for ka in row.ka)))
161         s.append("] l2=")
162         if row.l2:
163             s.append(str(row.l2[0].i))
164         s.append(" uuid=%s" % row.uuid)
165         print(''.join(s))
166         n += 1
167
168     for row in l2.itervalues():
169         s = ["%03d: i=%s l1=" % (step, row.i)]
170         if row.l1:
171             s.append(str(row.l1.i))
172         s.append(" uuid=%s" % row.uuid)
173         print(''.join(s))
174         n += 1
175
176     if not n:
177         print("%03d: empty" % step)
178     sys.stdout.flush()
179
180 def substitute_uuids(json, symtab):
181     if type(json) in [str, unicode]:
182         symbol = symtab.get(json)
183         if symbol:
184             return str(symbol)
185     elif type(json) == list:
186         return [substitute_uuids(element, symtab) for element in json]
187     elif type(json) == dict:
188         d = {}
189         for key, value in json.iteritems():
190             d[key] = substitute_uuids(value, symtab)
191         return d
192     return json
193
194 def parse_uuids(json, symtab):
195     if type(json) in [str, unicode] and ovs.ovsuuid.is_valid_string(json):
196         name = "#%d#" % len(symtab)
197         sys.stderr.write("%s = %s\n" % (name, json))
198         symtab[name] = json
199     elif type(json) == list:
200         for element in json:
201             parse_uuids(element, symtab)
202     elif type(json) == dict:
203         for value in json.itervalues():
204             parse_uuids(value, symtab)
205
206 def idltest_find_simple(idl, i):
207     for row in idl.tables["simple"].rows.itervalues():
208         if row.i == i:
209             return row
210     return None
211
212 def idl_set(idl, commands, step):
213     txn = ovs.db.idl.Transaction(idl)
214     increment = False
215     for command in commands.split(','):
216         words = command.split()
217         name = words[0]
218         args = words[1:]
219
220         if name == "set":
221             if len(args) != 3:
222                 sys.stderr.write('"set" command requires 3 arguments\n')
223                 sys.exit(1)
224
225             s = idltest_find_simple(idl, int(args[0]))
226             if not s:
227                 sys.stderr.write('"set" command asks for nonexistent i=%d\n'
228                                  % int(args[0]))
229                 sys.exit(1)
230
231             if args[1] == "b":
232                 s.b = args[2] == "1"
233             elif args[1] == "s":
234                 s.s = args[2]
235             elif args[1] == "u":
236                 s.u = uuid.UUID(args[2])
237             elif args[1] == "r":
238                 s.r = float(args[2])
239             else:
240                 sys.stderr.write('"set" comamnd asks for unknown column %s\n'
241                                  % args[2])
242                 sys.stderr.exit(1)
243         elif name == "insert":
244             if len(args) != 1:
245                 sys.stderr.write('"set" command requires 1 argument\n')
246                 sys.exit(1)
247
248             s = txn.insert(idl.tables["simple"])
249             s.i = int(args[0])
250         elif name == "delete":
251             if len(args) != 1:
252                 sys.stderr.write('"delete" command requires 1 argument\n')
253                 sys.exit(1)
254
255             s = idltest_find_simple(idl, int(args[0]))
256             if not s:
257                 sys.stderr.write('"delete" command asks for nonexistent i=%d\n'
258                                  % int(args[0]))
259                 sys.exit(1)
260             s.delete()
261         elif name == "verify":
262             if len(args) != 2:
263                 sys.stderr.write('"verify" command requires 2 arguments\n')
264                 sys.exit(1)
265
266             s = idltest_find_simple(idl, int(args[0]))
267             if not s:
268                 sys.stderr.write('"verify" command asks for nonexistent i=%d\n'
269                                  % int(args[0]))
270                 sys.exit(1)
271
272             if args[1] in ("i", "b", "s", "u", "r"):
273                 s.verify(args[1])
274             else:
275                 sys.stderr.write('"verify" command asks for unknown column '
276                                  '"%s"\n' % args[1])
277                 sys.exit(1)
278         elif name == "increment":
279             if len(args) != 2:
280                 sys.stderr.write('"increment" command requires 2 arguments\n')
281                 sys.exit(1)
282
283             txn.increment(args[0], args[1], [])
284             increment = True
285         elif name == "abort":
286             txn.abort()
287             break
288         elif name == "destroy":
289             print "%03d: destroy" % step
290             sys.stdout.flush()
291             txn.abort()
292             return
293         else:
294             sys.stderr.write("unknown command %s\n" % name)
295             sys.exit(1)
296
297     status = txn.commit_block()
298     sys.stdout.write("%03d: commit, status=%s"
299                      % (step, ovs.db.idl.Transaction.status_to_string(status)))
300     if increment and status == ovs.db.idl.Transaction.SUCCESS:
301         sys.stdout.write(", increment=%d" % txn.get_increment_new_value())
302     sys.stdout.write("\n")
303     sys.stdout.flush()
304
305 def do_idl(schema_file, remote, *commands):
306     schema = ovs.db.schema.DbSchema.from_json(ovs.json.from_file(schema_file))
307     idl = ovs.db.idl.Idl(remote, schema)
308
309     if commands:
310         error, stream = ovs.stream.Stream.open_block(
311             ovs.stream.Stream.open(remote))
312         if error:
313             sys.stderr.write("failed to connect to \"%s\"" % remote)
314             sys.exit(1)
315         rpc = ovs.jsonrpc.Connection(stream)
316     else:
317         rpc = None
318
319     symtab = {}
320     seqno = 0
321     step = 0
322     for command in commands:
323         if command.startswith("+"):
324             # The previous transaction didn't change anything.
325             command = command[1:]
326         else:
327             # Wait for update.
328             while idl.change_seqno == seqno and not idl.run():
329                 rpc.run()
330
331                 poller = ovs.poller.Poller()
332                 idl.wait(poller)
333                 rpc.wait(poller)
334                 poller.block()
335                 
336             print_idl(idl, step)
337             step += 1
338
339         seqno = idl.change_seqno
340
341         if command == "reconnect":
342             print("%03d: reconnect" % step)
343             sys.stdout.flush()
344             step += 1
345             idl.force_reconnect()
346         elif not command.startswith("["):
347             idl_set(idl, command, step)
348             step += 1
349         else:
350             json = ovs.json.from_string(command)
351             if type(json) in [str, unicode]:
352                 sys.stderr.write("\"%s\": %s\n" % (command, json))
353                 sys.exit(1)
354             json = substitute_uuids(json, symtab)
355             request = ovs.jsonrpc.Message.create_request("transact", json)
356             error, reply = rpc.transact_block(request)
357             if error:
358                 sys.stderr.write("jsonrpc transaction failed: %s"
359                                  % os.strerror(error))
360                 sys.exit(1)
361             sys.stdout.write("%03d: " % step)
362             sys.stdout.flush()
363             step += 1
364             if reply.result is not None:
365                 parse_uuids(reply.result, symtab)
366             reply.id = None
367             sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
368             sys.stdout.flush()
369
370     if rpc:
371         rpc.close()
372     while idl.change_seqno == seqno and not idl.run():
373         poller = ovs.poller.Poller()
374         idl.wait(poller)
375         poller.block()
376     print_idl(idl, step)
377     step += 1
378     idl.close()
379     print("%03d: done" % step)
380
381 def usage():
382     print """\
383 %(program_name)s: test utility for Open vSwitch database Python bindings
384 usage: %(program_name)s [OPTIONS] COMMAND ARG...
385
386 The following commands are supported:
387 default-atoms
388   test ovsdb_atom_default()
389 default-data
390   test ovsdb_datum_default()
391 parse-atomic-type TYPE
392   parse TYPE as OVSDB atomic type, and re-serialize
393 parse-base-type TYPE
394   parse TYPE as OVSDB base type, and re-serialize
395 parse-type JSON
396   parse JSON as OVSDB type, and re-serialize
397 parse-atoms TYPE ATOM...
398   parse JSON ATOMs as atoms of TYPE, and re-serialize
399 parse-atom-strings TYPE ATOM...
400   parse string ATOMs as atoms of given TYPE, and re-serialize
401 sort-atoms TYPE ATOM...
402   print JSON ATOMs in sorted order
403 parse-data TYPE DATUM...
404   parse JSON DATUMs as data of given TYPE, and re-serialize
405 parse-column NAME OBJECT
406   parse column NAME with info OBJECT, and re-serialize
407 parse-table NAME OBJECT [DEFAULT-IS-ROOT]
408   parse table NAME with info OBJECT
409 parse-schema JSON
410   parse JSON as an OVSDB schema, and re-serialize
411 idl SCHEMA SERVER [TRANSACTION...]
412   connect to SERVER (which has the specified SCHEMA) and dump the
413   contents of the database as seen initially by the IDL implementation
414   and after executing each TRANSACTION.  (Each TRANSACTION must modify
415   the database or this command will hang.)
416
417 The following options are also available:
418   -t, --timeout=SECS          give up after SECS seconds
419   -h, --help                  display this help message\
420 """ % {'program_name': ovs.util.PROGRAM_NAME}
421     sys.exit(0)
422
423 def main(argv):
424     try:
425         options, args = getopt.gnu_getopt(argv[1:], 't:h',
426                                           ['timeout',
427                                            'help'])
428     except getopt.GetoptError, geo:
429         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
430         sys.exit(1)
431
432     for key, value in options:
433         if key in ['-h', '--help']:
434             usage()
435         elif key in ['-t', '--timeout']:
436             try:
437                 timeout = int(value)
438                 if timeout < 1:
439                     raise TypeError
440             except TypeError:
441                 raise error.Error("value %s on -t or --timeout is not at "
442                                   "least 1" % value)
443             signal.alarm(timeout)
444         else:
445             sys.exit(0)
446
447     if not args:
448         sys.stderr.write("%s: missing command argument "
449                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
450         sys.exit(1)
451
452     commands = {"default-atoms": (do_default_atoms, 0),
453                 "default-data": (do_default_data, 0),
454                 "parse-atomic-type": (do_parse_atomic_type, 1),
455                 "parse-base-type": (do_parse_base_type, 1),
456                 "parse-type": (do_parse_type, 1),
457                 "parse-atoms": (do_parse_atoms, (2,)),
458                 "parse-data": (do_parse_data, (2,)),
459                 "sort-atoms": (do_sort_atoms, 2),
460                 "parse-column": (do_parse_column, 2),
461                 "parse-table": (do_parse_table, (2, 3)),
462                 "parse-schema": (do_parse_schema, 1),
463                 "idl": (do_idl, (2,))}
464
465     command_name = args[0]
466     args = args[1:]
467     if not command_name in commands:
468         sys.stderr.write("%s: unknown command \"%s\" "
469                          "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
470                                                       command_name))
471         sys.exit(1)
472
473     func, n_args = commands[command_name]
474     if type(n_args) == tuple:
475         if len(args) < n_args[0]:
476             sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
477                              "only %d provided\n"
478                              % (ovs.util.PROGRAM_NAME, command_name,
479                                 n_args, len(args)))
480             sys.exit(1)
481     elif type(n_args) == int:
482         if len(args) != n_args:
483             sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
484                              "provided\n"
485                              % (ovs.util.PROGRAM_NAME, command_name,
486                                 n_args, len(args)))
487             sys.exit(1)
488     else:
489         assert False
490
491     func(*args)
492
493 if __name__ == '__main__':
494     try:
495         main(sys.argv)
496     except error.Error, e:
497         sys.stderr.write("%s\n" % e)
498         sys.exit(1)