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)
87 return self == self.default(self.type)
90 def from_json(base, json, symtab=None):
92 json = ovs.db.parser.float_to_int(json)
93 if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
94 or (type_ == ovs.db.types.RealType and type(json) in [int, long, float])
95 or (type_ == ovs.db.types.BooleanType and type(json) == bool)
96 or (type_ == ovs.db.types.StringType and type(json) in [str, unicode])):
97 atom = Atom(type_, json)
98 elif type_ == ovs.db.types.UuidType:
99 atom = Atom(type_, ovs.ovsuuid.UUID.from_json(json, symtab))
101 raise error.Error("expected %s" % type_.to_string(), json)
102 atom.check_constraints(base)
105 def check_constraints(self, base):
106 """Checks whether 'atom' meets the constraints (if any) defined in
107 'base' and raises an ovs.db.error.Error if any constraint is violated.
109 'base' and 'atom' must have the same type.
111 Checking UUID constraints is deferred to transaction commit time, so
112 this function does nothing for UUID constraints."""
113 assert base.type == self.type
114 if base.enum is not None and self not in base.enum:
115 raise ConstraintViolation(
116 "%s is not one of the allowed values (%s)"
117 % (self.to_string(), base.enum.to_string()))
118 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
119 if ((base.min is None or self.value >= base.min) and
120 (base.max is None or self.value <= base.max)):
122 elif base.min is not None and base.max is not None:
123 raise ConstraintViolation(
124 "%s is not in the valid range %.15g to %.15g (inclusive)"
125 % (self.to_string(), base.min, base.max))
126 elif base.min is not None:
127 raise ConstraintViolation(
128 "%s is less than minimum allowed value %.15g"
129 % (self.to_string(), base.min))
131 raise ConstraintViolation(
132 "%s is greater than maximum allowed value %.15g"
133 % (self.to_string(), base.max))
134 elif base.type == ovs.db.types.StringType:
135 # XXX The C version validates that the string is valid UTF-8 here.
136 # Do we need to do that in Python too?
139 if length < base.min_length:
140 raise ConstraintViolation(
141 '"%s" length %d is less than minimum allowed length %d'
142 % (s, length, base.min_length))
143 elif length > base.max_length:
144 raise ConstraintViolation(
145 '"%s" length %d is greater than maximum allowed '
146 'length %d' % (s, length, base.max_length))
149 if self.type == ovs.db.types.UuidType:
150 return self.value.to_json()
154 def cInitAtom(self, var):
155 if self.type == ovs.db.types.IntegerType:
156 return ['%s.integer = %d;' % (var, self.value)]
157 elif self.type == ovs.db.types.RealType:
158 return ['%s.real = %.15g;' % (var, self.value)]
159 elif self.type == ovs.db.types.BooleanType:
161 return ['%s.boolean = true;']
163 return ['%s.boolean = false;']
164 elif self.type == ovs.db.types.StringType:
165 return ['%s.string = xstrdup("%s");'
166 % (var, escapeCString(self.value))]
167 elif self.type == ovs.db.types.UuidType:
168 return self.value.cInitUUID(var)
170 def toEnglish(self, escapeLiteral=returnUnchanged):
171 if self.type == ovs.db.types.IntegerType:
172 return '%d' % self.value
173 elif self.type == ovs.db.types.RealType:
174 return '%.15g' % self.value
175 elif self.type == ovs.db.types.BooleanType:
180 elif self.type == ovs.db.types.StringType:
181 return escapeLiteral(self.value)
182 elif self.type == ovs.db.types.UuidType:
183 return self.value.value
185 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
187 def __string_needs_quotes(s):
188 return Atom.__need_quotes_re.match(s)
191 if self.type == ovs.db.types.IntegerType:
192 return '%d' % self.value
193 elif self.type == ovs.db.types.RealType:
194 return '%.15g' % self.value
195 elif self.type == ovs.db.types.BooleanType:
200 elif self.type == ovs.db.types.StringType:
201 if Atom.__string_needs_quotes(self.value):
202 return ovs.json.to_string(self.value)
205 elif self.type == ovs.db.types.UuidType:
206 return str(self.value)
210 if type(x) in [int, long]:
211 t = ovs.db.types.IntegerType
212 elif type(x) == float:
213 t = ovs.db.types.RealType
214 elif x in [False, True]:
215 t = ovs.db.types.RealType
216 elif type(x) in [str, unicode]:
217 t = ovs.db.types.StringType
218 elif isinstance(x, uuid):
219 t = ovs.db.types.UuidType
225 def __init__(self, type_, values={}):
229 def __cmp__(self, other):
230 if not isinstance(other, Datum):
231 return NotImplemented
232 elif self.values < other.values:
234 elif self.values > other.values:
241 def __contains__(self, item):
242 return item in self.values
245 return Datum(self.type, dict(self.values))
252 values = {type_.key.default(): type_.value.default()}
254 values = {type_.key.default(): None}
255 return Datum(type_, values)
257 def is_default(self):
258 return self == Datum.default(self.type)
260 def check_constraints(self):
261 """Checks that each of the atoms in 'datum' conforms to the constraints
262 specified by its 'type' and raises an ovs.db.error.Error.
264 This function is not commonly useful because the most ordinary way to
265 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
266 which check constraints themselves."""
267 for keyAtom, valueAtom in self.values:
268 keyAtom.check_constraints()
269 if valueAtom is not None:
270 valueAtom.check_constraints()
273 def from_json(type_, json, symtab=None):
274 """Parses 'json' as a datum of the type described by 'type'. If
275 successful, returns a new datum. On failure, raises an
278 Violations of constraints expressed by 'type' are treated as errors.
280 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
281 Refer to ovsdb/SPECS for information about this, and for the syntax
282 that this function accepts."""
283 is_map = type_.is_map()
285 (type(json) == list and len(json) > 0 and json[0] == "set")):
291 inner = ovs.db.parser.unwrap_json(json, class_, list)
293 if n < type_.n_min or n > type_.n_max:
294 raise error.Error("%s must have %d to %d members but %d are "
295 "present" % (class_, type_.n_min,
300 for element in inner:
302 key, value = ovs.db.parser.parse_json_pair(element)
303 keyAtom = Atom.from_json(type_.key, key, symtab)
304 valueAtom = Atom.from_json(type_.value, value, symtab)
306 keyAtom = Atom.from_json(type_.key, element, symtab)
309 if keyAtom in values:
311 raise error.Error("map contains duplicate key")
313 raise error.Error("set contains duplicate")
315 values[keyAtom] = valueAtom
317 return Datum(type_, values)
319 keyAtom = Atom.from_json(type_.key, json, symtab)
320 return Datum(type_, {keyAtom: None})
323 if self.type.is_map():
324 return ["map", [[k.to_json(), v.to_json()]
325 for k, v in sorted(self.values.items())]]
326 elif len(self.values) == 1:
327 key = self.values.keys()[0]
330 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
334 if self.type.n_max > 1 or len(self.values) == 0:
335 if self.type.is_map():
346 for i, key in enumerate(sorted(self.values)):
350 s.append(key.to_string())
351 if self.type.is_map():
353 s.append(self.values[key].to_string())
360 if self.type.is_map():
361 return [[k.value, v.value] for k, v in self.values.iteritems()]
363 return [k.value for k in self.values.iterkeys()]
366 if len(self.values) == 1:
367 if self.type.is_map():
368 k, v = self.values.iteritems()[0]
369 return [k.value, v.value]
371 return self.values.keys()[0].value
375 def __getitem__(self, key):
376 if not isinstance(key, Atom):
378 if not self.type.is_map():
380 elif key not in self.values:
383 return self.values[key].value
385 def get(self, key, default=None):
386 if not isinstance(key, Atom):
388 if key in self.values:
389 return self.values[key].value
394 return self.to_string()
396 def conforms_to_type(self):
398 return self.type.n_min <= n <= self.type.n_max
400 def cInitDatum(self, var):
401 if len(self.values) == 0:
402 return ["ovsdb_datum_init_empty(%s);" % var]
404 s = ["%s->n = %d;" % (var, len(self.values))]
405 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
406 % (var, len(self.values), var)]
408 for i, key in enumerate(sorted(self.values)):
409 s += key.cInitAtom("%s->keys[%d]" % (var, i))
412 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
413 % (var, len(self.values), var)]
414 for i, (key, value) in enumerate(sorted(self.values.items())):
415 s += value.cInitAtom("%s->values[%d]" % (var, i))
417 s += ["%s->values = NULL;" % var]
419 if len(self.values) > 1:
420 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
421 % (var, self.type.key.type.to_string().upper())]