python: Use enumerate() builtin function to simplify counted iteration.
[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 errno
16 import logging
17 import os
18 import re
19 import select
20 import sys
21 import uuid
22
23 import ovs.poller
24 import ovs.socket_util
25 import ovs.json
26 import ovs.jsonrpc
27 import ovs.ovsuuid
28
29 import ovs.db.parser
30 from ovs.db import error
31 import ovs.db.types
32
33 class ConstraintViolation(error.Error):
34     def __init__(self, msg, json=None):
35         error.Error.__init__(self, msg, json, tag="constraint violation")
36
37 def escapeCString(src):
38     dst = []
39     for c in src:
40         if c in "\\\"":
41             dst.append("\\" + c)
42         elif ord(c) < 32:
43             if c == '\n':
44                 dst.append('\\n')
45             elif c == '\r':
46                 dst.append('\\r')
47             elif c == '\a':
48                 dst.append('\\a')
49             elif c == '\b':
50                 dst.append('\\b')
51             elif c == '\f':
52                 dst.append('\\f')
53             elif c == '\t':
54                 dst.append('\\t')
55             elif c == '\v':
56                 dst.append('\\v')
57             else:
58                 dst.append('\\%03o' % ord(c))
59         else:
60             dst.append(c)
61     return ''.join(dst)
62
63 def returnUnchanged(x):
64     return x
65
66 class Atom(object):
67     def __init__(self, type_, value=None):
68         self.type = type_
69         if value is not None:
70             self.value = value
71         else:
72             self.value = type_.default_atom()
73
74     def __cmp__(self, other):
75         if not isinstance(other, Atom) or self.type != other.type:
76             return NotImplemented
77         elif self.value < other.value:
78             return -1
79         elif self.value > other.value:
80             return 1
81         else:
82             return 0
83
84     def __hash__(self):
85         return hash(self.value)
86
87     @staticmethod
88     def default(type_):
89         return Atom(type_)
90
91     def is_default(self):
92         return self == self.default(self.type)
93
94     @staticmethod
95     def from_json(base, json, symtab=None):
96         type_ = base.type
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))
105         else:
106             raise error.Error("expected %s" % type_.to_string(), json)
107         atom.check_constraints(base)
108         return atom
109
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.
113
114         'base' and 'atom' must have the same type.
115
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)):
126                 pass
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))
135             else:
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?
142             s = self.value
143             length = len(s)
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))
152     
153     def to_json(self):
154         if self.type == ovs.db.types.UuidType:
155             return self.value.to_json()
156         else:
157             return self.value
158
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:
165             if self.value:
166                 return ['%s.boolean = true;']
167             else:
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)
174
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:
181             if self.value:
182                 return 'true'
183             else:
184                 return 'false'
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
189
190     __need_quotes_re = re.compile("$|true|false|[^_a-zA-Z]|.*[^-._a-zA-Z]")
191     @staticmethod
192     def __string_needs_quotes(s):
193         return Atom.__need_quotes_re.match(s)
194
195     def to_string(self):
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:
201             if self.value:
202                 return 'true'
203             else:
204                 return 'false'
205         elif self.type == ovs.db.types.StringType:
206             if Atom.__string_needs_quotes(self.value):
207                 return ovs.json.to_string(self.value)
208             else:
209                 return self.value
210         elif self.type == ovs.db.types.UuidType:
211             return str(self.value)
212
213     @staticmethod
214     def new(x):
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
225         else:
226             raise TypeError
227         return Atom(t, x)
228
229 class Datum(object):
230     def __init__(self, type_, values={}):
231         self.type = type_
232         self.values = values
233
234     def __cmp__(self, other):
235         if not isinstance(other, Datum):
236             return NotImplemented
237         elif self.values < other.values:
238             return -1
239         elif self.values > other.values:
240             return 1
241         else:
242             return 0
243
244     __hash__ = None
245
246     def __contains__(self, item):
247         return item in self.values
248
249     def clone(self):
250         return Datum(self.type, dict(self.values))
251
252     @staticmethod
253     def default(type_):
254         if type_.n_min == 0:
255             values = {}
256         elif type_.is_map():
257             values = {type_.key.default(): type_.value.default()}
258         else:
259             values = {type_.key.default(): None}
260         return Datum(type_, values)
261
262     def is_default(self):
263         return self == Datum.default(self.type)
264
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.
268
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()
276
277     @staticmethod
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
281         ovs.db.error.Error.
282         
283         Violations of constraints expressed by 'type' are treated as errors.
284         
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()
289         if (is_map or
290             (type(json) == list and len(json) > 0 and json[0] == "set")):
291             if is_map:
292                 class_ = "map"
293             else:
294                 class_ = "set"
295
296             inner = ovs.db.parser.unwrap_json(json, class_, list)
297             n = len(inner)
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,
301                                                type_.n_max, n),
302                                   json)
303
304             values = {}
305             for element in inner:
306                 if is_map:
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)
310                 else:
311                     keyAtom = Atom.from_json(type_.key, element, symtab)
312                     valueAtom = None
313
314                 if keyAtom in values:
315                     if is_map:
316                         raise error.Error("map contains duplicate key")
317                     else:
318                         raise error.Error("set contains duplicate")
319
320                 values[keyAtom] = valueAtom
321
322             return Datum(type_, values)
323         else:
324             keyAtom = Atom.from_json(type_.key, json, symtab)
325             return Datum(type_, {keyAtom: None})
326
327     def to_json(self):
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]
333             return key.to_json()
334         else:
335             return ["set", [k.to_json() for k in sorted(self.values.keys())]]
336
337     def to_string(self):
338         head = tail = None
339         if self.type.n_max > 1 or len(self.values) == 0:
340             if self.type.is_map():
341                 head = "{"
342                 tail = "}"
343             else:
344                 head = "["
345                 tail = "]"
346
347         s = []
348         if head:
349             s.append(head)
350
351         for i, key in enumerate(sorted(self.values)):
352             if i:
353                 s.append(", ")
354
355             s.append(key.to_string())
356             if self.type.is_map():
357                 s.append("=")
358                 s.append(self.values[key].to_string())
359
360         if tail:
361             s.append(tail)
362         return ''.join(s)
363
364     def as_list(self):
365         if self.type.is_map():
366             return [[k.value, v.value] for k, v in self.values.iteritems()]
367         else:
368             return [k.value for k in self.values.iterkeys()]
369         
370     def as_scalar(self):
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]
375             else:
376                 return self.values.keys()[0].value
377         else:
378             return None
379
380     def __getitem__(self, key):
381         if not isinstance(key, Atom):
382             key = Atom.new(key)
383         if not self.type.is_map():
384             raise IndexError
385         elif key not in self.values:
386             raise KeyError
387         else:
388             return self.values[key].value
389
390     def get(self, key, default=None):
391         if not isinstance(key, Atom):
392             key = Atom.new(key)
393         if key in self.values:
394             return self.values[key].value
395         else:
396             return default
397         
398     def __str__(self):
399         return self.to_string()
400
401     def conforms_to_type(self):
402         n = len(self.values)
403         return self.type.n_min <= n <= self.type.n_max
404
405     def cInitDatum(self, var):
406         if len(self.values) == 0:
407             return ["ovsdb_datum_init_empty(%s);" % var]
408
409         s = ["%s->n = %d;" % (var, len(self.values))]
410         s += ["%s->keys = xmalloc(%d * sizeof *%s->keys);"
411               % (var, len(self.values), var)]
412
413         for i, key in enumerate(sorted(self.values)):
414             s += key.cInitAtom("%s->keys[%d]" % (var, i))
415         
416         if self.type.value:
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))
421         else:
422             s += ["%s->values = NULL;" % var]
423
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())]
427
428         return s