f71def9dafff75ad16dd835a7686025c0268108e
[sliver-openvswitch.git] / python / ovs / db / data.py
1 # Copyright (c) 2009, 2010, 2011 Nicira Networks
2 #
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:
6 #
7 #     http://www.apache.org/licenses/LICENSE-2.0
8 #
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.
14
15 import re
16 import uuid
17
18 import ovs.poller
19 import ovs.socket_util
20 import ovs.json
21 import ovs.jsonrpc
22 import ovs.ovsuuid
23
24 import ovs.db.parser
25 from ovs.db import error
26 import ovs.db.types
27
28 class ConstraintViolation(error.Error):
29     def __init__(self, msg, json=None):
30         error.Error.__init__(self, msg, json, tag="constraint violation")
31
32 def escapeCString(src):
33     dst = []
34     for c in src:
35         if c in "\\\"":
36             dst.append("\\" + c)
37         elif ord(c) < 32:
38             if c == '\n':
39                 dst.append('\\n')
40             elif c == '\r':
41                 dst.append('\\r')
42             elif c == '\a':
43                 dst.append('\\a')
44             elif c == '\b':
45                 dst.append('\\b')
46             elif c == '\f':
47                 dst.append('\\f')
48             elif c == '\t':
49                 dst.append('\\t')
50             elif c == '\v':
51                 dst.append('\\v')
52             else:
53                 dst.append('\\%03o' % ord(c))
54         else:
55             dst.append(c)
56     return ''.join(dst)
57
58 def returnUnchanged(x):
59     return x
60
61 class Atom(object):
62     def __init__(self, type_, value=None):
63         self.type = type_
64         if value is not None:
65             self.value = value
66         else:
67             self.value = type_.default_atom()
68
69     def __cmp__(self, other):
70         if not isinstance(other, Atom) or self.type != other.type:
71             return NotImplemented
72         elif self.value < other.value:
73             return -1
74         elif self.value > other.value:
75             return 1
76         else:
77             return 0
78
79     def __hash__(self):
80         return hash(self.value)
81
82     @staticmethod
83     def default(type_):
84         return Atom(type_)
85
86     def is_default(self):
87         return self == self.default(self.type)
88
89     @staticmethod
90     def from_json(base, json, symtab=None):
91         type_ = base.type
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.from_json(json, symtab))
100         else:
101             raise error.Error("expected %s" % type_.to_string(), json)
102         atom.check_constraints(base)
103         return atom
104
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.
108
109         'base' and 'atom' must have the same type.
110
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)):
121                 pass
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))
130             else:
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?
137             s = self.value
138             length = len(s)
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))
147     
148     def to_json(self):
149         if self.type == ovs.db.types.UuidType:
150             return ovs.ovsuuid.to_json(self.value)
151         else:
152             return self.value
153
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:
160             if self.value:
161                 return ['%s.boolean = true;']
162             else:
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 ovs.ovsuuid.to_c_assignment(self.value, var)
169
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:
176             if self.value:
177                 return 'true'
178             else:
179                 return 'false'
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
184
185     __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
186     @staticmethod
187     def __string_needs_quotes(s):
188         return Atom.__need_quotes_re.match(s)
189
190     def to_string(self):
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:
196             if self.value:
197                 return 'true'
198             else:
199                 return 'false'
200         elif self.type == ovs.db.types.StringType:
201             if Atom.__string_needs_quotes(self.value):
202                 return ovs.json.to_string(self.value)
203             else:
204                 return self.value
205         elif self.type == ovs.db.types.UuidType:
206             return str(self.value)
207
208     @staticmethod
209     def new(x):
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.BooleanType
216         elif type(x) in [str, unicode]:
217             t = ovs.db.types.StringType
218         elif isinstance(x, uuid):
219             t = ovs.db.types.UuidType
220         else:
221             raise TypeError
222         return Atom(t, x)
223
224 class Datum(object):
225     def __init__(self, type_, values={}):
226         self.type = type_
227         self.values = values
228
229     def __cmp__(self, other):
230         if not isinstance(other, Datum):
231             return NotImplemented
232         elif self.values < other.values:
233             return -1
234         elif self.values > other.values:
235             return 1
236         else:
237             return 0
238
239     __hash__ = None
240
241     def __contains__(self, item):
242         return item in self.values
243
244     def copy(self):
245         return Datum(self.type, dict(self.values))
246
247     @staticmethod
248     def default(type_):
249         if type_.n_min == 0:
250             values = {}
251         elif type_.is_map():
252             values = {type_.key.default(): type_.value.default()}
253         else:
254             values = {type_.key.default(): None}
255         return Datum(type_, values)
256
257     def is_default(self):
258         return self == Datum.default(self.type)
259
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.
263
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.iteritems():
268             keyAtom.check_constraints(self.type.key)
269             if valueAtom is not None:
270                 valueAtom.check_constraints(self.type.value)
271
272     @staticmethod
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
276         ovs.db.error.Error.
277         
278         Violations of constraints expressed by 'type' are treated as errors.
279         
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()
284         if (is_map or
285             (type(json) == list and len(json) > 0 and json[0] == "set")):
286             if is_map:
287                 class_ = "map"
288             else:
289                 class_ = "set"
290
291             inner = ovs.db.parser.unwrap_json(json, class_, [list, tuple],
292                                               "array")
293             n = len(inner)
294             if n < type_.n_min or n > type_.n_max:
295                 raise error.Error("%s must have %d to %d members but %d are "
296                                   "present" % (class_, type_.n_min,
297                                                type_.n_max, n),
298                                   json)
299
300             values = {}
301             for element in inner:
302                 if is_map:
303                     key, value = ovs.db.parser.parse_json_pair(element)
304                     keyAtom = Atom.from_json(type_.key, key, symtab)
305                     valueAtom = Atom.from_json(type_.value, value, symtab)
306                 else:
307                     keyAtom = Atom.from_json(type_.key, element, symtab)
308                     valueAtom = None
309
310                 if keyAtom in values:
311                     if is_map:
312                         raise error.Error("map contains duplicate key")
313                     else:
314                         raise error.Error("set contains duplicate")
315
316                 values[keyAtom] = valueAtom
317
318             return Datum(type_, values)
319         else:
320             keyAtom = Atom.from_json(type_.key, json, symtab)
321             return Datum(type_, {keyAtom: None})
322
323     def to_json(self):
324         if self.type.is_map():
325             return ["map", [[k.to_json(), v.to_json()]
326                             for k, v in sorted(self.values.items())]]
327         elif len(self.values) == 1:
328             key = self.values.keys()[0]
329             return key.to_json()
330         else:
331             return ["set", [k.to_json() for k in sorted(self.values.keys())]]
332
333     def to_string(self):
334         head = tail = None
335         if self.type.n_max > 1 or len(self.values) == 0:
336             if self.type.is_map():
337                 head = "{"
338                 tail = "}"
339             else:
340                 head = "["
341                 tail = "]"
342
343         s = []
344         if head:
345             s.append(head)
346
347         for i, key in enumerate(sorted(self.values)):
348             if i:
349                 s.append(", ")
350
351             s.append(key.to_string())
352             if self.type.is_map():
353                 s.append("=")
354                 s.append(self.values[key].to_string())
355
356         if tail:
357             s.append(tail)
358         return ''.join(s)
359
360     def as_list(self):
361         if self.type.is_map():
362             return [[k.value, v.value] for k, v in self.values.iteritems()]
363         else:
364             return [k.value for k in self.values.iterkeys()]
365         
366     def as_scalar(self):
367         if len(self.values) == 1:
368             if self.type.is_map():
369                 k, v = self.values.iteritems()[0]
370                 return [k.value, v.value]
371             else:
372                 return self.values.keys()[0].value
373         else:
374             return None
375
376     def __getitem__(self, key):
377         if not isinstance(key, Atom):
378             key = Atom.new(key)
379         if not self.type.is_map():
380             raise IndexError
381         elif key not in self.values:
382             raise KeyError
383         else:
384             return self.values[key].value
385
386     def get(self, key, default=None):
387         if not isinstance(key, Atom):
388             key = Atom.new(key)
389         if key in self.values:
390             return self.values[key].value
391         else:
392             return default
393         
394     def __str__(self):
395         return self.to_string()
396
397     def conforms_to_type(self):
398         n = len(self.values)
399         return self.type.n_min <= n <= self.type.n_max
400
401     def cInitDatum(self, var):
402         if len(self.values) == 0:
403             return ["ovsdb_datum_init_empty(%s);" % var]
404
405         s = ["%s->n = %d;" % (var, len(self.values))]
406         s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
407               % (var, len(self.values), var)]
408
409         for i, key in enumerate(sorted(self.values)):
410             s += key.cInitAtom("%s->keys[%d]" % (var, i))
411         
412         if self.type.value:
413             s += ["%s->values = xmalloc(%d * sizeof *%s->values);"
414                   % (var, len(self.values), var)]
415             for i, (key, value) in enumerate(sorted(self.values.items())):
416                 s += value.cInitAtom("%s->values[%d]" % (var, i))
417         else:
418             s += ["%s->values = NULL;" % var]
419
420         if len(self.values) > 1:
421             s += ["ovsdb_datum_sort_assert(%s, OVSDB_TYPE_%s);"
422                   % (var, self.type.key.type.to_string().upper())]
423
424         return s