1 # Copyright (c) 2009, 2010 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.
24 import ovs.socket_util
30 from ovs.db import error
33 class ConstraintViolation(error.Error):
34 def __init__(self, msg, json=None):
35 error.Error.__init__(self, msg, json, tag="constraint violation")
37 def escapeCString(src):
58 dst += '\\%03o' % ord(c)
63 def returnUnchanged(x):
67 def __init__(self, type, value=None):
72 self.value = type.default_atom()
74 def __cmp__(self, other):
75 if not isinstance(other, Atom) or self.type != other.type:
77 elif self.value < other.value:
79 elif self.value > other.value:
85 return hash(self.value)
92 return self == default(self.type)
95 def from_json(base, json, symtab=None):
97 json = ovs.db.parser.float_to_int(json)
98 if ((type_ == ovs.db.types.IntegerType and type(json) in [int, long])
99 or (type_ == ovs.db.types.RealType and type(json) in [int, long, float])
100 or (type_ == ovs.db.types.BooleanType and type(json) == bool)
101 or (type_ == ovs.db.types.StringType and type(json) in [str, unicode])):
102 atom = Atom(type_, json)
103 elif type_ == ovs.db.types.UuidType:
104 atom = Atom(type_, ovs.ovsuuid.UUID.from_json(json, symtab))
106 raise error.Error("expected %s" % type_.to_string(), json)
107 atom.check_constraints(base)
110 def check_constraints(self, base):
111 """Checks whether 'atom' meets the constraints (if any) defined in
112 'base' and raises an ovs.db.error.Error if any constraint is violated.
114 'base' and 'atom' must have the same type.
116 Checking UUID constraints is deferred to transaction commit time, so
117 this function does nothing for UUID constraints."""
118 assert base.type == self.type
119 if base.enum is not None and self not in base.enum:
120 raise ConstraintViolation(
121 "%s is not one of the allowed values (%s)"
122 % (self.to_string(), base.enum.to_string()))
123 elif base.type in [ovs.db.types.IntegerType, ovs.db.types.RealType]:
124 if ((base.min is None or self.value >= base.min) and
125 (base.max is None or self.value <= base.max)):
127 elif base.min is not None and base.max is not None:
128 raise ConstraintViolation(
129 "%s is not in the valid range %.15g to %.15g (inclusive)"
130 % (self.to_string(), base.min, base.max))
131 elif base.min is not None:
132 raise ConstraintViolation(
133 "%s is less than minimum allowed value %.15g"
134 % (self.to_string(), base.min))
136 raise ConstraintViolation(
137 "%s is greater than maximum allowed value %.15g"
138 % (self.to_string(), base.max))
139 elif base.type == ovs.db.types.StringType:
140 # XXX The C version validates that the string is valid UTF-8 here.
141 # Do we need to do that in Python too?
144 if length < base.min_length:
145 raise ConstraintViolation(
146 "\"%s\" length %d is less than minimum allowed length %d"
147 % (s, length, base.min_length))
148 elif length > base.max_length:
149 raise ConstraintViolation(
150 "\"%s\" length %d is greater than maximum allowed "
151 "length %d" % (s, length, base.max_length))
154 if self.type == ovs.db.types.UuidType:
155 return self.value.to_json()
159 def cInitAtom(self, var):
160 if self.type == ovs.db.types.IntegerType:
161 return ['%s.integer = %d;' % (var, self.value)]
162 elif self.type == ovs.db.types.RealType:
163 return ['%s.real = %.15g;' % (var, self.value)]
164 elif self.type == ovs.db.types.BooleanType:
166 return ['%s.boolean = true;']
168 return ['%s.boolean = false;']
169 elif self.type == ovs.db.types.StringType:
170 return ['%s.string = xstrdup("%s");'
171 % (var, escapeCString(self.value))]
172 elif self.type == ovs.db.types.UuidType:
173 return self.value.cInitUUID(var)
175 def toEnglish(self, escapeLiteral=returnUnchanged):
176 if self.type == ovs.db.types.IntegerType:
177 return '%d' % self.value
178 elif self.type == ovs.db.types.RealType:
179 return '%.15g' % self.value
180 elif self.type == ovs.db.types.BooleanType:
185 elif self.type == ovs.db.types.StringType:
186 return escapeLiteral(self.value)
187 elif self.type == ovs.db.types.UuidType:
188 return self.value.value
190 __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
192 def __string_needs_quotes(s):
193 return Atom.__need_quotes_re.match(s)
196 if self.type == ovs.db.types.IntegerType:
197 return '%d' % self.value
198 elif self.type == ovs.db.types.RealType:
199 return '%.15g' % self.value
200 elif self.type == ovs.db.types.BooleanType:
205 elif self.type == ovs.db.types.StringType:
206 if Atom.__string_needs_quotes(self.value):
207 return ovs.json.to_string(self.value)
210 elif self.type == ovs.db.types.UuidType:
211 return str(self.value)
215 if type(x) in [int, long]:
216 t = ovs.db.types.IntegerType
217 elif type(x) == float:
218 t = ovs.db.types.RealType
219 elif x in [False, True]:
220 t = ovs.db.types.RealType
221 elif type(x) in [str, unicode]:
222 t = ovs.db.types.StringType
223 elif isinstance(x, uuid):
224 t = ovs.db.types.UuidType
230 def __init__(self, type, values={}):
234 def __cmp__(self, other):
235 if not isinstance(other, Datum):
236 return NotImplemented
237 elif self.values < other.values:
239 elif self.values > other.values:
246 def __contains__(self, item):
247 return item in self.values
250 return Datum(self.type, dict(self.values))
257 values = {type.key.default(): type.value.default()}
259 values = {type.key.default(): None}
260 return Datum(type, values)
263 def is_default(self):
264 return self == default(self.type)
266 def check_constraints(self):
267 """Checks that each of the atoms in 'datum' conforms to the constraints
268 specified by its 'type' and raises an ovs.db.error.Error.
270 This function is not commonly useful because the most ordinary way to
271 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
272 which check constraints themselves."""
273 for keyAtom, valueAtom in self.values:
274 keyAtom.check_constraints()
275 if valueAtom is not None:
276 valueAtom.check_constraints()
279 def from_json(type_, json, symtab=None):
280 """Parses 'json' as a datum of the type described by 'type'. If
281 successful, returns a new datum. On failure, raises an
284 Violations of constraints expressed by 'type' are treated as errors.
286 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
287 Refer to ovsdb/SPECS for information about this, and for the syntax
288 that this function accepts."""
289 is_map = type_.is_map()
291 (type(json) == list and len(json) > 0 and json[0] == "set")):
297 inner = ovs.db.parser.unwrap_json(json, class_, list)
299 if n < type_.n_min or n > type_.n_max:
300 raise error.Error("%s must have %d to %d members but %d are "
301 "present" % (class_, type_.n_min,
306 for element in inner:
308 key, value = ovs.db.parser.parse_json_pair(element)
309 keyAtom = Atom.from_json(type_.key, key, symtab)
310 valueAtom = Atom.from_json(type_.value, value, symtab)
312 keyAtom = Atom.from_json(type_.key, element, symtab)
315 if keyAtom in values:
317 raise error.Error("map contains duplicate key")
319 raise error.Error("set contains duplicate")
321 values[keyAtom] = valueAtom
323 return Datum(type_, values)
325 keyAtom = Atom.from_json(type_.key, json, symtab)
326 return Datum(type_, {keyAtom: None})
329 if len(self.values) == 1 and not self.type.is_map():
330 key = self.values.keys()[0]
332 elif not self.type.is_map():
333 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
335 return ["map", [[k.to_json(), v.to_json()]
336 for k, v in sorted(self.values.items())]]
339 if self.type.n_max > 1 or len(self.values) == 0:
340 if self.type.is_map():
348 for key in sorted(self.values):
353 if self.type.is_map():
354 s += "%s=%s" % (key.to_string(), self.values[key].to_string())
358 if self.type.n_max > 1 or len(self.values) == 0:
359 if self.type.is_map():
366 if self.type.is_map():
367 return [[k.value, v.value] for k, v in self.values.iteritems()]
369 return [k.value for k in self.values.iterkeys()]
372 if len(self.values) == 1:
373 if self.type.is_map():
374 k, v = self.values.iteritems()[0]
375 return [k.value, v.value]
377 return self.values.keys()[0].value
381 def __getitem__(self, key):
382 if not isinstance(key, Atom):
384 if not self.type.is_map():
386 elif key not in self.values:
389 return self.values[key].value
391 def get(self, key, default=None):
392 if not isinstance(key, Atom):
394 if key in self.values:
395 return self.values[key].value
400 return self.to_string()
402 def conforms_to_type(self):
404 return n >= self.type.n_min and n <= self.type.n_max
406 def cInitDatum(self, var):
407 if len(self.values) == 0:
408 return ["ovsdb_datum_init_empty(%s);" % var]
410 s = ["%s->n = %d;" % (var, len(self.values))]
411 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
412 % (var, len(self.values), var)]
415 for key, value in sorted(self.values.items()):
416 s += key.cInitAtom("%s->keys[%d]" % (var, i))
420 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
421 % (var, len(self.values), var)]
423 for key, value in sorted(self.values.items()):
424 s += value.cInitAtom("%s->values[%d]" % (var, i))
427 s += ["%s->values = NULL;" % var]
429 if len(self.values) > 1:
430 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
431 % (var, self.type.key.type.to_string().upper())]