1 # Copyright (c) 2009, 2010, 2011 Nicira Networks
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.
19 import ovs.socket_util
25 from ovs.db import error
29 class ConstraintViolation(error.Error):
30 def __init__(self, msg, json=None):
31 error.Error.__init__(self, msg, json, tag="constraint violation")
34 def escapeCString(src):
55 dst.append('\\%03o' % ord(c))
61 def returnUnchanged(x):
66 def __init__(self, type_, value=None):
71 self.value = type_.default_atom()
73 def __cmp__(self, other):
74 if not isinstance(other, Atom) or self.type != other.type:
76 elif self.value < other.value:
78 elif self.value > other.value:
84 return hash(self.value)
88 """Returns the default value for the given type_, which must be an
89 instance of ovs.db.types.AtomicType.
91 The default value for each atomic type is;
93 - 0, for integer or real atoms.
95 - False, for a boolean atom.
97 - "", for a string atom.
99 - The all-zeros UUID, for a UUID atom."""
102 def is_default(self):
103 return self == self.default(self.type)
106 def from_json(base, json, symtab=None):
108 json = ovs.db.parser.float_to_int(json)
109 if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
110 or (type_ == ovs.db.types.RealType
111 and type(json) in [int, long, float])
112 or (type_ == ovs.db.types.BooleanType and type(json) == bool)
113 or (type_ == ovs.db.types.StringType
114 and type(json) in [str, unicode])):
115 atom = Atom(type_, json)
116 elif type_ == ovs.db.types.UuidType:
117 atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
119 raise error.Error("expected %s" % type_.to_string(), json)
120 atom.check_constraints(base)
124 def from_python(base, value):
125 value = ovs.db.parser.float_to_int(value)
126 if type(value) in base.type.python_types:
127 atom = Atom(base.type, value)
129 raise error.Error("expected %s, got %s" % (base.type, type(value)))
130 atom.check_constraints(base)
133 def check_constraints(self, base):
134 """Checks whether 'atom' meets the constraints (if any) defined in
135 'base' and raises an ovs.db.error.Error if any constraint is violated.
137 'base' and 'atom' must have the same type.
138 Checking UUID constraints is deferred to transaction commit time, so
139 this function does nothing for UUID constraints."""
140 assert base.type == self.type
141 if base.enum is not None and self not in base.enum:
142 raise ConstraintViolation(
143 "%s is not one of the allowed values (%s)"
144 % (self.to_string(), base.enum.to_string()))
145 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
146 if ((base.min is None or self.value >= base.min) and
147 (base.max is None or self.value <= base.max)):
149 elif base.min is not None and base.max is not None:
150 raise ConstraintViolation(
151 "%s is not in the valid range %.15g to %.15g (inclusive)"
152 % (self.to_string(), base.min, base.max))
153 elif base.min is not None:
154 raise ConstraintViolation(
155 "%s is less than minimum allowed value %.15g"
156 % (self.to_string(), base.min))
158 raise ConstraintViolation(
159 "%s is greater than maximum allowed value %.15g"
160 % (self.to_string(), base.max))
161 elif base.type == ovs.db.types.StringType:
162 # XXX The C version validates that the string is valid UTF-8 here.
163 # Do we need to do that in Python too?
166 if length < base.min_length:
167 raise ConstraintViolation(
168 '"%s" length %d is less than minimum allowed length %d'
169 % (s, length, base.min_length))
170 elif length > base.max_length:
171 raise ConstraintViolation(
172 '"%s" length %d is greater than maximum allowed '
173 'length %d' % (s, length, base.max_length))
176 if self.type == ovs.db.types.UuidType:
177 return ovs.ovsuuid.to_json(self.value)
181 def cInitAtom(self, var):
182 if self.type == ovs.db.types.IntegerType:
183 return ['%s.integer = %d;' % (var, self.value)]
184 elif self.type == ovs.db.types.RealType:
185 return ['%s.real = %.15g;' % (var, self.value)]
186 elif self.type == ovs.db.types.BooleanType:
188 return ['%s.boolean = true;']
190 return ['%s.boolean = false;']
191 elif self.type == ovs.db.types.StringType:
192 return ['%s.string = xstrdup("%s");'
193 % (var, escapeCString(self.value))]
194 elif self.type == ovs.db.types.UuidType:
195 return ovs.ovsuuid.to_c_assignment(self.value, var)
197 def toEnglish(self, escapeLiteral=returnUnchanged):
198 if self.type == ovs.db.types.IntegerType:
199 return '%d' % self.value
200 elif self.type == ovs.db.types.RealType:
201 return '%.15g' % self.value
202 elif self.type == ovs.db.types.BooleanType:
207 elif self.type == ovs.db.types.StringType:
208 return escapeLiteral(self.value)
209 elif self.type == ovs.db.types.UuidType:
210 return self.value.value
212 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
215 def __string_needs_quotes(s):
216 return Atom.__need_quotes_re.match(s)
219 if self.type == ovs.db.types.IntegerType:
220 return '%d' % self.value
221 elif self.type == ovs.db.types.RealType:
222 return '%.15g' % self.value
223 elif self.type == ovs.db.types.BooleanType:
228 elif self.type == ovs.db.types.StringType:
229 if Atom.__string_needs_quotes(self.value):
230 return ovs.json.to_string(self.value)
233 elif self.type == ovs.db.types.UuidType:
234 return str(self.value)
238 if type(x) in [int, long]:
239 t = ovs.db.types.IntegerType
240 elif type(x) == float:
241 t = ovs.db.types.RealType
242 elif x in [False, True]:
243 t = ovs.db.types.BooleanType
244 elif type(x) in [str, unicode]:
245 t = ovs.db.types.StringType
246 elif isinstance(x, uuid):
247 t = ovs.db.types.UuidType
254 def __init__(self, type_, values={}):
258 def __cmp__(self, other):
259 if not isinstance(other, Datum):
260 return NotImplemented
261 elif self.values < other.values:
263 elif self.values > other.values:
270 def __contains__(self, item):
271 return item in self.values
274 return Datum(self.type, dict(self.values))
281 values = {type_.key.default(): type_.value.default()}
283 values = {type_.key.default(): None}
284 return Datum(type_, values)
286 def is_default(self):
287 return self == Datum.default(self.type)
289 def check_constraints(self):
290 """Checks that each of the atoms in 'datum' conforms to the constraints
291 specified by its 'type' and raises an ovs.db.error.Error.
293 This function is not commonly useful because the most ordinary way to
294 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
295 which check constraints themselves."""
296 for keyAtom, valueAtom in self.values.iteritems():
297 keyAtom.check_constraints(self.type.key)
298 if valueAtom is not None:
299 valueAtom.check_constraints(self.type.value)
302 def from_json(type_, json, symtab=None):
303 """Parses 'json' as a datum of the type described by 'type'. If
304 successful, returns a new datum. On failure, raises an
307 Violations of constraints expressed by 'type' are treated as errors.
309 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
310 Refer to ovsdb/SPECS for information about this, and for the syntax
311 that this function accepts."""
312 is_map = type_.is_map()
314 (type(json) == list and len(json) > 0 and json[0] == "set")):
320 inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
323 if n < type_.n_min or n > type_.n_max:
324 raise error.Error("%s must have %d to %d members but %d are "
325 "present" % (class_, type_.n_min,
330 for element in inner:
332 key, value = ovs.db.parser.parse_json_pair(element)
333 keyAtom = Atom.from_json(type_.key, key, symtab)
334 valueAtom = Atom.from_json(type_.value, value, symtab)
336 keyAtom = Atom.from_json(type_.key, element, symtab)
339 if keyAtom in values:
341 raise error.Error("map contains duplicate key")
343 raise error.Error("set contains duplicate")
345 values[keyAtom] = valueAtom
347 return Datum(type_, values)
349 keyAtom = Atom.from_json(type_.key, json, symtab)
350 return Datum(type_, {keyAtom: None})
353 if self.type.is_map():
354 return ["map", [[k.to_json(), v.to_json()]
355 for k, v in sorted(self.values.items())]]
356 elif len(self.values) == 1:
357 key = self.values.keys()[0]
360 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
364 if self.type.n_max > 1 or len(self.values) == 0:
365 if self.type.is_map():
376 for i, key in enumerate(sorted(self.values)):
380 s.append(key.to_string())
381 if self.type.is_map():
383 s.append(self.values[key].to_string())
390 if self.type.is_map():
391 return [[k.value, v.value] for k, v in self.values.iteritems()]
393 return [k.value for k in self.values.iterkeys()]
396 return dict(self.values)
399 if len(self.values) == 1:
400 if self.type.is_map():
401 k, v = self.values.iteritems()[0]
402 return [k.value, v.value]
404 return self.values.keys()[0].value
408 def to_python(self, uuid_to_row):
409 """Returns this datum's value converted into a natural Python
410 representation of this datum's type, according to the following
413 - If the type has exactly one value and it is not a map (that is,
414 self.type.is_scalar() returns True), then the value is:
416 * An int or long, for an integer column.
418 * An int or long or float, for a real column.
420 * A bool, for a boolean column.
422 * A str or unicode object, for a string column.
424 * A uuid.UUID object, for a UUID column without a ref_table.
426 * An object represented the referenced row, for a UUID column with
427 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
430 If some error occurs (e.g. the database server's idea of the column
431 is different from the IDL's idea), then the default value for the
432 scalar type is used (see Atom.default()).
434 - Otherwise, if the type is not a map, then the value is a Python list
435 whose elements have the types described above.
437 - Otherwise, the type is a map, and the value is a Python dict that
438 maps from key to value, with key and value types determined as
441 'uuid_to_row' must be a function that takes a value and an
442 ovs.db.types.BaseType and translates UUIDs into row objects."""
443 if self.type.is_scalar():
444 value = uuid_to_row(self.as_scalar(), self.type.key)
446 return self.type.key.default()
449 elif self.type.is_map():
451 for k, v in self.values.iteritems():
452 dk = uuid_to_row(k.value, self.type.key)
453 dv = uuid_to_row(v.value, self.type.value)
454 if dk is not None and dv is not None:
459 for k in self.values:
460 dk = uuid_to_row(k.value, self.type.key)
466 def from_python(type_, value, row_to_uuid):
467 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
468 new datum's value is taken from 'value', which must take the form
469 described as a valid return value from Datum.to_python() for 'type'.
471 Each scalar value within 'value' is initally passed through
472 'row_to_uuid', which should convert objects that represent rows (if
473 any) into uuid.UUID objects and return other data unchanged.
475 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
478 if type(value) == dict:
479 for k, v in value.iteritems():
480 ka = Atom.from_python(type_.key, row_to_uuid(k))
481 va = Atom.from_python(type_.value, row_to_uuid(v))
483 elif type(value) in (list, tuple):
485 ka = Atom.from_python(type_.key, row_to_uuid(k))
488 ka = Atom.from_python(type_.key, row_to_uuid(value))
491 datum = Datum(type_, d)
492 datum.check_constraints()
493 if not datum.conforms_to_type():
494 raise error.Error("%d values when type requires between %d and %d"
495 % (len(d), type_.n_min, type_.n_max))
499 def __getitem__(self, key):
500 if not isinstance(key, Atom):
502 if not self.type.is_map():
504 elif key not in self.values:
507 return self.values[key].value
509 def get(self, key, default=None):
510 if not isinstance(key, Atom):
512 if key in self.values:
513 return self.values[key].value
518 return self.to_string()
520 def conforms_to_type(self):
522 return self.type.n_min <= n <= self.type.n_max
524 def cInitDatum(self, var):
525 if len(self.values) == 0:
526 return ["ovsdb_datum_init_empty(%s);" % var]
528 s = ["%s->n = %d;" % (var, len(self.values))]
529 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
530 % (var, len(self.values), var)]
532 for i, key in enumerate(sorted(self.values)):
533 s += key.cInitAtom("%s->keys[%d]" % (var, i))
536 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
537 % (var, len(self.values), var)]
538 for i, (key, value) in enumerate(sorted(self.values.items())):
539 s += value.cInitAtom("%s->values[%d]" % (var, i))
541 s += ["%s->values = NULL;" % var]
543 if len(self.values) > 1:
544 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
545 % (var, self.type.key.type.to_string().upper())]