reconnect: Refactor tests to use common macro.
[sliver-openvswitch.git] / ovsdb / OVSDB.py
1 import re
2
3 class Error(Exception):
4     def __init__(self, msg):
5         Exception.__init__(self)
6         self.msg = msg
7
8 def getMember(json, name, validTypes, description, default=None):
9     if name in json:
10         member = json[name]
11         if len(validTypes) and type(member) not in validTypes:
12             raise Error("%s: type mismatch for '%s' member"
13                         % (description, name))
14         return member
15     return default
16
17 def mustGetMember(json, name, expectedType, description):
18     member = getMember(json, name, expectedType, description)
19     if member == None:
20         raise Error("%s: missing '%s' member" % (description, name))
21     return member
22
23 class DbSchema:
24     def __init__(self, name, tables):
25         self.name = name
26         self.tables = tables
27
28     @staticmethod
29     def fromJson(json):
30         name = mustGetMember(json, 'name', [unicode], 'database')
31         tablesJson = mustGetMember(json, 'tables', [dict], 'database')
32         tables = {}
33         for tableName, tableJson in tablesJson.iteritems():
34             tables[tableName] = TableSchema.fromJson(tableJson,
35                                                      "%s table" % tableName)
36         return DbSchema(name, tables)
37
38 class IdlSchema(DbSchema):
39     def __init__(self, name, tables, idlPrefix, idlHeader):
40         DbSchema.__init__(self, name, tables)
41         self.idlPrefix = idlPrefix
42         self.idlHeader = idlHeader
43
44     @staticmethod
45     def fromJson(json):
46         schema = DbSchema.fromJson(json)
47         idlPrefix = mustGetMember(json, 'idlPrefix', [unicode], 'database')
48         idlHeader = mustGetMember(json, 'idlHeader', [unicode], 'database')
49         return IdlSchema(schema.name, schema.tables, idlPrefix, idlHeader)
50
51 class TableSchema:
52     def __init__(self, columns):
53         self.columns = columns
54
55     @staticmethod
56     def fromJson(json, description):
57         columnsJson = mustGetMember(json, 'columns', [dict], description)
58         columns = {}
59         for name, json in columnsJson.iteritems():
60             columns[name] = ColumnSchema.fromJson(
61                 json, "column %s in %s" % (name, description))
62         return TableSchema(columns)
63
64 class ColumnSchema:
65     def __init__(self, type, persistent):
66         self.type = type
67         self.persistent = persistent
68
69     @staticmethod
70     def fromJson(json, description):
71         type = Type.fromJson(mustGetMember(json, 'type', [dict, unicode],
72                                            description),
73                              'type of %s' % description)
74         ephemeral = getMember(json, 'ephemeral', [bool], description)
75         persistent = ephemeral != True
76         return ColumnSchema(type, persistent)
77
78 def escapeCString(src):
79     dst = ""
80     for c in src:
81         if c in "\\\"":
82             dst += "\\" + c
83         elif ord(c) < 32:
84             if c == '\n':
85                 dst += '\\n'
86             elif c == '\r':
87                 dst += '\\r'
88             elif c == '\a':
89                 dst += '\\a'
90             elif c == '\b':
91                 dst += '\\b'
92             elif c == '\f':
93                 dst += '\\f'
94             elif c == '\t':
95                 dst += '\\t'
96             elif c == '\v':
97                 dst += '\\v'
98             else:
99                 dst += '\\%03o' % ord(c)
100         else:
101             dst += c
102     return dst
103
104 def returnUnchanged(x):
105     return x
106
107 class UUID:
108     x = "[0-9a-fA-f]"
109     uuidRE = re.compile("^(%s{8})-(%s{4})-(%s{4})-(%s{4})-(%s{4})(%s{8})$"
110                         % (x, x, x, x, x, x))
111
112     def __init__(self, value):
113         self.value = value
114
115     @staticmethod
116     def fromString(s):
117         if not uuidRE.match(s):
118             raise Error("%s is not a valid UUID" % s)
119         return UUID(s)
120
121     @staticmethod
122     def fromJson(json):
123         if UUID.isValidJson(json):
124             return UUID(json[1])
125         else:
126             raise Error("%s is not valid JSON for a UUID" % json)
127
128     @staticmethod
129     def isValidJson(json):
130         return len(json) == 2 and json[0] == "uuid" and uuidRE.match(json[1])
131             
132     def toJson(self):
133         return ["uuid", self.value]
134
135     def cInitUUID(self, var):
136         m = re.match(self.value)
137         return ["%s.parts[0] = 0x%s;" % (var, m.group(1)),
138                 "%s.parts[1] = 0x%s%s;" % (var, m.group(2), m.group(3)),
139                 "%s.parts[2] = 0x%s%s;" % (var, m.group(4), m.group(5)),
140                 "%s.parts[3] = 0x%s;" % (var, m.group(6))]
141
142 class Atom:
143     def __init__(self, type, value):
144         self.type = type
145         self.value = value
146
147     @staticmethod
148     def fromJson(type_, json):
149         if ((type_ == 'integer' and type(json) in [int, long])
150             or (type_ == 'real' and type(json) in [int, long, float])
151             or (type_ == 'boolean' and json in [True, False])
152             or (type_ == 'string' and type(json) in [str, unicode])):
153             return Atom(type_, json)
154         elif type_ == 'uuid':
155             return UUID.fromJson(json)
156         else:
157             raise Error("%s is not valid JSON for type %s" % (json, type_))
158
159     def toJson(self):
160         if self.type == 'uuid':
161             return self.value.toString()
162         else:
163             return self.value
164
165     def cInitAtom(self, var):
166         if self.type == 'integer':
167             return ['%s.integer = %d;' % (var, self.value)]
168         elif self.type == 'real':
169             return ['%s.real = %.15g;' % (var, self.value)]
170         elif self.type == 'boolean':
171             if self.value:
172                 return ['%s.boolean = true;']
173             else:
174                 return ['%s.boolean = false;']
175         elif self.type == 'string':
176             return ['%s.string = xstrdup("%s");'
177                     % (var, escapeCString(self.value))]
178         elif self.type == 'uuid':
179             return self.value.cInitUUID(var)
180
181     def toEnglish(self, escapeLiteral=returnUnchanged):
182         if self.type == 'integer':
183             return '%d' % self.value
184         elif self.type == 'real':
185             return '%.15g' % self.value
186         elif self.type == 'boolean':
187             if self.value:
188                 return 'true'
189             else:
190                 return 'false'
191         elif self.type == 'string':
192             return escapeLiteral(self.value)
193         elif self.type == 'uuid':
194             return self.value.value
195
196 # Returns integer x formatted in decimal with thousands set off by commas.
197 def commafy(x):
198     return _commafy("%d" % x)
199 def _commafy(s):
200     if s.startswith('-'):
201         return '-' + _commafy(s[1:])
202     elif len(s) <= 3:
203         return s
204     else:
205         return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
206
207 class BaseType:
208     def __init__(self, type,
209                  enum=None,
210                  refTable=None, refType="strong",
211                  minInteger=None, maxInteger=None,
212                  minReal=None, maxReal=None,
213                  minLength=None, maxLength=None):
214         self.type = type
215         self.enum = enum
216         self.refTable = refTable
217         self.refType = refType
218         self.minInteger = minInteger
219         self.maxInteger = maxInteger
220         self.minReal = minReal
221         self.maxReal = maxReal
222         self.minLength = minLength
223         self.maxLength = maxLength
224
225     @staticmethod
226     def fromJson(json, description):
227         if type(json) == unicode:
228             return BaseType(json)
229         else:
230             atomicType = mustGetMember(json, 'type', [unicode], description)
231             enum = getMember(json, 'enum', [], description)
232             if enum:
233                 enumType = Type(atomicType, None, 0, 'unlimited')
234                 enum = Datum.fromJson(enumType, enum)
235             refTable = getMember(json, 'refTable', [unicode], description)
236             refType = getMember(json, 'refType', [unicode], description)
237             if refType == None:
238                 refType = "strong"
239             minInteger = getMember(json, 'minInteger', [int, long], description)
240             maxInteger = getMember(json, 'maxInteger', [int, long], description)
241             minReal = getMember(json, 'minReal', [int, long, float], description)
242             maxReal = getMember(json, 'maxReal', [int, long, float], description)
243             minLength = getMember(json, 'minLength', [int], description)
244             maxLength = getMember(json, 'minLength', [int], description)
245             return BaseType(atomicType, enum, refTable, refType, minInteger, maxInteger, minReal, maxReal, minLength, maxLength)
246
247     def toEnglish(self, escapeLiteral=returnUnchanged):
248         if self.type == 'uuid' and self.refTable:
249             s = escapeLiteral(self.refTable)
250             if self.refType == 'weak':
251                 s = "weak reference to " + s
252             return s
253         else:
254             return self.type
255
256     def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
257         if self.enum:
258             literals = [value.toEnglish(escapeLiteral)
259                         for value in self.enum.values]
260             if len(literals) == 2:
261                 return 'either %s or %s' % (literals[0], literals[1])
262             else:
263                 return 'one of %s, %s, or %s' % (literals[0],
264                                                  ', '.join(literals[1:-1]),
265                                                  literals[-1])
266         elif self.minInteger != None and self.maxInteger != None:
267             return 'in range %s to %s' % (commafy(self.minInteger),
268                                          commafy(self.maxInteger))
269         elif self.minInteger != None:
270             return 'at least %s' % commafy(self.minInteger)
271         elif self.maxInteger != None:
272             return 'at most %s' % commafy(self.maxInteger)
273         elif self.minReal != None and self.maxReal != None:
274             return 'in range %g to %g' % (self.minReal, self.maxReal)
275         elif self.minReal != None:
276             return 'at least %g' % self.minReal
277         elif self.maxReal != None:
278             return 'at most %g' % self.maxReal
279         elif self.minLength != None and self.maxLength != None:
280             if self.minLength == self.maxLength:
281                 return 'exactly %d characters long' % (self.minLength)
282             else:
283                 return 'between %d and %d characters long' % (self.minLength, self.maxLength)
284         elif self.minLength != None:
285             return 'at least %d characters long' % self.minLength
286         elif self.maxLength != None:
287             return 'at most %d characters long' % self.maxLength
288         else:
289             return ''
290
291     def toCType(self, prefix):
292         if self.refTable:
293             return "struct %s%s *" % (prefix, self.refTable.lower())
294         else:
295             return {'integer': 'int64_t ',
296                     'real': 'double ',
297                     'uuid': 'struct uuid ',
298                     'boolean': 'bool ',
299                     'string': 'char *'}[self.type]
300
301     def toAtomicType(self):
302         return "OVSDB_TYPE_%s" % self.type.upper()
303
304     def copyCValue(self, dst, src):
305         args = {'dst': dst, 'src': src}
306         if self.refTable:
307             return ("%(dst)s = %(src)s->header_.uuid;") % args
308         elif self.type == 'string':
309             return "%(dst)s = xstrdup(%(src)s);" % args
310         else:
311             return "%(dst)s = %(src)s;" % args
312
313     def initCDefault(self, var, isOptional):
314         if self.refTable:
315             return "%s = NULL;" % var
316         elif self.type == 'string' and not isOptional:
317             return "%s = \"\";" % var
318         else:
319             return {'integer': '%s = 0;',
320                     'real': '%s = 0.0;',
321                     'uuid': 'uuid_zero(&%s);',
322                     'boolean': '%s = false;',
323                     'string': '%s = NULL;'}[self.type] % var
324
325     def cInitBaseType(self, indent, var):
326         stmts = []
327         stmts.append('ovsdb_base_type_init(&%s, OVSDB_TYPE_%s);' % (
328                 var, self.type.upper()),)
329         if self.enum:
330             stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
331                          % (var, var))
332             stmts += self.enum.cInitDatum("%s.enum_" % var)
333         if self.type == 'integer':
334             if self.minInteger != None:
335                 stmts.append('%s.u.integer.min = INT64_C(%d);' % (var, self.minInteger))
336             if self.maxInteger != None:
337                 stmts.append('%s.u.integer.max = INT64_C(%d);' % (var, self.maxInteger))
338         elif self.type == 'real':
339             if self.minReal != None:
340                 stmts.append('%s.u.real.min = %d;' % (var, self.minReal))
341             if self.maxReal != None:
342                 stmts.append('%s.u.real.max = %d;' % (var, self.maxReal))
343         elif self.type == 'string':
344             if self.minLength != None:
345                 stmts.append('%s.u.string.minLen = %d;' % (var, self.minLength))            
346             if self.maxLength != None:
347                 stmts.append('%s.u.string.maxLen = %d;' % (var, self.maxLength))
348         elif self.type == 'uuid':
349             if self.refTable != None:
350                 stmts.append('%s.u.uuid.refTableName = "%s";' % (var, escapeCString(self.refTable)))
351         return '\n'.join([indent + stmt for stmt in stmts])
352
353 class Type:
354     def __init__(self, key, value=None, min=1, max=1):
355         self.key = key
356         self.value = value
357         self.min = min
358         self.max = max
359     
360     @staticmethod
361     def fromJson(json, description):
362         if type(json) == unicode:
363             return Type(BaseType(json))
364         else:
365             keyJson = mustGetMember(json, 'key', [dict, unicode], description)
366             key = BaseType.fromJson(keyJson, 'key in %s' % description)
367
368             valueJson = getMember(json, 'value', [dict, unicode], description)
369             if valueJson:
370                 value = BaseType.fromJson(valueJson,
371                                           'value in %s' % description)
372             else:
373                 value = None
374
375             min = getMember(json, 'min', [int], description, 1)
376             max = getMember(json, 'max', [int, unicode], description, 1)
377             return Type(key, value, min, max)
378
379     def isScalar(self):
380         return self.min == 1 and self.max == 1 and not self.value
381
382     def isOptional(self):
383         return self.min == 0 and self.max == 1
384
385     def isOptionalPointer(self):
386         return (self.min == 0 and self.max == 1 and not self.value
387                 and (self.key.type == 'string' or self.key.refTable))
388
389     def toEnglish(self, escapeLiteral=returnUnchanged):
390         keyName = self.key.toEnglish(escapeLiteral)
391         if self.value:
392             valueName = self.value.toEnglish(escapeLiteral)
393
394         if self.isScalar():
395             return keyName
396         elif self.isOptional():
397             if self.value:
398                 return "optional %s-%s pair" % (keyName, valueName)
399             else:
400                 return "optional %s" % keyName
401         else:
402             if self.max == "unlimited":
403                 if self.min:
404                     quantity = "%d or more " % self.min
405                 else:
406                     quantity = ""
407             elif self.min:
408                 quantity = "%d to %d " % (self.min, self.max)
409             else:
410                 quantity = "up to %d " % self.max
411
412             if self.value:
413                 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
414             else:
415                 if keyName.endswith('s'):
416                     plural = keyName + "es"
417                 else:
418                     plural = keyName + "s"
419                 return "set of %s%s" % (quantity, plural)
420
421     def constraintsToEnglish(self, escapeLiteral=returnUnchanged):
422         s = ""
423
424         constraints = []
425         keyConstraints = self.key.constraintsToEnglish(escapeLiteral)
426         if keyConstraints:
427             if self.value:
428                 constraints += ['key ' + keyConstraints]
429             else:
430                 constraints += [keyConstraints]
431
432         if self.value:
433             valueConstraints = self.value.constraintsToEnglish(escapeLiteral)
434             if valueConstraints:
435                 constraints += ['value ' + valueConstraints]
436
437         return ', '.join(constraints)
438                 
439     def cDeclComment(self):
440         if self.min == 1 and self.max == 1 and self.key.type == "string":
441             return "\t/* Always nonnull. */"
442         else:
443             return ""
444
445     def cInitType(self, indent, var):
446         initKey = self.key.cInitBaseType(indent, "%s.key" % var)
447         if self.value:
448             initValue = self.value.cInitBaseType(indent, "%s.value" % var)
449         else:
450             initValue = ('%sovsdb_base_type_init(&%s.value, '
451                          'OVSDB_TYPE_VOID);' % (indent, var))
452         initMin = "%s%s.n_min = %s;" % (indent, var, self.min)
453         if self.max == "unlimited":
454             max = "UINT_MAX"
455         else:
456             max = self.max
457         initMax = "%s%s.n_max = %s;" % (indent, var, max)
458         return "\n".join((initKey, initValue, initMin, initMax))
459
460 class Datum:
461     def __init__(self, type, values):
462         self.type = type
463         self.values = values
464
465     @staticmethod
466     def fromJson(type_, json):
467         if not type_.value:
468             if len(json) == 2 and json[0] == "set":
469                 values = []
470                 for atomJson in json[1]:
471                     values += [Atom.fromJson(type_.key, atomJson)]
472             else:
473                 values = [Atom.fromJson(type_.key, json)]
474         else:
475             if len(json) != 2 or json[0] != "map":
476                 raise Error("%s is not valid JSON for a map" % json)
477             values = []
478             for pairJson in json[1]:
479                 values += [(Atom.fromJson(type_.key, pairJson[0]),
480                             Atom.fromJson(type_.value, pairJson[1]))]
481         return Datum(type_, values)
482
483     def cInitDatum(self, var):
484         if len(self.values) == 0:
485             return ["ovsdb_datum_init_empty(%s);" % var]
486
487         s = ["%s->n = %d;" % (var, len(self.values))]
488         s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
489               % (var, len(self.values), var)]
490
491         for i in range(len(self.values)):
492             key = self.values[i]
493             if self.type.value:
494                 key = key[0]
495             s += key.cInitAtom("%s->keys[%d]" % (var, i))
496         
497         if self.type.value:
498             s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
499                   % (var, len(self.values), var)]
500             for i in range(len(self.values)):
501                 value = self.values[i][1]
502                 s += key.cInitAtom("%s->values[%d]" % (var, i))
503         else:
504             s += ["%s->values = NULL;" % var]
505
506         if len(self.values) > 1:
507             s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
508                   % (var, self.type.key.upper())]
509
510         return s