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
28 class ConstraintViolation(error.Error):
29 def __init__(self, msg, json=None):
30 error.Error.__init__(self, msg, json, tag="constraint violation")
32 def escapeCString(src):
53 dst.append('\\%03o' % ord(c))
58 def returnUnchanged(x):
62 def __init__(self, type_, value=None):
67 self.value = type_.default_atom()
69 def __cmp__(self, other):
70 if not isinstance(other, Atom) or self.type != other.type:
72 elif self.value < other.value:
74 elif self.value > other.value:
80 return hash(self.value)
84 """Returns the default value for the given type_, which must be an
85 instance of ovs.db.types.AtomicType.
87 The default value for each atomic type is;
89 - 0, for integer or real atoms.
91 - False, for a boolean atom.
93 - "", for a string atom.
95 - The all-zeros UUID, for a UUID atom."""
99 return self == self.default(self.type)
102 def from_json(base, json, symtab=None):
104 json = ovs.db.parser.float_to_int(json)
105 if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
106 or (type_ == ovs.db.types.RealType and type(json) in [int, long, float])
107 or (type_ == ovs.db.types.BooleanType and type(json) == bool)
108 or (type_ == ovs.db.types.StringType and type(json) in [str, unicode])):
109 atom = Atom(type_, json)
110 elif type_ == ovs.db.types.UuidType:
111 atom = Atom(type_, ovs.ovsuuid.from_json(json, symtab))
113 raise error.Error("expected %s" % type_.to_string(), json)
114 atom.check_constraints(base)
118 def from_python(base, value):
119 value = ovs.db.parser.float_to_int(value)
120 if type(value) in base.type.python_types:
121 atom = Atom(base.type, value)
123 raise error.Error("expected %s, got %s" % (base.type, type(value)))
124 atom.check_constraints(base)
127 def check_constraints(self, base):
128 """Checks whether 'atom' meets the constraints (if any) defined in
129 'base' and raises an ovs.db.error.Error if any constraint is violated.
131 'base' and 'atom' must have the same type.
132 Checking UUID constraints is deferred to transaction commit time, so
133 this function does nothing for UUID constraints."""
134 assert base.type == self.type
135 if base.enum is not None and self not in base.enum:
136 raise ConstraintViolation(
137 "%s is not one of the allowed values (%s)"
138 % (self.to_string(), base.enum.to_string()))
139 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
140 if ((base.min is None or self.value >= base.min) and
141 (base.max is None or self.value <= base.max)):
143 elif base.min is not None and base.max is not None:
144 raise ConstraintViolation(
145 "%s is not in the valid range %.15g to %.15g (inclusive)"
146 % (self.to_string(), base.min, base.max))
147 elif base.min is not None:
148 raise ConstraintViolation(
149 "%s is less than minimum allowed value %.15g"
150 % (self.to_string(), base.min))
152 raise ConstraintViolation(
153 "%s is greater than maximum allowed value %.15g"
154 % (self.to_string(), base.max))
155 elif base.type == ovs.db.types.StringType:
156 # XXX The C version validates that the string is valid UTF-8 here.
157 # Do we need to do that in Python too?
160 if length < base.min_length:
161 raise ConstraintViolation(
162 '"%s" length %d is less than minimum allowed length %d'
163 % (s, length, base.min_length))
164 elif length > base.max_length:
165 raise ConstraintViolation(
166 '"%s" length %d is greater than maximum allowed '
167 'length %d' % (s, length, base.max_length))
170 if self.type == ovs.db.types.UuidType:
171 return ovs.ovsuuid.to_json(self.value)
175 def cInitAtom(self, var):
176 if self.type == ovs.db.types.IntegerType:
177 return ['%s.integer = %d;' % (var, self.value)]
178 elif self.type == ovs.db.types.RealType:
179 return ['%s.real = %.15g;' % (var, self.value)]
180 elif self.type == ovs.db.types.BooleanType:
182 return ['%s.boolean = true;']
184 return ['%s.boolean = false;']
185 elif self.type == ovs.db.types.StringType:
186 return ['%s.string = xstrdup("%s");'
187 % (var, escapeCString(self.value))]
188 elif self.type == ovs.db.types.UuidType:
189 return ovs.ovsuuid.to_c_assignment(self.value, var)
191 def toEnglish(self, escapeLiteral=returnUnchanged):
192 if self.type == ovs.db.types.IntegerType:
193 return '%d' % self.value
194 elif self.type == ovs.db.types.RealType:
195 return '%.15g' % self.value
196 elif self.type == ovs.db.types.BooleanType:
201 elif self.type == ovs.db.types.StringType:
202 return escapeLiteral(self.value)
203 elif self.type == ovs.db.types.UuidType:
204 return self.value.value
206 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
208 def __string_needs_quotes(s):
209 return Atom.__need_quotes_re.match(s)
212 if self.type == ovs.db.types.IntegerType:
213 return '%d' % self.value
214 elif self.type == ovs.db.types.RealType:
215 return '%.15g' % self.value
216 elif self.type == ovs.db.types.BooleanType:
221 elif self.type == ovs.db.types.StringType:
222 if Atom.__string_needs_quotes(self.value):
223 return ovs.json.to_string(self.value)
226 elif self.type == ovs.db.types.UuidType:
227 return str(self.value)
231 if type(x) in [int, long]:
232 t = ovs.db.types.IntegerType
233 elif type(x) == float:
234 t = ovs.db.types.RealType
235 elif x in [False, True]:
236 t = ovs.db.types.BooleanType
237 elif type(x) in [str, unicode]:
238 t = ovs.db.types.StringType
239 elif isinstance(x, uuid):
240 t = ovs.db.types.UuidType
246 def __init__(self, type_, values={}):
250 def __cmp__(self, other):
251 if not isinstance(other, Datum):
252 return NotImplemented
253 elif self.values < other.values:
255 elif self.values > other.values:
262 def __contains__(self, item):
263 return item in self.values
266 return Datum(self.type, dict(self.values))
273 values = {type_.key.default(): type_.value.default()}
275 values = {type_.key.default(): None}
276 return Datum(type_, values)
278 def is_default(self):
279 return self == Datum.default(self.type)
281 def check_constraints(self):
282 """Checks that each of the atoms in 'datum' conforms to the constraints
283 specified by its 'type' and raises an ovs.db.error.Error.
285 This function is not commonly useful because the most ordinary way to
286 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
287 which check constraints themselves."""
288 for keyAtom, valueAtom in self.values.iteritems():
289 keyAtom.check_constraints(self.type.key)
290 if valueAtom is not None:
291 valueAtom.check_constraints(self.type.value)
294 def from_json(type_, json, symtab=None):
295 """Parses 'json' as a datum of the type described by 'type'. If
296 successful, returns a new datum. On failure, raises an
299 Violations of constraints expressed by 'type' are treated as errors.
301 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
302 Refer to ovsdb/SPECS for information about this, and for the syntax
303 that this function accepts."""
304 is_map = type_.is_map()
306 (type(json) == list and len(json) > 0 and json[0] == "set")):
312 inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
315 if n < type_.n_min or n > type_.n_max:
316 raise error.Error("%s must have %d to %d members but %d are "
317 "present" % (class_, type_.n_min,
322 for element in inner:
324 key, value = ovs.db.parser.parse_json_pair(element)
325 keyAtom = Atom.from_json(type_.key, key, symtab)
326 valueAtom = Atom.from_json(type_.value, value, symtab)
328 keyAtom = Atom.from_json(type_.key, element, symtab)
331 if keyAtom in values:
333 raise error.Error("map contains duplicate key")
335 raise error.Error("set contains duplicate")
337 values[keyAtom] = valueAtom
339 return Datum(type_, values)
341 keyAtom = Atom.from_json(type_.key, json, symtab)
342 return Datum(type_, {keyAtom: None})
345 if self.type.is_map():
346 return ["map", [[k.to_json(), v.to_json()]
347 for k, v in sorted(self.values.items())]]
348 elif len(self.values) == 1:
349 key = self.values.keys()[0]
352 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
356 if self.type.n_max > 1 or len(self.values) == 0:
357 if self.type.is_map():
368 for i, key in enumerate(sorted(self.values)):
372 s.append(key.to_string())
373 if self.type.is_map():
375 s.append(self.values[key].to_string())
382 if self.type.is_map():
383 return [[k.value, v.value] for k, v in self.values.iteritems()]
385 return [k.value for k in self.values.iterkeys()]
388 return dict(self.values)
391 if len(self.values) == 1:
392 if self.type.is_map():
393 k, v = self.values.iteritems()[0]
394 return [k.value, v.value]
396 return self.values.keys()[0].value
400 def to_python(self, uuid_to_row):
401 """Returns this datum's value converted into a natural Python
402 representation of this datum's type, according to the following
405 - If the type has exactly one value and it is not a map (that is,
406 self.type.is_scalar() returns True), then the value is:
408 * An int or long, for an integer column.
410 * An int or long or float, for a real column.
412 * A bool, for a boolean column.
414 * A str or unicode object, for a string column.
416 * A uuid.UUID object, for a UUID column without a ref_table.
418 * An object represented the referenced row, for a UUID column with
419 a ref_table. (For the Idl, this object will be an ovs.db.idl.Row
422 If some error occurs (e.g. the database server's idea of the column
423 is different from the IDL's idea), then the default value for the
424 scalar type is used (see Atom.default()).
426 - Otherwise, if the type is not a map, then the value is a Python list
427 whose elements have the types described above.
429 - Otherwise, the type is a map, and the value is a Python dict that
430 maps from key to value, with key and value types determined as
433 'uuid_to_row' must be a function that takes a value and an
434 ovs.db.types.BaseType and translates UUIDs into row objects."""
435 if self.type.is_scalar():
436 value = uuid_to_row(self.as_scalar(), self.type.key)
438 return self.type.key.default()
441 elif self.type.is_map():
443 for k, v in self.values.iteritems():
444 dk = uuid_to_row(k.value, self.type.key)
445 dv = uuid_to_row(v.value, self.type.value)
446 if dk is not None and dv is not None:
451 for k in self.values:
452 dk = uuid_to_row(k.value, self.type.key)
458 def from_python(type_, value, row_to_uuid):
459 """Returns a new Datum with the given ovs.db.types.Type 'type_'. The
460 new datum's value is taken from 'value', which must take the form
461 described as a valid return value from Datum.to_python() for 'type'.
463 Each scalar value within 'value' is initally passed through
464 'row_to_uuid', which should convert objects that represent rows (if
465 any) into uuid.UUID objects and return other data unchanged.
467 Raises ovs.db.error.Error if 'value' is not in an appropriate form for
470 if type(value) == dict:
471 for k, v in value.iteritems():
472 ka = Atom.from_python(type_.key, row_to_uuid(k))
473 va = Atom.from_python(type_.value, row_to_uuid(v))
475 elif type(value) in (list, tuple):
477 ka = Atom.from_python(type_.key, row_to_uuid(k))
480 ka = Atom.from_python(type_.key, row_to_uuid(value))
483 datum = Datum(type_, d)
484 datum.check_constraints()
485 if not datum.conforms_to_type():
486 raise error.Error("%d values when type requires between %d and %d"
487 % (len(d), type_.n_min, type_.n_max))
491 def __getitem__(self, key):
492 if not isinstance(key, Atom):
494 if not self.type.is_map():
496 elif key not in self.values:
499 return self.values[key].value
501 def get(self, key, default=None):
502 if not isinstance(key, Atom):
504 if key in self.values:
505 return self.values[key].value
510 return self.to_string()
512 def conforms_to_type(self):
514 return self.type.n_min <= n <= self.type.n_max
516 def cInitDatum(self, var):
517 if len(self.values) == 0:
518 return ["ovsdb_datum_init_empty(%s);" % var]
520 s = ["%s->n = %d;" % (var, len(self.values))]
521 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
522 % (var, len(self.values), var)]
524 for i, key in enumerate(sorted(self.values)):
525 s += key.cInitAtom("%s->keys[%d]" % (var, i))
528 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
529 % (var, len(self.values), var)]
530 for i, (key, value) in enumerate(sorted(self.values.items())):
531 s += value.cInitAtom("%s->values[%d]" % (var, i))
533 s += ["%s->values = NULL;" % var]
535 if len(self.values) > 1:
536 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
537 % (var, self.type.key.type.to_string().upper())]