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.
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.append('\\%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 == 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)
262 def is_default(self):
263 return self == Datum.default(self.type)
265 def check_constraints(self):
266 """Checks that each of the atoms in 'datum' conforms to the constraints
267 specified by its 'type' and raises an ovs.db.error.Error.
269 This function is not commonly useful because the most ordinary way to
270 obtain a datum is ultimately via Datum.from_json() or Atom.from_json(),
271 which check constraints themselves."""
272 for keyAtom, valueAtom in self.values:
273 keyAtom.check_constraints()
274 if valueAtom is not None:
275 valueAtom.check_constraints()
278 def from_json(type_, json, symtab=None):
279 """Parses 'json' as a datum of the type described by 'type'. If
280 successful, returns a new datum. On failure, raises an
283 Violations of constraints expressed by 'type' are treated as errors.
285 If 'symtab' is nonnull, then named UUIDs in 'symtab' are accepted.
286 Refer to ovsdb/SPECS for information about this, and for the syntax
287 that this function accepts."""
288 is_map = type_.is_map()
290 (type(json) == list and len(json) > 0 and json[0] == "set")):
296 inner = ovs.db.parser.unwrap_json(json, class_, list)
298 if n < type_.n_min or n > type_.n_max:
299 raise error.Error("%s must have %d to %d members but %d are "
300 "present" % (class_, type_.n_min,
305 for element in inner:
307 key, value = ovs.db.parser.parse_json_pair(element)
308 keyAtom = Atom.from_json(type_.key, key, symtab)
309 valueAtom = Atom.from_json(type_.value, value, symtab)
311 keyAtom = Atom.from_json(type_.key, element, symtab)
314 if keyAtom in values:
316 raise error.Error("map contains duplicate key")
318 raise error.Error("set contains duplicate")
320 values[keyAtom] = valueAtom
322 return Datum(type_, values)
324 keyAtom = Atom.from_json(type_.key, json, symtab)
325 return Datum(type_, {keyAtom: None})
328 if len(self.values) == 1 and not self.type.is_map():
329 key = self.values.keys()[0]
331 elif not self.type.is_map():
332 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
334 return ["map", [[k.to_json(), v.to_json()]
335 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():
352 for key in sorted(self.values):
357 s.append(key.to_string())
358 if self.type.is_map():
360 s.append(self.values[key].to_string())
367 if self.type.is_map():
368 return [[k.value, v.value] for k, v in self.values.iteritems()]
370 return [k.value for k in self.values.iterkeys()]
373 if len(self.values) == 1:
374 if self.type.is_map():
375 k, v = self.values.iteritems()[0]
376 return [k.value, v.value]
378 return self.values.keys()[0].value
382 def __getitem__(self, key):
383 if not isinstance(key, Atom):
385 if not self.type.is_map():
387 elif key not in self.values:
390 return self.values[key].value
392 def get(self, key, default=None):
393 if not isinstance(key, Atom):
395 if key in self.values:
396 return self.values[key].value
401 return self.to_string()
403 def conforms_to_type(self):
405 return self.type.n_min <= n <= self.type.n_max
407 def cInitDatum(self, var):
408 if len(self.values) == 0:
409 return ["ovsdb_datum_init_empty(%s);" % var]
411 s = ["%s->n = %d;" % (var, len(self.values))]
412 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
413 % (var, len(self.values), var)]
416 for key, value in sorted(self.values.items()):
417 s += key.cInitAtom("%s->keys[%d]" % (var, i))
421 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
422 % (var, len(self.values), var)]
424 for key, value in sorted(self.values.items()):
425 s += value.cInitAtom("%s->values[%d]" % (var, i))
428 s += ["%s->values = NULL;" % var]
430 if len(self.values) > 1:
431 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
432 % (var, self.type.key.type.to_string().upper())]