]> git.proxmox.com Git - ovs.git/blob - ovsdb/OVSDB.py
reconnect: Refactor tests to use common macro.
[ovs.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