1 # Copyright (c) 2009, 2010, 2011, 2014 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.
21 import ovs.socket_util
27 from ovs.db import error
31 class ConstraintViolation(error.Error):
32 def __init__(self, msg, json=None):
33 error.Error.__init__(self, msg, json, tag="constraint violation")
36 def escapeCString(src):
57 dst.append('\\%03o' % ord(c))
63 def returnUnchanged(x):
68 def __init__(self, type_, value=None):
73 self.value = type_.default_atom()
75 def __cmp__(self, other):
76 if not isinstance(other, Atom) or self.type != other.type:
78 elif self.value < other.value:
80 elif self.value > other.value:
86 return hash(self.value)
90 """Returns the default value for the given type_, which must be an
91 instance of ovs.db.types.AtomicType.
93 The default value for each atomic type is;
95 - 0, for integer or real atoms.
97 - False, for a boolean atom.
99 - "", for a string atom.
101 - The all-zeros UUID, for a UUID atom."""
104 def is_default(self):
105 return self == self.default(self.type)
108 def from_json(base, json, symtab=None):
110 json = ovs.db.parser.float_to_int(json)
111 real_types = list(six.integer_types)
112 real_types.extend([float])
113 real_types = tuple(real_types)
114 if ((type_ == ovs.db.types.IntegerType
115 and isinstance(json, six.integer_types))
116 or (type_ == ovs.db.types.RealType
117 and isinstance(json, real_types))
118 or (type_ == ovs.db.types.BooleanType and isinstance(json, bool))
119 or (type_ == ovs.db.types.StringType
120 and isinstance(json, six.string_types))):
121 atom = Atom(type_, json)
122 elif type_ == ovs.db.types.UuidType:
123 atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
125 raise error.Error("expected %s" % type_.to_string(), json)
126 atom.check_constraints(base)
130 def from_python(base, value):
131 value = ovs.db.parser.float_to_int(value)
132 if isinstance(value, base.type.python_types):
133 atom = Atom(base.type, value)
135 raise error.Error("expected %s, got %s" % (base.type, type(value)))
136 atom.check_constraints(base)
139 def check_constraints(self, base):
140 """Checks whether 'atom' meets the constraints (if any) defined in
141 'base' and raises an ovs.db.error.Error if any constraint is violated.
143 'base' and 'atom' must have the same type.
144 Checking UUID constraints is deferred to transaction commit time, so
145 this function does nothing for UUID constraints."""
146 assert base.type == self.type
147 if base.enum is not None and self not in base.enum:
148 raise ConstraintViolation(
149 "%s is not one of the allowed values (%s)"
150 % (self.to_string(), base.enum.to_string()))
151 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
152 if ((base.min is None or self.value >= base.min) and
153 (base.max is None or self.value <= base.max)):
155 elif base.min is not None and base.max is not None:
156 raise ConstraintViolation(
157 "%s is not in the valid range %.15g to %.15g (inclusive)"
158 % (self.to_string(), base.min, base.max))
159 elif base.min is not None:
160 raise ConstraintViolation(
161 "%s is less than minimum allowed value %.15g"
162 % (self.to_string(), base.min))
164 raise ConstraintViolation(
165 "%s is greater than maximum allowed value %.15g"
166 % (self.to_string(), base.max))
167 elif base.type == ovs.db.types.StringType:
168 # XXX The C version validates that the string is valid UTF-8 here.
169 # Do we need to do that in Python too?
172 if length < base.min_length:
173 raise ConstraintViolation(
174 '"%s" length %d is less than minimum allowed length %d'
175 % (s, length, base.min_length))
176 elif length > base.max_length:
177 raise ConstraintViolation(
178 '"%s" length %d is greater than maximum allowed '
179 'length %d' % (s, length, base.max_length))
182 if self.type == ovs.db.types.UuidType:
183 return ovs.ovsuuid.to_json(self.value)
187 def cInitAtom(self, var):
188 if self.type == ovs.db.types.IntegerType:
189 return ['%s.integer = %d;' % (var, self.value)]
190 elif self.type == ovs.db.types.RealType:
191 return ['%s.real = %.15g;' % (var, self.value)]
192 elif self.type == ovs.db.types.BooleanType:
194 return ['%s.boolean = true;']
196 return ['%s.boolean = false;']
197 elif self.type == ovs.db.types.StringType:
198 return ['%s.string = xstrdup("%s");'
199 % (var, escapeCString(self.value))]
200 elif self.type == ovs.db.types.UuidType:
201 return ovs.ovsuuid.to_c_assignment(self.value, var)
203 def toEnglish(self, escapeLiteral=returnUnchanged):
204 if self.type == ovs.db.types.IntegerType:
205 return '%d' % self.value
206 elif self.type == ovs.db.types.RealType:
207 return '%.15g' % self.value
208 elif self.type == ovs.db.types.BooleanType:
213 elif self.type == ovs.db.types.StringType:
214 return escapeLiteral(self.value)
215 elif self.type == ovs.db.types.UuidType:
216 return self.value.value
218 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
221 def __string_needs_quotes(s):
222 return Atom.__need_quotes_re.match(s)
225 if self.type == ovs.db.types.IntegerType:
226 return '%d' % self.value
227 elif self.type == ovs.db.types.RealType:
228 return '%.15g' % self.value
229 elif self.type == ovs.db.types.BooleanType:
234 elif self.type == ovs.db.types.StringType:
235 if Atom.__string_needs_quotes(self.value):
236 return ovs.json.to_string(self.value)
239 elif self.type == ovs.db.types.UuidType:
240 return str(self.value)
244 if isinstance(x, six.integer_types):
245 t = ovs.db.types.IntegerType
246 elif isinstance(x, float):
247 t = ovs.db.types.RealType
248 elif isinstance(x, bool):
249 t = ovs.db.types.BooleanType
250 elif isinstance(x, six.string_types):
251 t = ovs.db.types.StringType
252 elif isinstance(x, uuid):
253 t = ovs.db.types.UuidType
260 def __init__(self, type_, values={}):
264 def __cmp__(self, other):
265 if not isinstance(other, Datum):
266 return NotImplemented
267 elif self.values < other.values:
269 elif self.values > other.values:
276 def __contains__(self, item):
277 return item in self.values
280 return Datum(self.type, dict(self.values))
287 values = {type_.key.default(): type_.value.default()}
289 values = {type_.key.default(): None}
290 return Datum(type_, values)
292 def is_default(self):
293 return self == Datum.default(self.type)
295 def check_constraints(self):
296 """Checks that each of the atoms in 'datum' conforms to the constraints
297 specified by its 'type' and raises an ovs.db.error.Error.
299 This function is not commonly useful because the most ordinary way to
300 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
301 which check constraints themselves."""
302 for keyAtom, valueAtom in six.iteritems(self.values):
303 keyAtom.check_constraints(self.type.key)
304 if valueAtom is not None:
305 valueAtom.check_constraints(self.type.value)
308 def from_json(type_, json, symtab=None):
309 """Parses 'json' as a datum of the type described by 'type'. If
310 successful, returns a new datum. On failure, raises an
313 Violations of constraints expressed by 'type' are treated as errors.
315 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
316 Refer to RFC 7047 for information about this, and for the syntax
317 that this function accepts."""
318 is_map = type_.is_map()
320 (type(json) == list and len(json) > 0 and json[0] == "set")):
326 inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
329 if n < type_.n_min or n > type_.n_max:
330 raise error.Error("%s must have %d to %d members but %d are "
331 "present" % (class_, type_.n_min,
336 for element in inner:
338 key, value = ovs.db.parser.parse_json_pair(element)
339 keyAtom = Atom.from_json(type_.key, key, symtab)
340 valueAtom = Atom.from_json(type_.value, value, symtab)
342 keyAtom = Atom.from_json(type_.key, element, symtab)
345 if keyAtom in values:
347 raise error.Error("map contains duplicate key")
349 raise error.Error("set contains duplicate")
351 values[keyAtom] = valueAtom
353 return Datum(type_, values)
355 keyAtom = Atom.from_json(type_.key, json, symtab)
356 return Datum(type_, {keyAtom: None})
359 if self.type.is_map():
360 return ["map", [[k.to_json(), v.to_json()]
361 for k, v in sorted(self.values.items())]]
362 elif len(self.values) == 1:
363 key = next(six.iterkeys(self.values))
366 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
370 if self.type.n_max > 1 or len(self.values) == 0:
371 if self.type.is_map():
382 for i, key in enumerate(sorted(self.values)):
386 s.append(key.to_string())
387 if self.type.is_map():
389 s.append(self.values[key].to_string())
396 if self.type.is_map():
397 return [[k.value, v.value] for k, v in six.iteritems(self.values)]
399 return [k.value for k in six.iterkeys(self.values)]
402 return dict(self.values)
405 if len(self.values) == 1:
406 if self.type.is_map():
407 k, v = next(six.iteritems(self.values))
408 return [k.value, v.value]
410 return next(six.iterkeys(self.values)).value
414 def to_python(self, uuid_to_row):
415 """Returns this datum's value converted into a natural Python
416 representation of this datum's type, according to the following
419 - If the type has exactly one value and it is not a map (that is,
420 self.type.is_scalar() returns True), then the value is:
422 * An int or long, for an integer column.
424 * An int or long or float, for a real column.
426 * A bool, for a boolean column.
428 * A str or unicode object, for a string column.
430 * A uuid.UUID object, for a UUID column without a ref_table.
432 * An object represented the referenced row, for a UUID column with
433 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
436 If some error occurs (e.g. the database server's idea of the column
437 is different from the IDL's idea), then the default value for the
438 scalar type is used (see Atom.default()).
440 - Otherwise, if the type is not a map, then the value is a Python list
441 whose elements have the types described above.
443 - Otherwise, the type is a map, and the value is a Python dict that
444 maps from key to value, with key and value types determined as
447 'uuid_to_row' must be a function that takes a value and an
448 ovs.db.types.BaseType and translates UUIDs into row objects."""
449 if self.type.is_scalar():
450 value = uuid_to_row(self.as_scalar(), self.type.key)
452 return self.type.key.default()
455 elif self.type.is_map():
457 for k, v in six.iteritems(self.values):
458 dk = uuid_to_row(k.value, self.type.key)
459 dv = uuid_to_row(v.value, self.type.value)
460 if dk is not None and dv is not None:
465 for k in self.values:
466 dk = uuid_to_row(k.value, self.type.key)
472 def from_python(type_, value, row_to_uuid):
473 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
474 new datum's value is taken from 'value', which must take the form
475 described as a valid return value from Datum.to_python() for 'type'.
477 Each scalar value within 'value' is initially passed through
478 'row_to_uuid', which should convert objects that represent rows (if
479 any) into uuid.UUID objects and return other data unchanged.
481 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
484 if type(value) == dict:
485 for k, v in six.iteritems(value):
486 ka = Atom.from_python(type_.key, row_to_uuid(k))
487 va = Atom.from_python(type_.value, row_to_uuid(v))
489 elif type(value) in (list, tuple):
491 ka = Atom.from_python(type_.key, row_to_uuid(k))
494 ka = Atom.from_python(type_.key, row_to_uuid(value))
497 datum = Datum(type_, d)
498 datum.check_constraints()
499 if not datum.conforms_to_type():
500 raise error.Error("%d values when type requires between %d and %d"
501 % (len(d), type_.n_min, type_.n_max))
505 def __getitem__(self, key):
506 if not isinstance(key, Atom):
508 if not self.type.is_map():
510 elif key not in self.values:
513 return self.values[key].value
515 def get(self, key, default=None):
516 if not isinstance(key, Atom):
518 if key in self.values:
519 return self.values[key].value
524 return self.to_string()
526 def conforms_to_type(self):
528 return self.type.n_min <= n <= self.type.n_max
530 def cInitDatum(self, var):
531 if len(self.values) == 0:
532 return ["ovsdb_datum_init_empty(%s);" % var]
534 s = ["%s->n = %d;" % (var, len(self.values))]
535 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
536 % (var, len(self.values), var)]
538 for i, key in enumerate(sorted(self.values)):
539 s += key.cInitAtom("%s->keys[%d]" % (var, i))
542 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
543 % (var, len(self.values), var)]
544 for i, (key, value) in enumerate(sorted(self.values.items())):
545 s += value.cInitAtom("%s->values[%d]" % (var, i))
547 s += ["%s->values = NULL;" % var]
549 if len(self.values) > 1:
550 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
551 % (var, self.type.key.type.to_string().upper())]