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 if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
112 or (type_ == ovs.db.types.RealType
113 and type(json) in [int, long, float])
114 or (type_ == ovs.db.types.BooleanType and type(json) == bool)
115 or (type_ == ovs.db.types.StringType
116 and type(json) in [str, unicode])):
117 atom = Atom(type_, json)
118 elif type_ == ovs.db.types.UuidType:
119 atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
121 raise error.Error("expected %s" % type_.to_string(), json)
122 atom.check_constraints(base)
126 def from_python(base, value):
127 value = ovs.db.parser.float_to_int(value)
128 if type(value) in base.type.python_types:
129 atom = Atom(base.type, value)
131 raise error.Error("expected %s, got %s" % (base.type, type(value)))
132 atom.check_constraints(base)
135 def check_constraints(self, base):
136 """Checks whether 'atom' meets the constraints (if any) defined in
137 'base' and raises an ovs.db.error.Error if any constraint is violated.
139 'base' and 'atom' must have the same type.
140 Checking UUID constraints is deferred to transaction commit time, so
141 this function does nothing for UUID constraints."""
142 assert base.type == self.type
143 if base.enum is not None and self not in base.enum:
144 raise ConstraintViolation(
145 "%s is not one of the allowed values (%s)"
146 % (self.to_string(), base.enum.to_string()))
147 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
148 if ((base.min is None or self.value >= base.min) and
149 (base.max is None or self.value <= base.max)):
151 elif base.min is not None and base.max is not None:
152 raise ConstraintViolation(
153 "%s is not in the valid range %.15g to %.15g (inclusive)"
154 % (self.to_string(), base.min, base.max))
155 elif base.min is not None:
156 raise ConstraintViolation(
157 "%s is less than minimum allowed value %.15g"
158 % (self.to_string(), base.min))
160 raise ConstraintViolation(
161 "%s is greater than maximum allowed value %.15g"
162 % (self.to_string(), base.max))
163 elif base.type == ovs.db.types.StringType:
164 # XXX The C version validates that the string is valid UTF-8 here.
165 # Do we need to do that in Python too?
168 if length < base.min_length:
169 raise ConstraintViolation(
170 '"%s" length %d is less than minimum allowed length %d'
171 % (s, length, base.min_length))
172 elif length > base.max_length:
173 raise ConstraintViolation(
174 '"%s" length %d is greater than maximum allowed '
175 'length %d' % (s, length, base.max_length))
178 if self.type == ovs.db.types.UuidType:
179 return ovs.ovsuuid.to_json(self.value)
183 def cInitAtom(self, var):
184 if self.type == ovs.db.types.IntegerType:
185 return ['%s.integer = %d;' % (var, self.value)]
186 elif self.type == ovs.db.types.RealType:
187 return ['%s.real = %.15g;' % (var, self.value)]
188 elif self.type == ovs.db.types.BooleanType:
190 return ['%s.boolean = true;']
192 return ['%s.boolean = false;']
193 elif self.type == ovs.db.types.StringType:
194 return ['%s.string = xstrdup("%s");'
195 % (var, escapeCString(self.value))]
196 elif self.type == ovs.db.types.UuidType:
197 return ovs.ovsuuid.to_c_assignment(self.value, var)
199 def toEnglish(self, escapeLiteral=returnUnchanged):
200 if self.type == ovs.db.types.IntegerType:
201 return '%d' % self.value
202 elif self.type == ovs.db.types.RealType:
203 return '%.15g' % self.value
204 elif self.type == ovs.db.types.BooleanType:
209 elif self.type == ovs.db.types.StringType:
210 return escapeLiteral(self.value)
211 elif self.type == ovs.db.types.UuidType:
212 return self.value.value
214 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
217 def __string_needs_quotes(s):
218 return Atom.__need_quotes_re.match(s)
221 if self.type == ovs.db.types.IntegerType:
222 return '%d' % self.value
223 elif self.type == ovs.db.types.RealType:
224 return '%.15g' % self.value
225 elif self.type == ovs.db.types.BooleanType:
230 elif self.type == ovs.db.types.StringType:
231 if Atom.__string_needs_quotes(self.value):
232 return ovs.json.to_string(self.value)
235 elif self.type == ovs.db.types.UuidType:
236 return str(self.value)
240 if type(x) in [int, long]:
241 t = ovs.db.types.IntegerType
242 elif type(x) == float:
243 t = ovs.db.types.RealType
244 elif x in [False, True]:
245 t = ovs.db.types.BooleanType
246 elif type(x) in [str, unicode]:
247 t = ovs.db.types.StringType
248 elif isinstance(x, uuid):
249 t = ovs.db.types.UuidType
256 def __init__(self, type_, values={}):
260 def __cmp__(self, other):
261 if not isinstance(other, Datum):
262 return NotImplemented
263 elif self.values < other.values:
265 elif self.values > other.values:
272 def __contains__(self, item):
273 return item in self.values
276 return Datum(self.type, dict(self.values))
283 values = {type_.key.default(): type_.value.default()}
285 values = {type_.key.default(): None}
286 return Datum(type_, values)
288 def is_default(self):
289 return self == Datum.default(self.type)
291 def check_constraints(self):
292 """Checks that each of the atoms in 'datum' conforms to the constraints
293 specified by its 'type' and raises an ovs.db.error.Error.
295 This function is not commonly useful because the most ordinary way to
296 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
297 which check constraints themselves."""
298 for keyAtom, valueAtom in six.iteritems(self.values):
299 keyAtom.check_constraints(self.type.key)
300 if valueAtom is not None:
301 valueAtom.check_constraints(self.type.value)
304 def from_json(type_, json, symtab=None):
305 """Parses 'json' as a datum of the type described by 'type'. If
306 successful, returns a new datum. On failure, raises an
309 Violations of constraints expressed by 'type' are treated as errors.
311 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
312 Refer to RFC 7047 for information about this, and for the syntax
313 that this function accepts."""
314 is_map = type_.is_map()
316 (type(json) == list and len(json) > 0 and json[0] == "set")):
322 inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
325 if n < type_.n_min or n > type_.n_max:
326 raise error.Error("%s must have %d to %d members but %d are "
327 "present" % (class_, type_.n_min,
332 for element in inner:
334 key, value = ovs.db.parser.parse_json_pair(element)
335 keyAtom = Atom.from_json(type_.key, key, symtab)
336 valueAtom = Atom.from_json(type_.value, value, symtab)
338 keyAtom = Atom.from_json(type_.key, element, symtab)
341 if keyAtom in values:
343 raise error.Error("map contains duplicate key")
345 raise error.Error("set contains duplicate")
347 values[keyAtom] = valueAtom
349 return Datum(type_, values)
351 keyAtom = Atom.from_json(type_.key, json, symtab)
352 return Datum(type_, {keyAtom: None})
355 if self.type.is_map():
356 return ["map", [[k.to_json(), v.to_json()]
357 for k, v in sorted(self.values.items())]]
358 elif len(self.values) == 1:
359 key = next(six.iterkeys(self.values))
362 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
366 if self.type.n_max > 1 or len(self.values) == 0:
367 if self.type.is_map():
378 for i, key in enumerate(sorted(self.values)):
382 s.append(key.to_string())
383 if self.type.is_map():
385 s.append(self.values[key].to_string())
392 if self.type.is_map():
393 return [[k.value, v.value] for k, v in six.iteritems(self.values)]
395 return [k.value for k in six.iterkeys(self.values)]
398 return dict(self.values)
401 if len(self.values) == 1:
402 if self.type.is_map():
403 k, v = next(six.iteritems(self.values))
404 return [k.value, v.value]
406 return next(six.iterkeys(self.values)).value
410 def to_python(self, uuid_to_row):
411 """Returns this datum's value converted into a natural Python
412 representation of this datum's type, according to the following
415 - If the type has exactly one value and it is not a map (that is,
416 self.type.is_scalar() returns True), then the value is:
418 * An int or long, for an integer column.
420 * An int or long or float, for a real column.
422 * A bool, for a boolean column.
424 * A str or unicode object, for a string column.
426 * A uuid.UUID object, for a UUID column without a ref_table.
428 * An object represented the referenced row, for a UUID column with
429 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
432 If some error occurs (e.g. the database server's idea of the column
433 is different from the IDL's idea), then the default value for the
434 scalar type is used (see Atom.default()).
436 - Otherwise, if the type is not a map, then the value is a Python list
437 whose elements have the types described above.
439 - Otherwise, the type is a map, and the value is a Python dict that
440 maps from key to value, with key and value types determined as
443 'uuid_to_row' must be a function that takes a value and an
444 ovs.db.types.BaseType and translates UUIDs into row objects."""
445 if self.type.is_scalar():
446 value = uuid_to_row(self.as_scalar(), self.type.key)
448 return self.type.key.default()
451 elif self.type.is_map():
453 for k, v in six.iteritems(self.values):
454 dk = uuid_to_row(k.value, self.type.key)
455 dv = uuid_to_row(v.value, self.type.value)
456 if dk is not None and dv is not None:
461 for k in self.values:
462 dk = uuid_to_row(k.value, self.type.key)
468 def from_python(type_, value, row_to_uuid):
469 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
470 new datum's value is taken from 'value', which must take the form
471 described as a valid return value from Datum.to_python() for 'type'.
473 Each scalar value within 'value' is initially passed through
474 'row_to_uuid', which should convert objects that represent rows (if
475 any) into uuid.UUID objects and return other data unchanged.
477 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
480 if type(value) == dict:
481 for k, v in six.iteritems(value):
482 ka = Atom.from_python(type_.key, row_to_uuid(k))
483 va = Atom.from_python(type_.value, row_to_uuid(v))
485 elif type(value) in (list, tuple):
487 ka = Atom.from_python(type_.key, row_to_uuid(k))
490 ka = Atom.from_python(type_.key, row_to_uuid(value))
493 datum = Datum(type_, d)
494 datum.check_constraints()
495 if not datum.conforms_to_type():
496 raise error.Error("%d values when type requires between %d and %d"
497 % (len(d), type_.n_min, type_.n_max))
501 def __getitem__(self, key):
502 if not isinstance(key, Atom):
504 if not self.type.is_map():
506 elif key not in self.values:
509 return self.values[key].value
511 def get(self, key, default=None):
512 if not isinstance(key, Atom):
514 if key in self.values:
515 return self.values[key].value
520 return self.to_string()
522 def conforms_to_type(self):
524 return self.type.n_min <= n <= self.type.n_max
526 def cInitDatum(self, var):
527 if len(self.values) == 0:
528 return ["ovsdb_datum_init_empty(%s);" % var]
530 s = ["%s->n = %d;" % (var, len(self.values))]
531 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
532 % (var, len(self.values), var)]
534 for i, key in enumerate(sorted(self.values)):
535 s += key.cInitAtom("%s->keys[%d]" % (var, i))
538 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
539 % (var, len(self.values), var)]
540 for i, (key, value) in enumerate(sorted(self.values.items())):
541 s += value.cInitAtom("%s->values[%d]" % (var, i))
543 s += ["%s->values = NULL;" % var]
545 if len(self.values) > 1:
546 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
547 % (var, self.type.key.type.to_string().upper())]