Implement initial Python bindings for Open vSwitch database.
[sliver-openvswitch.git] / tests / test-ovsdb.py
1 # Copyright (c) 2009, 2010 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 codecs
16 import getopt
17 import re
18 import os
19 import signal
20 import sys
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 def unbox_json(json):
32     if type(json) == list and len(json) == 1:
33         return json[0]
34     else:
35         return json
36
37 def do_default_atoms():
38     for type in types.ATOMIC_TYPES:
39         if type == types.VoidType:
40             continue
41
42         sys.stdout.write("%s: " % type.to_string())
43
44         atom = data.Atom.default(type)
45         if atom != data.Atom.default(type):
46             sys.stdout.write("wrong\n")
47             sys.exit(1)
48
49         sys.stdout.write("OK\n")
50
51 def do_default_data():
52     any_errors = False
53     for n_min in 0, 1:
54         for key in types.ATOMIC_TYPES:
55             if key == types.VoidType:
56                 continue
57             for value in types.ATOMIC_TYPES:
58                 if value == types.VoidType:
59                     valueBase = None
60                 else:
61                     valueBase = types.BaseType(value)
62                 type = types.Type(types.BaseType(key), valueBase, n_min, 1)
63                 assert type.is_valid()
64
65                 sys.stdout.write("key %s, value %s, n_min %d: "
66                                  % (key.to_string(), value.to_string(), n_min))
67
68                 datum = data.Datum.default(type)
69                 if datum != data.Datum.default(type):
70                     sys.stdout.write("wrong\n")
71                     any_errors = True
72                 else:
73                     sys.stdout.write("OK\n")
74     if any_errors:
75         sys.exit(1)
76
77 def do_parse_atomic_type(type_string):
78     type_json = unbox_json(ovs.json.from_string(type_string))
79     atomic_type = types.AtomicType.from_json(type_json)
80     print ovs.json.to_string(atomic_type.to_json(), sort_keys=True)
81
82 def do_parse_base_type(type_string):
83     type_json = unbox_json(ovs.json.from_string(type_string))
84     base_type = types.BaseType.from_json(type_json)
85     print ovs.json.to_string(base_type.to_json(), sort_keys=True)
86
87 def do_parse_type(type_string):
88     type_json = unbox_json(ovs.json.from_string(type_string))
89     type = types.Type.from_json(type_json)
90     print ovs.json.to_string(type.to_json(), sort_keys=True)
91
92 def do_parse_atoms(type_string, *atom_strings):
93     type_json = unbox_json(ovs.json.from_string(type_string))
94     base = types.BaseType.from_json(type_json)
95     for atom_string in atom_strings:
96         atom_json = unbox_json(ovs.json.from_string(atom_string))
97         try:
98             atom = data.Atom.from_json(base, atom_json)
99             print ovs.json.to_string(atom.to_json())
100         except error.Error, e:
101             print e
102
103 def do_parse_data(type_string, *data_strings):
104     type_json = unbox_json(ovs.json.from_string(type_string))
105     type = types.Type.from_json(type_json)
106     for datum_string in data_strings:
107         datum_json = unbox_json(ovs.json.from_string(datum_string))
108         datum = data.Datum.from_json(type, datum_json)
109         print ovs.json.to_string(datum.to_json())
110
111 def do_sort_atoms(type_string, atom_strings):
112     type_json = unbox_json(ovs.json.from_string(type_string))
113     base = types.BaseType.from_json(type_json)
114     atoms = [data.Atom.from_json(base, atom_json)
115              for atom_json in unbox_json(ovs.json.from_string(atom_strings))]
116     print ovs.json.to_string([data.Atom.to_json(atom)
117                               for atom in sorted(atoms)])
118
119 def do_parse_column(name, column_string):
120     column_json = unbox_json(ovs.json.from_string(column_string))
121     column = ovs.db.schema.ColumnSchema.from_json(column_json, name)
122     print ovs.json.to_string(column.to_json(), sort_keys=True)
123
124 def do_parse_table(name, table_string):
125     table_json = unbox_json(ovs.json.from_string(table_string))
126     table = ovs.db.schema.TableSchema.from_json(table_json, name)
127     print ovs.json.to_string(table.to_json(), sort_keys=True)
128
129 def do_parse_rows(table_string, *rows):
130     table_json = unbox_json(ovs.json.from_string(table_string))
131     table = ovs.db.schema.TableSchema.from_json(table_json, name)
132
133 def do_parse_schema(schema_string):
134     schema_json = unbox_json(ovs.json.from_string(schema_string))
135     schema = ovs.db.schema.DbSchema.from_json(schema_json)
136     print ovs.json.to_string(schema.to_json(), sort_keys=True)
137
138 def print_idl(idl, step):
139     n = 0
140     for uuid, row in idl.data["simple"].iteritems():
141         s = ("%03d: i=%s r=%s b=%s s=%s u=%s "
142              "ia=%s ra=%s ba=%s sa=%s ua=%s uuid=%s"
143              % (step, row.i, row.r, row.b, row.s, row.u,
144                 row.ia, row.ra, row.ba, row.sa, row.ua, uuid))
145         print(re.sub('""|,', "", s))
146         n += 1
147     if not n:
148         print("%03d: empty" % step)
149
150 def substitute_uuids(json, symtab):
151     if type(json) in [str, unicode]:
152         symbol = symtab.get(json)
153         if symbol:
154             return str(symbol)
155     elif type(json) == list:
156         return [substitute_uuids(element, symtab) for element in json]
157     elif type(json) == dict:
158         d = {}
159         for key, value in json.iteritems():
160             d[key] = substitute_uuids(value, symtab)
161         return d
162     return json
163
164 def parse_uuids(json, symtab):
165     if type(json) in [str, unicode] and ovs.ovsuuid.UUID.is_valid_string(json):
166         name = "#%d#" % len(symtab)
167         sys.stderr.write("%s = %s\n" % (name, json))
168         symtab[name] = json
169     elif type(json) == list:
170         for element in json:
171             parse_uuids(element, symtab)
172     elif type(json) == dict:
173         for value in json.itervalues():
174             parse_uuids(value, symtab)
175
176 def do_idl(remote, *commands):
177     idl = ovs.db.idl.Idl(remote, "idltest")
178
179     if commands:
180         error, stream = ovs.stream.Stream.open_block(
181             ovs.stream.Stream.open(remote))
182         if error:
183             sys.stderr.write("failed to connect to \"%s\"" % remote)
184             sys.exit(1)
185         rpc = ovs.jsonrpc.Connection(stream)
186     else:
187         rpc = None
188
189     symtab = {}
190     seqno = 0
191     step = 0
192     for command in commands:
193         if command.startswith("+"):
194             # The previous transaction didn't change anything.
195             command = command[1:]
196         else:
197             # Wait for update.
198             while idl.get_seqno() == seqno and not idl.run():
199                 rpc.run()
200
201                 poller = ovs.poller.Poller()
202                 idl.wait(poller)
203                 rpc.wait(poller)
204                 poller.block()
205                 
206             print_idl(idl, step)
207             step += 1
208
209         seqno = idl.get_seqno()
210
211         if command == "reconnect":
212             print("%03d: reconnect" % step)
213             step += 1
214             idl.force_reconnect()
215         elif not command.startswith("["):
216             idl_set(idl, command, step)
217             step += 1
218         else:
219             json = ovs.json.from_string(command)
220             if type(json) in [str, unicode]:
221                 sys.stderr.write("\"%s\": %s\n" % (command, json))
222                 sys.exit(1)
223             json = substitute_uuids(json, symtab)
224             request = ovs.jsonrpc.Message.create_request("transact", json)
225             error, reply = rpc.transact_block(request)
226             if error:
227                 sys.stderr.write("jsonrpc transaction failed: %s"
228                                  % os.strerror(error))
229                 sys.exit(1)
230             sys.stdout.write("%03d: " % step)
231             sys.stdout.flush()
232             step += 1
233             if reply.result is not None:
234                 parse_uuids(reply.result, symtab)
235             reply.id = None
236             sys.stdout.write("%s\n" % ovs.json.to_string(reply.to_json()))
237
238     if rpc:
239         rpc.close()
240     while idl.get_seqno() == seqno and not idl.run():
241         poller = ovs.poller.Poller()
242         idl.wait(poller)
243         poller.block()
244     print_idl(idl, step)
245     step += 1
246     idl.close()
247     print("%03d: done" % step)
248
249 def usage():
250     print """\
251 %(program_name)s: test utility for Open vSwitch database Python bindings
252 usage: %(program_name)s [OPTIONS] COMMAND ARG...
253
254 The following commands are supported:
255 default-atoms
256   test ovsdb_atom_default()
257 default-data
258   test ovsdb_datum_default()
259 parse-atomic-type TYPE
260   parse TYPE as OVSDB atomic type, and re-serialize
261 parse-base-type TYPE
262   parse TYPE as OVSDB base type, and re-serialize
263 parse-type JSON
264   parse JSON as OVSDB type, and re-serialize
265 parse-atoms TYPE ATOM...
266   parse JSON ATOMs as atoms of TYPE, and re-serialize
267 parse-atom-strings TYPE ATOM...
268   parse string ATOMs as atoms of given TYPE, and re-serialize
269 sort-atoms TYPE ATOM...
270   print JSON ATOMs in sorted order
271 parse-data TYPE DATUM...
272   parse JSON DATUMs as data of given TYPE, and re-serialize
273 parse-column NAME OBJECT
274   parse column NAME with info OBJECT, and re-serialize
275 parse-table NAME OBJECT
276   parse table NAME with info OBJECT
277 parse-schema JSON
278   parse JSON as an OVSDB schema, and re-serialize
279 idl SERVER [TRANSACTION...]
280   connect to SERVER and dump the contents of the database
281   as seen initially by the IDL implementation and after
282   executing each TRANSACTION.  (Each TRANSACTION must modify
283   the database or this command will hang.)
284
285 The following options are also available:
286   -t, --timeout=SECS          give up after SECS seconds
287   -h, --help                  display this help message\
288 """ % {'program_name': ovs.util.PROGRAM_NAME}
289     sys.exit(0)
290
291 def main(argv):
292     # Make stdout and stderr UTF-8, even if they are redirected to a file.
293     sys.stdout = codecs.getwriter("utf-8")(sys.stdout)
294     sys.stderr = codecs.getwriter("utf-8")(sys.stderr)
295
296     try:
297         options, args = getopt.gnu_getopt(argv[1:], 't:h',
298                                           ['timeout',
299                                            'help'])
300     except getopt.GetoptError, geo:
301         sys.stderr.write("%s: %s\n" % (ovs.util.PROGRAM_NAME, geo.msg))
302         sys.exit(1)
303
304     for key, value in options:
305         if key in ['-h', '--help']:
306             usage()
307         elif key in ['-t', '--timeout']:
308             try:
309                 timeout = int(value)
310                 if timeout < 1:
311                     raise TypeError
312             except TypeError:
313                 raise error.Error("value %s on -t or --timeout is not at "
314                                   "least 1" % value)
315             signal.alarm(timeout)
316         else:
317             sys.exit(0)
318
319     optKeys = [key for key, value in options]
320
321     if not args:
322         sys.stderr.write("%s: missing command argument "
323                          "(use --help for help)\n" % ovs.util.PROGRAM_NAME)
324         sys.exit(1)
325
326     commands = {"default-atoms": (do_default_atoms, 0),
327                 "default-data": (do_default_data, 0),
328                 "parse-atomic-type": (do_parse_atomic_type, 1),
329                 "parse-base-type": (do_parse_base_type, 1),
330                 "parse-type": (do_parse_type, 1),
331                 "parse-atoms": (do_parse_atoms, (2,)),
332                 "parse-data": (do_parse_data, (2,)),
333                 "sort-atoms": (do_sort_atoms, 2),
334                 "parse-column": (do_parse_column, 2),
335                 "parse-table": (do_parse_table, 2),
336                 "parse-schema": (do_parse_schema, 1),
337                 "idl": (do_idl, (1,))}
338
339     command_name = args[0]
340     args = args[1:]
341     if not command_name in commands:
342         sys.stderr.write("%s: unknown command \"%s\" "
343                          "(use --help for help)\n" % (ovs.util.PROGRAM_NAME,
344                                                       command_name))
345         sys.exit(1)
346
347     func, n_args = commands[command_name]
348     if type(n_args) == tuple:
349         if len(args) < n_args[0]:
350             sys.stderr.write("%s: \"%s\" requires at least %d arguments but "
351                              "only %d provided\n"
352                              % (ovs.util.PROGRAM_NAME, command_name,
353                                 n_args, len(args)))
354             sys.exit(1)
355     elif type(n_args) == int:
356         if len(args) != n_args:
357             sys.stderr.write("%s: \"%s\" requires %d arguments but %d "
358                              "provided\n"
359                              % (ovs.util.PROGRAM_NAME, command_name,
360                                 n_args, len(args)))
361             sys.exit(1)
362     else:
363         assert False
364
365     func(*args)
366
367 if __name__ == '__main__':
368     try:
369         main(sys.argv)
370     except error.Error, e:
371         sys.stderr.write("%s\n" % e)
372         sys.exit(1)