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())
419 if self.type.is_map():
420 return [[k.value, v.value] for k, v in six.iteritems(self.values)]
422 return [k.value for k in six.iterkeys(self.values)]
425 return dict(self.values)
428 if len(self.values) == 1:
429 if self.type.is_map():
430 k, v = next(six.iteritems(self.values))
431 return [k.value, v.value]
433 return next(six.iterkeys(self.values)).value
437 def to_python(self, uuid_to_row):
438 """Returns this datum's value converted into a natural Python
439 representation of this datum's type, according to the following
442 - If the type has exactly one value and it is not a map (that is,
443 self.type.is_scalar() returns True), then the value is:
445 * An int or long, for an integer column.
447 * An int or long or float, for a real column.
449 * A bool, for a boolean column.
451 * A str or unicode object, for a string column.
453 * A uuid.UUID object, for a UUID column without a ref_table.
455 * An object represented the referenced row, for a UUID column with
456 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
459 If some error occurs (e.g. the database server's idea of the column
460 is different from the IDL's idea), then the default value for the
461 scalar type is used (see Atom.default()).
463 - Otherwise, if the type is not a map, then the value is a Python list
464 whose elements have the types described above.
466 - Otherwise, the type is a map, and the value is a Python dict that
467 maps from key to value, with key and value types determined as
470 'uuid_to_row' must be a function that takes a value and an
471 ovs.db.types.BaseType and translates UUIDs into row objects."""
472 if self.type.is_scalar():
473 value = uuid_to_row(self.as_scalar(), self.type.key)
475 return self.type.key.default()
478 elif self.type.is_map():
480 for k, v in six.iteritems(self.values):
481 dk = uuid_to_row(k.value, self.type.key)
482 dv = uuid_to_row(v.value, self.type.value)
483 if dk is not None and dv is not None:
488 for k in self.values:
489 dk = uuid_to_row(k.value, self.type.key)
495 def from_python(type_, value, row_to_uuid):
496 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
497 new datum's value is taken from 'value', which must take the form
498 described as a valid return value from Datum.to_python() for 'type'.
500 Each scalar value within 'value' is initially passed through
501 'row_to_uuid', which should convert objects that represent rows (if
502 any) into uuid.UUID objects and return other data unchanged.
504 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
507 if isinstance(value, dict):
508 for k, v in six.iteritems(value):
509 ka = Atom.from_python(type_.key, row_to_uuid(k))
510 va = Atom.from_python(type_.value, row_to_uuid(v))
512 elif isinstance(value, (list, tuple)):
514 ka = Atom.from_python(type_.key, row_to_uuid(k))
517 ka = Atom.from_python(type_.key, row_to_uuid(value))
520 datum = Datum(type_, d)
521 datum.check_constraints()
522 if not datum.conforms_to_type():
523 raise error.Error("%d values when type requires between %d and %d"
524 % (len(d), type_.n_min, type_.n_max))
528 def __getitem__(self, key):
529 if not isinstance(key, Atom):
531 if not self.type.is_map():
533 elif key not in self.values:
536 return self.values[key].value
538 def get(self, key, default=None):
539 if not isinstance(key, Atom):
541 if key in self.values:
542 return self.values[key].value
547 return self.to_string()
549 def conforms_to_type(self):
551 return self.type.n_min <= n <= self.type.n_max
553 def cInitDatum(self, var):
554 if len(self.values) == 0:
555 return ["ovsdb_datum_init_empty(%s);" % var]
557 s = ["%s->n = %d;" % (var, len(self.values))]
558 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
559 % (var, len(self.values), var)]
561 for i, key in enumerate(sorted(self.values)):
562 s += key.cInitAtom("%s->keys[%d]" % (var, i))
565 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
566 % (var, len(self.values), var)]
567 for i, (key, value) in enumerate(sorted(self.values.items())):
568 s += value.cInitAtom("%s->values[%d]" % (var, i))
570 s += ["%s->values = NULL;" % var]
572 if len(self.values) > 1:
573 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
574 % (var, self.type.key.type.to_string().upper())]