1 # Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
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:
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 from ovs.db import error
26 class AtomicType(object):
27 def __init__(self, name, default, python_types):
29 self.default = default
30 self.python_types = python_types
35 for atomic_type in ATOMIC_TYPES:
36 if s == atomic_type.name:
38 raise error.Error('"%s" is not an atomic-type' % s, s)
42 if type(json) not in [str, unicode]:
43 raise error.Error("atomic-type expected", json)
45 return AtomicType.from_string(json)
56 def default_atom(self):
57 return ovs.db.data.Atom(self, self.default)
59 REAL_PYTHON_TYPES = list(six.integer_types)
60 REAL_PYTHON_TYPES.extend([float])
61 REAL_PYTHON_TYPES = tuple(REAL_PYTHON_TYPES)
63 VoidType = AtomicType("void", None, ())
64 IntegerType = AtomicType("integer", 0, six.integer_types)
65 RealType = AtomicType("real", 0.0, REAL_PYTHON_TYPES)
66 BooleanType = AtomicType("boolean", False, (bool,))
67 StringType = AtomicType("string", "", (str, unicode))
68 UuidType = AtomicType("uuid", ovs.ovsuuid.zero(), (uuid.UUID,))
70 ATOMIC_TYPES = [VoidType, IntegerType, RealType, BooleanType, StringType,
74 def escapeCString(src):
95 dst += '\\%03o' % ord(c)
102 """Returns integer x formatted in decimal with thousands set off by
104 return _commafy("%d" % x)
108 if s.startswith('-'):
109 return '-' + _commafy(s[1:])
113 return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
116 def returnUnchanged(x):
120 class BaseType(object):
121 def __init__(self, type_, enum=None, min=None, max=None,
122 min_length=0, max_length=sys.maxint, ref_table_name=None):
123 assert isinstance(type_, AtomicType)
128 self.min_length = min_length
129 self.max_length = max_length
130 self.ref_table_name = ref_table_name
132 self.ref_type = 'strong'
135 self.ref_table = None
138 return ovs.db.data.Atom.default(self.type)
140 def __eq__(self, other):
141 if not isinstance(other, BaseType):
142 return NotImplemented
143 return (self.type == other.type and self.enum == other.enum and
144 self.min == other.min and self.max == other.max and
145 self.min_length == other.min_length and
146 self.max_length == other.max_length and
147 self.ref_table_name == other.ref_table_name)
149 def __ne__(self, other):
150 if not isinstance(other, BaseType):
151 return NotImplemented
153 return not (self == other)
156 def __parse_uint(parser, name, default):
157 value = parser.get_optional(name, six.integer_types)
161 max_value = 2 ** 32 - 1
162 if not (0 <= value <= max_value):
163 raise error.Error("%s out of valid range 0 to %d"
164 % (name, max_value), value)
169 if type(json) in [str, unicode]:
170 return BaseType(AtomicType.from_json(json))
172 parser = ovs.db.parser.Parser(json, "ovsdb type")
173 atomic_type = AtomicType.from_json(parser.get("type", [str, unicode]))
175 base = BaseType(atomic_type)
177 enum = parser.get_optional("enum", [])
179 base.enum = ovs.db.data.Datum.from_json(
180 BaseType.get_enum_type(base.type), enum)
181 elif base.type == IntegerType:
182 base.min = parser.get_optional("minInteger", six.integer_types)
183 base.max = parser.get_optional("maxInteger", six.integer_types)
184 if (base.min is not None and base.max is not None
185 and base.min > base.max):
186 raise error.Error("minInteger exceeds maxInteger", json)
187 elif base.type == RealType:
188 base.min = parser.get_optional("minReal", REAL_PYTHON_TYPES)
189 base.max = parser.get_optional("maxReal", REAL_PYTHON_TYPES)
190 if (base.min is not None and base.max is not None
191 and base.min > base.max):
192 raise error.Error("minReal exceeds maxReal", json)
193 elif base.type == StringType:
194 base.min_length = BaseType.__parse_uint(parser, "minLength", 0)
195 base.max_length = BaseType.__parse_uint(parser, "maxLength",
197 if base.min_length > base.max_length:
198 raise error.Error("minLength exceeds maxLength", json)
199 elif base.type == UuidType:
200 base.ref_table_name = parser.get_optional("refTable", ['id'])
201 if base.ref_table_name:
202 base.ref_type = parser.get_optional("refType", [str, unicode],
204 if base.ref_type not in ['strong', 'weak']:
205 raise error.Error('refType must be "strong" or "weak" '
206 '(not "%s")' % base.ref_type)
212 if not self.has_constraints():
213 return self.type.to_json()
215 json = {'type': self.type.to_json()}
218 json['enum'] = self.enum.to_json()
220 if self.type == IntegerType:
221 if self.min is not None:
222 json['minInteger'] = self.min
223 if self.max is not None:
224 json['maxInteger'] = self.max
225 elif self.type == RealType:
226 if self.min is not None:
227 json['minReal'] = self.min
228 if self.max is not None:
229 json['maxReal'] = self.max
230 elif self.type == StringType:
231 if self.min_length != 0:
232 json['minLength'] = self.min_length
233 if self.max_length != sys.maxint:
234 json['maxLength'] = self.max_length
235 elif self.type == UuidType:
236 if self.ref_table_name:
237 json['refTable'] = self.ref_table_name
238 if self.ref_type != 'strong':
239 json['refType'] = self.ref_type
243 base = BaseType(self.type, self.enum.copy(), self.min, self.max,
244 self.min_length, self.max_length, self.ref_table_name)
245 base.ref_table = self.ref_table
249 if self.type in (VoidType, BooleanType, UuidType):
251 elif self.type in (IntegerType, RealType):
252 return self.min is None or self.max is None or self.min <= self.max
253 elif self.type == StringType:
254 return self.min_length <= self.max_length
258 def has_constraints(self):
259 return (self.enum is not None or self.min is not None or
260 self.max is not None or
261 self.min_length != 0 or self.max_length != sys.maxint or
262 self.ref_table_name is not None)
264 def without_constraints(self):
265 return BaseType(self.type)
268 def get_enum_type(atomic_type):
269 """Returns the type of the 'enum' member for a BaseType whose
270 'type' is 'atomic_type'."""
271 return Type(BaseType(atomic_type), None, 1, sys.maxint)
274 return self.type == UuidType and self.ref_table_name is not None
276 def is_strong_ref(self):
277 return self.is_ref() and self.ref_type == 'strong'
279 def is_weak_ref(self):
280 return self.is_ref() and self.ref_type == 'weak'
282 def toEnglish(self, escapeLiteral=returnUnchanged):
283 if self.type == UuidType and self.ref_table_name:
284 s = escapeLiteral(self.ref_table_name)
285 if self.ref_type == 'weak':
286 s = "weak reference to " + s
289 return self.type.to_string()
291 def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
292 escapeNumber=returnUnchanged):
294 literals = [value.toEnglish(escapeLiteral)
295 for value in self.enum.values]
296 if len(literals) == 1:
297 english = 'must be %s' % (literals[0])
298 elif len(literals) == 2:
299 english = 'either %s or %s' % (literals[0], literals[1])
301 english = 'one of %s, %s, or %s' % (literals[0],
302 ', '.join(literals[1:-1]),
304 elif self.min is not None and self.max is not None:
305 if self.type == IntegerType:
306 english = 'in range %s to %s' % (
307 escapeNumber(commafy(self.min)),
308 escapeNumber(commafy(self.max)))
310 english = 'in range %s to %s' % (
311 escapeNumber("%g" % self.min),
312 escapeNumber("%g" % self.max))
313 elif self.min is not None:
314 if self.type == IntegerType:
315 english = 'at least %s' % escapeNumber(commafy(self.min))
317 english = 'at least %s' % escapeNumber("%g" % self.min)
318 elif self.max is not None:
319 if self.type == IntegerType:
320 english = 'at most %s' % escapeNumber(commafy(self.max))
322 english = 'at most %s' % escapeNumber("%g" % self.max)
323 elif self.min_length != 0 and self.max_length != sys.maxint:
324 if self.min_length == self.max_length:
325 english = ('exactly %s characters long'
326 % commafy(self.min_length))
328 english = ('between %s and %s characters long'
329 % (commafy(self.min_length),
330 commafy(self.max_length)))
331 elif self.min_length != 0:
332 return 'at least %s characters long' % commafy(self.min_length)
333 elif self.max_length != sys.maxint:
334 english = 'at most %s characters long' % commafy(self.max_length)
340 def toCType(self, prefix):
341 if self.ref_table_name:
342 return "struct %s%s *" % (prefix, self.ref_table_name.lower())
344 return {IntegerType: 'int64_t ',
346 UuidType: 'struct uuid ',
347 BooleanType: 'bool ',
348 StringType: 'char *'}[self.type]
350 def toAtomicType(self):
351 return "OVSDB_TYPE_%s" % self.type.to_string().upper()
353 def copyCValue(self, dst, src):
354 args = {'dst': dst, 'src': src}
355 if self.ref_table_name:
356 return ("%(dst)s = %(src)s->header_.uuid;") % args
357 elif self.type == StringType:
358 return "%(dst)s = xstrdup(%(src)s);" % args
360 return "%(dst)s = %(src)s;" % args
362 def assign_c_value_casting_away_const(self, dst, src):
363 args = {'dst': dst, 'src': src}
364 if self.ref_table_name:
365 return ("%(dst)s = %(src)s->header_.uuid;") % args
366 elif self.type == StringType:
367 return "%(dst)s = CONST_CAST(char *, %(src)s);" % args
369 return "%(dst)s = %(src)s;" % args
371 def initCDefault(self, var, is_optional):
372 if self.ref_table_name:
373 return "%s = NULL;" % var
374 elif self.type == StringType and not is_optional:
375 return '%s = "";' % var
377 pattern = {IntegerType: '%s = 0;',
378 RealType: '%s = 0.0;',
379 UuidType: 'uuid_zero(&%s);',
380 BooleanType: '%s = false;',
381 StringType: '%s = NULL;'}[self.type]
384 def cInitBaseType(self, indent, var):
386 stmts.append('ovsdb_base_type_init(&%s, %s);' % (
387 var, self.toAtomicType()))
389 stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
391 stmts += self.enum.cInitDatum("%s.enum_" % var)
392 if self.type == IntegerType:
393 if self.min is not None:
394 stmts.append('%s.u.integer.min = INT64_C(%d);'
396 if self.max is not None:
397 stmts.append('%s.u.integer.max = INT64_C(%d);'
399 elif self.type == RealType:
400 if self.min is not None:
401 stmts.append('%s.u.real.min = %d;' % (var, self.min))
402 if self.max is not None:
403 stmts.append('%s.u.real.max = %d;' % (var, self.max))
404 elif self.type == StringType:
405 if self.min_length is not None:
406 stmts.append('%s.u.string.minLen = %d;'
407 % (var, self.min_length))
408 if self.max_length != sys.maxint:
409 stmts.append('%s.u.string.maxLen = %d;'
410 % (var, self.max_length))
411 elif self.type == UuidType:
412 if self.ref_table_name is not None:
413 stmts.append('%s.u.uuid.refTableName = "%s";'
414 % (var, escapeCString(self.ref_table_name)))
415 stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
416 % (var, self.ref_type.upper()))
417 return '\n'.join([indent + stmt for stmt in stmts])
424 def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
431 if self.value is None:
434 value = self.value.copy()
435 return Type(self.key.copy(), value, self.n_min, self.n_max)
437 def __eq__(self, other):
438 if not isinstance(other, Type):
439 return NotImplemented
440 return (self.key == other.key and self.value == other.value and
441 self.n_min == other.n_min and self.n_max == other.n_max)
443 def __ne__(self, other):
444 if not isinstance(other, Type):
445 return NotImplemented
447 return not (self == other)
450 return (self.key.type != VoidType and self.key.is_valid() and
451 (self.value is None or
452 (self.value.type != VoidType and self.value.is_valid())) and
453 self.n_min <= 1 <= self.n_max)
456 return self.n_min == 1 and self.n_max == 1 and not self.value
458 def is_optional(self):
459 return self.n_min == 0 and self.n_max == 1
461 def is_composite(self):
462 return self.n_max > 1
465 return self.value is None and (self.n_min != 1 or self.n_max != 1)
468 return self.value is not None
471 return (self.is_map()
472 and self.key.type == StringType
473 and self.value.type == StringType)
475 def is_optional_pointer(self):
476 return (self.is_optional() and not self.value
477 and (self.key.type == StringType or self.key.ref_table_name))
480 def __n_from_json(json, default):
483 elif type(json) == int and 0 <= json <= sys.maxint:
486 raise error.Error("bad min or max value", json)
490 if type(json) in [str, unicode]:
491 return Type(BaseType.from_json(json))
493 parser = ovs.db.parser.Parser(json, "ovsdb type")
494 key_json = parser.get("key", [dict, str, unicode])
495 value_json = parser.get_optional("value", [dict, str, unicode])
496 min_json = parser.get_optional("min", [int])
497 max_json = parser.get_optional("max", [int, str, unicode])
500 key = BaseType.from_json(key_json)
502 value = BaseType.from_json(value_json)
506 n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
508 if max_json == 'unlimited':
511 n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
513 type_ = Type(key, value, n_min, n_max)
514 if not type_.is_valid():
515 raise error.Error("ovsdb type fails constraint checks", json)
519 if self.is_scalar() and not self.key.has_constraints():
520 return self.key.to_json()
522 json = {"key": self.key.to_json()}
523 if self.value is not None:
524 json["value"] = self.value.to_json()
525 if self.n_min != Type.DEFAULT_MIN:
526 json["min"] = self.n_min
527 if self.n_max == sys.maxint:
528 json["max"] = "unlimited"
529 elif self.n_max != Type.DEFAULT_MAX:
530 json["max"] = self.n_max
533 def toEnglish(self, escapeLiteral=returnUnchanged):
534 keyName = self.key.toEnglish(escapeLiteral)
536 valueName = self.value.toEnglish(escapeLiteral)
540 elif self.is_optional():
542 return "optional %s-%s pair" % (keyName, valueName)
544 return "optional %s" % keyName
546 if self.n_max == sys.maxint:
548 quantity = "%s or more " % commafy(self.n_min)
552 quantity = "%s to %s " % (commafy(self.n_min),
555 quantity = "up to %s " % commafy(self.n_max)
558 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
560 if keyName.endswith('s'):
561 plural = keyName + "es"
563 plural = keyName + "s"
564 return "set of %s%s" % (quantity, plural)
566 def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
567 escapeNumber=returnUnchanged):
569 keyConstraints = self.key.constraintsToEnglish(escapeLiteral,
573 constraints.append('key %s' % keyConstraints)
575 constraints.append(keyConstraints)
578 valueConstraints = self.value.constraintsToEnglish(escapeLiteral,
581 constraints.append('value %s' % valueConstraints)
583 return ', '.join(constraints)
585 def cDeclComment(self):
586 if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
587 return "\t/* Always nonnull. */"
591 def cInitType(self, indent, var):
592 initKey = self.key.cInitBaseType(indent, "%s.key" % var)
594 initValue = self.value.cInitBaseType(indent, "%s.value" % var)
596 initValue = ('%sovsdb_base_type_init(&%s.value, '
597 'OVSDB_TYPE_VOID);' % (indent, var))
598 initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
599 if self.n_max == sys.maxint:
603 initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
604 return "\n".join((initKey, initValue, initMin, initMax))