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 self.type.is_map():
329 return ["map", [[k.to_json(), v.to_json()]
330 for k, v in sorted(self.values.items())]]
331 elif len(self.values) == 1:
332 key = self.values.keys()[0]
335 return ["set", [k.to_json() for k in sorted(self.values.keys())]]
339 if self.type.n_max > 1 or len(self.values) == 0:
340 if self.type.is_map():
351 for i, key in enumerate(sorted(self.values)):
355 s.append(key.to_string())
356 if self.type.is_map():
358 s.append(self.values[key].to_string())
365 if self.type.is_map():
366 return [[k.value, v.value] for k, v in self.values.iteritems()]
368 return [k.value for k in self.values.iterkeys()]
371 if len(self.values) == 1:
372 if self.type.is_map():
373 k, v = self.values.iteritems()[0]
374 return [k.value, v.value]
376 return self.values.keys()[0].value
380 def __getitem__(self, key):
381 if not isinstance(key, Atom):
383 if not self.type.is_map():
385 elif key not in self.values:
388 return self.values[key].value
390 def get(self, key, default=None):
391 if not isinstance(key, Atom):
393 if key in self.values:
394 return self.values[key].value
399 return self.to_string()
401 def conforms_to_type(self):
403 return self.type.n_min <= n <= self.type.n_max
405 def cInitDatum(self, var):
406 if len(self.values) == 0:
407 return ["ovsdb_datum_init_empty(%s);" % var]
409 s = ["%s->n = %d;" % (var, len(self.values))]
410 s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
411 % (var, len(self.values), var)]
413 for i, key in enumerate(sorted(self.values)):
414 s += key.cInitAtom("%s->keys[%d]" % (var, i))
417 s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
418 % (var, len(self.values), var)]
419 for i, (key, value) in enumerate(sorted(self.values.items())):
420 s += value.cInitAtom("%s->values[%d]" % (var, i))
422 s += ["%s->values = NULL;" % var]
424 if len(self.values) > 1:
425 s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
426 % (var, self.type.key.type.to_string().upper())]