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.
22 import ovs.socket_util
28 from ovs.db import error
32 class ConstraintViolation(error.Error):
33 def __init__(self, msg, json=None):
34 error.Error.__init__(self, msg, json, tag="constraint violation")
37 def escapeCString(src):
58 dst.append('\\%03o' % ord(c))
64 def returnUnchanged(x):
68 @functools.total_ordering
70 def __init__(self, type_, value=None):
75 self.value = type_.default_atom()
77 def __eq__(self, other):
78 if not isinstance(other, Atom) or self.type != other.type:
80 return True if self.value == other.value else False
82 def __lt__(self, other):
83 if not isinstance(other, Atom) or self.type != other.type:
85 return True if self.value < other.value else False
87 def __cmp__(self, other):
88 if not isinstance(other, Atom) or self.type != other.type:
90 elif self.value < other.value:
92 elif self.value > other.value:
98 return hash(self.value)
102 """Returns the default value for the given type_, which must be an
103 instance of ovs.db.types.AtomicType.
105 The default value for each atomic type is;
107 - 0, for integer or real atoms.
109 - False, for a boolean atom.
111 - "", for a string atom.
113 - The all-zeros UUID, for a UUID atom."""
116 def is_default(self):
117 return self == self.default(self.type)
120 def from_json(base, json, symtab=None):
122 json = ovs.db.parser.float_to_int(json)
123 real_types = list(six.integer_types)
124 real_types.extend([float])
125 real_types = tuple(real_types)
126 if ((type_ == ovs.db.types.IntegerType
127 and isinstance(json, six.integer_types))
128 or (type_ == ovs.db.types.RealType
129 and isinstance(json, real_types))
130 or (type_ == ovs.db.types.BooleanType and isinstance(json, bool))
131 or (type_ == ovs.db.types.StringType
132 and isinstance(json, six.string_types))):
133 atom = Atom(type_, json)
134 elif type_ == ovs.db.types.UuidType:
135 atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
137 raise error.Error("expected %s" % type_.to_string(), json)
138 atom.check_constraints(base)
142 def from_python(base, value):
143 value = ovs.db.parser.float_to_int(value)
144 if isinstance(value, base.type.python_types):
145 atom = Atom(base.type, value)
147 raise error.Error("expected %s, got %s" % (base.type, type(value)))
148 atom.check_constraints(base)
151 def check_constraints(self, base):
152 """Checks whether 'atom' meets the constraints (if any) defined in
153 'base' and raises an ovs.db.error.Error if any constraint is violated.
155 'base' and 'atom' must have the same type.
156 Checking UUID constraints is deferred to transaction commit time, so
157 this function does nothing for UUID constraints."""
158 assert base.type == self.type
159 if base.enum is not None and self not in base.enum:
160 raise ConstraintViolation(
161 "%s is not one of the allowed values (%s)"
162 % (self.to_string(), base.enum.to_string()))
163 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
164 if ((base.min is None or self.value >= base.min) and
165 (base.max is None or self.value <= base.max)):
167 elif base.min is not None and base.max is not None:
168 raise ConstraintViolation(
169 "%s is not in the valid range %.15g to %.15g (inclusive)"
170 % (self.to_string(), base.min, base.max))
171 elif base.min is not None:
172 raise ConstraintViolation(
173 "%s is less than minimum allowed value %.15g"
174 % (self.to_string(), base.min))
176 raise ConstraintViolation(
177 "%s is greater than maximum allowed value %.15g"
178 % (self.to_string(), base.max))
179 elif base.type == ovs.db.types.StringType:
180 # XXX The C version validates that the string is valid UTF-8 here.
181 # Do we need to do that in Python too?
184 if length < base.min_length:
185 raise ConstraintViolation(
186 '"%s" length %d is less than minimum allowed length %d'
187 % (s, length, base.min_length))
188 elif length > base.max_length:
189 raise ConstraintViolation(
190 '"%s" length %d is greater than maximum allowed '
191 'length %d' % (s, length, base.max_length))
194 if self.type == ovs.db.types.UuidType:
195 return ovs.ovsuuid.to_json(self.value)
199 def cInitAtom(self, var):
200 if self.type == ovs.db.types.IntegerType:
201 return ['%s.integer = %d;' % (var, self.value)]
202 elif self.type == ovs.db.types.RealType:
203 return ['%s.real = %.15g;' % (var, self.value)]
204 elif self.type == ovs.db.types.BooleanType:
206 return ['%s.boolean = true;']
208 return ['%s.boolean = false;']
209 elif self.type == ovs.db.types.StringType:
210 return ['%s.string = xstrdup("%s");'
211 % (var, escapeCString(self.value))]
212 elif self.type == ovs.db.types.UuidType:
213 return ovs.ovsuuid.to_c_assignment(self.value, var)
215 def toEnglish(self, escapeLiteral=returnUnchanged):
216 if self.type == ovs.db.types.IntegerType:
217 return '%d' % self.value
218 elif self.type == ovs.db.types.RealType:
219 return '%.15g' % self.value
220 elif self.type == ovs.db.types.BooleanType:
225 elif self.type == ovs.db.types.StringType:
226 return escapeLiteral(self.value)
227 elif self.type == ovs.db.types.UuidType:
228 return self.value.value
230 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
233 def __string_needs_quotes(s):
234 return Atom.__need_quotes_re.match(s)
237 if self.type == ovs.db.types.IntegerType:
238 return '%d' % self.value
239 elif self.type == ovs.db.types.RealType:
240 return '%.15g' % self.value
241 elif self.type == ovs.db.types.BooleanType:
246 elif self.type == ovs.db.types.StringType:
247 if Atom.__string_needs_quotes(self.value):
248 return ovs.json.to_string(self.value)
251 elif self.type == ovs.db.types.UuidType:
252 return str(self.value)
256 if isinstance(x, six.integer_types):
257 t = ovs.db.types.IntegerType
258 elif isinstance(x, float):
259 t = ovs.db.types.RealType
260 elif isinstance(x, bool):
261 t = ovs.db.types.BooleanType
262 elif isinstance(x, six.string_types):
263 t = ovs.db.types.StringType
264 elif isinstance(x, uuid):
265 t = ovs.db.types.UuidType
271 @functools.total_ordering
273 def __init__(self, type_, values={}):
277 def __eq__(self, other):
278 if not isinstance(other, Datum):
279 return NotImplemented
280 return True if self.values == other.values else False
282 def __lt__(self, other):
283 if not isinstance(other, Datum):
284 return NotImplemented
285 return True if self.values < other.values else False
287 def __cmp__(self, other):
288 if not isinstance(other, Datum):
289 return NotImplemented
290 elif self.values < other.values:
292 elif self.values > other.values:
299 def __contains__(self, item):
300 return item in self.values
303 return Datum(self.type, dict(self.values))
310 values = {type_.key.default(): type_.value.default()}
312 values = {type_.key.default(): None}
313 return Datum(type_, values)
315 def is_default(self):
316 return self == Datum.default(self.type)
318 def check_constraints(self):
319 """Checks that each of the atoms in 'datum' conforms to the constraints
320 specified by its 'type' and raises an ovs.db.error.Error.
322 This function is not commonly useful because the most ordinary way to
323 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
324 which check constraints themselves."""
325 for keyAtom, valueAtom in six.iteritems(self.values):
326 keyAtom.check_constraints(self.type.key)
327 if valueAtom is not None:
328 valueAtom.check_constraints(self.type.value)
331 def from_json(type_, json, symtab=None):
332 """Parses 'json' as a datum of the type described by 'type'. If
333 successful, returns a new datum. On failure, raises an
336 Violations of constraints expressed by 'type' are treated as errors.
338 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
339 Refer to RFC 7047 for information about this, and for the syntax
340 that this function accepts."""
341 is_map = type_.is_map()
343 (isinstance(json, list) and len(json) > 0 and json[0] == "set")):
349 inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
352 if n < type_.n_min or n > type_.n_max:
353 raise error.Error("%s must have %d to %d members but %d are "
354 "present" % (class_, type_.n_min,
359 for element in inner:
361 key, value = ovs.db.parser.parse_json_pair(element)
362 keyAtom = Atom.from_json(type_.key, key, symtab)
363 valueAtom = Atom.from_json(type_.value, value, symtab)
365 keyAtom = Atom.from_json(type_.key, element, symtab)
368 if keyAtom in values:
370 raise error.Error("map contains duplicate key")
372 raise error.Error("set contains duplicate")
374 values[keyAtom] = valueAtom
376 return Datum(type_, values)
378 keyAtom = Atom.from_json(type_.key, json, symtab)
379 return Datum(type_, {keyAtom: None})
382 if self.type.is_map():
383 return ["map", [[k.to_json(), v.to_json()]
384 for k, v in sorted(self.values.items())]]
385 elif len(self.values) == 1:
386 key = next(six.iterkeys(self.values))
389 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
393 if self.type.n_max > 1 or len(self.values) == 0:
394 if self.type.is_map():
405 for i, key in enumerate(sorted(self.values)):
409 s.append(key.to_string())
410 if self.type.is_map():
412 s.append(self.values[key].to_string())
418 def diff(self, datum):
419 if self.type.n_max > 1 or len(self.values) == 0:
420 for k, v in six.iteritems(datum.values):
421 if k in self.values and v == self.values[k]:
431 if self.type.is_map():
432 return [[k.value, v.value] for k, v in six.iteritems(self.values)]
434 return [k.value for k in six.iterkeys(self.values)]
437 return dict(self.values)
440 if len(self.values) == 1:
441 if self.type.is_map():
442 k, v = next(six.iteritems(self.values))
443 return [k.value, v.value]
445 return next(six.iterkeys(self.values)).value
449 def to_python(self, uuid_to_row):
450 """Returns this datum's value converted into a natural Python
451 representation of this datum's type, according to the following
454 - If the type has exactly one value and it is not a map (that is,
455 self.type.is_scalar() returns True), then the value is:
457 * An int or long, for an integer column.
459 * An int or long or float, for a real column.
461 * A bool, for a boolean column.
463 * A str or unicode object, for a string column.
465 * A uuid.UUID object, for a UUID column without a ref_table.
467 * An object represented the referenced row, for a UUID column with
468 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
471 If some error occurs (e.g. the database server's idea of the column
472 is different from the IDL's idea), then the default value for the
473 scalar type is used (see Atom.default()).
475 - Otherwise, if the type is not a map, then the value is a Python list
476 whose elements have the types described above.
478 - Otherwise, the type is a map, and the value is a Python dict that
479 maps from key to value, with key and value types determined as
482 'uuid_to_row' must be a function that takes a value and an
483 ovs.db.types.BaseType and translates UUIDs into row objects."""
484 if self.type.is_scalar():
485 value = uuid_to_row(self.as_scalar(), self.type.key)
487 return self.type.key.default()
490 elif self.type.is_map():
492 for k, v in six.iteritems(self.values):
493 dk = uuid_to_row(k.value, self.type.key)
494 dv = uuid_to_row(v.value, self.type.value)
495 if dk is not None and dv is not None:
500 for k in self.values:
501 dk = uuid_to_row(k.value, self.type.key)
507 def from_python(type_, value, row_to_uuid):
508 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
509 new datum's value is taken from 'value', which must take the form
510 described as a valid return value from Datum.to_python() for 'type'.
512 Each scalar value within 'value' is initially passed through
513 'row_to_uuid', which should convert objects that represent rows (if
514 any) into uuid.UUID objects and return other data unchanged.
516 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
519 if isinstance(value, dict):
520 for k, v in six.iteritems(value):
521 ka = Atom.from_python(type_.key, row_to_uuid(k))
522 va = Atom.from_python(type_.value, row_to_uuid(v))
524 elif isinstance(value, (list, set, tuple)):
526 ka = Atom.from_python(type_.key, row_to_uuid(k))
529 ka = Atom.from_python(type_.key, row_to_uuid(value))
532 datum = Datum(type_, d)
533 datum.check_constraints()
534 if not datum.conforms_to_type():
535 raise error.Error("%d values when type requires between %d and %d"
536 % (len(d), type_.n_min, type_.n_max))
540 def __getitem__(self, key):
541 if not isinstance(key, Atom):
543 if not self.type.is_map():
545 elif key not in self.values:
548 return self.values[key].value
550 def get(self, key, default=None):
551 if not isinstance(key, Atom):
553 if key in self.values:
554 return self.values[key].value
559 return self.to_string()
561 def conforms_to_type(self):
563 return self.type.n_min <= n <= self.type.n_max
565 def cInitDatum(self, var):
566 if len(self.values) == 0:
567 return ["ovsdb_datum_init_empty(%s);" % var]
569 s = ["%s->n = %d;" % (var, len(self.values))]
570 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
571 % (var, len(self.values), var)]
573 for i, key in enumerate(sorted(self.values)):
574 s += key.cInitAtom("%s->keys[%d]" % (var, i))
577 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
578 % (var, len(self.values), var)]
579 for i, (key, value) in enumerate(sorted(self.values.items())):
580 s += value.cInitAtom("%s->values[%d]" % (var, i))
582 s += ["%s->values = NULL;" % var]
584 if len(self.values) > 1:
585 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
586 % (var, self.type.key.type.to_string().upper())]