Setting tag sliver-openvswitch-2.2.90-1
[sliver-openvswitch.git] / python / ovs / db / types.py
1 # Copyright (c) 2009, 2010, 2011, 2012, 2013 Nicira, Inc.
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 sys
16 import uuid
17
18 from ovs.db import error
19 import ovs.db.parser
20 import ovs.db.data
21 import ovs.ovsuuid
22
23
24 class AtomicType(object):
25     def __init__(self, name, default, python_types):
26         self.name = name
27         self.default = default
28         self.python_types = python_types
29
30     @staticmethod
31     def from_string(s):
32         if s != "void":
33             for atomic_type in ATOMIC_TYPES:
34                 if s == atomic_type.name:
35                     return atomic_type
36         raise error.Error('"%s" is not an atomic-type' % s, s)
37
38     @staticmethod
39     def from_json(json):
40         if type(json) not in [str, unicode]:
41             raise error.Error("atomic-type expected", json)
42         else:
43             return AtomicType.from_string(json)
44
45     def __str__(self):
46         return self.name
47
48     def to_string(self):
49         return self.name
50
51     def to_json(self):
52         return self.name
53
54     def default_atom(self):
55         return ovs.db.data.Atom(self, self.default)
56
57 VoidType = AtomicType("void", None, ())
58 IntegerType = AtomicType("integer", 0, (int, long))
59 RealType = AtomicType("real", 0.0, (int, long, float))
60 BooleanType = AtomicType("boolean", False, (bool,))
61 StringType = AtomicType("string", "", (str, unicode))
62 UuidType = AtomicType("uuid", ovs.ovsuuid.zero(), (uuid.UUID,))
63
64 ATOMIC_TYPES = [VoidType, IntegerType, RealType, BooleanType, StringType,
65                 UuidType]
66
67
68 def escapeCString(src):
69     dst = ""
70     for c in src:
71         if c in "\\\"":
72             dst += "\\" + c
73         elif ord(c) < 32:
74             if c == '\n':
75                 dst += '\\n'
76             elif c == '\r':
77                 dst += '\\r'
78             elif c == '\a':
79                 dst += '\\a'
80             elif c == '\b':
81                 dst += '\\b'
82             elif c == '\f':
83                 dst += '\\f'
84             elif c == '\t':
85                 dst += '\\t'
86             elif c == '\v':
87                 dst += '\\v'
88             else:
89                 dst += '\\%03o' % ord(c)
90         else:
91             dst += c
92     return dst
93
94
95 def commafy(x):
96     """Returns integer x formatted in decimal with thousands set off by
97     commas."""
98     return _commafy("%d" % x)
99
100
101 def _commafy(s):
102     if s.startswith('-'):
103         return '-' + _commafy(s[1:])
104     elif len(s) <= 3:
105         return s
106     else:
107         return _commafy(s[:-3]) + ',' + _commafy(s[-3:])
108
109
110 def returnUnchanged(x):
111     return x
112
113
114 class BaseType(object):
115     def __init__(self, type_, enum=None, min=None, max=None,
116                  min_length=0, max_length=sys.maxint, ref_table_name=None):
117         assert isinstance(type_, AtomicType)
118         self.type = type_
119         self.enum = enum
120         self.min = min
121         self.max = max
122         self.min_length = min_length
123         self.max_length = max_length
124         self.ref_table_name = ref_table_name
125         if ref_table_name:
126             self.ref_type = 'strong'
127         else:
128             self.ref_type = None
129         self.ref_table = None
130
131     def default(self):
132         return ovs.db.data.Atom.default(self.type)
133
134     def __eq__(self, other):
135         if not isinstance(other, BaseType):
136             return NotImplemented
137         return (self.type == other.type and self.enum == other.enum and
138                 self.min == other.min and self.max == other.max and
139                 self.min_length == other.min_length and
140                 self.max_length == other.max_length and
141                 self.ref_table_name == other.ref_table_name)
142
143     def __ne__(self, other):
144         if not isinstance(other, BaseType):
145             return NotImplemented
146         else:
147             return not (self == other)
148
149     @staticmethod
150     def __parse_uint(parser, name, default):
151         value = parser.get_optional(name, [int, long])
152         if value is None:
153             value = default
154         else:
155             max_value = 2 ** 32 - 1
156             if not (0 <= value <= max_value):
157                 raise error.Error("%s out of valid range 0 to %d"
158                                   % (name, max_value), value)
159         return value
160
161     @staticmethod
162     def from_json(json):
163         if type(json) in [str, unicode]:
164             return BaseType(AtomicType.from_json(json))
165
166         parser = ovs.db.parser.Parser(json, "ovsdb type")
167         atomic_type = AtomicType.from_json(parser.get("type", [str, unicode]))
168
169         base = BaseType(atomic_type)
170
171         enum = parser.get_optional("enum", [])
172         if enum is not None:
173             base.enum = ovs.db.data.Datum.from_json(
174                     BaseType.get_enum_type(base.type), enum)
175         elif base.type == IntegerType:
176             base.min = parser.get_optional("minInteger", [int, long])
177             base.max = parser.get_optional("maxInteger", [int, long])
178             if (base.min is not None and base.max is not None
179                     and base.min > base.max):
180                 raise error.Error("minInteger exceeds maxInteger", json)
181         elif base.type == RealType:
182             base.min = parser.get_optional("minReal", [int, long, float])
183             base.max = parser.get_optional("maxReal", [int, long, float])
184             if (base.min is not None and base.max is not None
185                     and base.min > base.max):
186                 raise error.Error("minReal exceeds maxReal", json)
187         elif base.type == StringType:
188             base.min_length = BaseType.__parse_uint(parser, "minLength", 0)
189             base.max_length = BaseType.__parse_uint(parser, "maxLength",
190                                                     sys.maxint)
191             if base.min_length > base.max_length:
192                 raise error.Error("minLength exceeds maxLength", json)
193         elif base.type == UuidType:
194             base.ref_table_name = parser.get_optional("refTable", ['id'])
195             if base.ref_table_name:
196                 base.ref_type = parser.get_optional("refType", [str, unicode],
197                                                    "strong")
198                 if base.ref_type not in ['strong', 'weak']:
199                     raise error.Error('refType must be "strong" or "weak" '
200                                       '(not "%s")' % base.ref_type)
201         parser.finish()
202
203         return base
204
205     def to_json(self):
206         if not self.has_constraints():
207             return self.type.to_json()
208
209         json = {'type': self.type.to_json()}
210
211         if self.enum:
212             json['enum'] = self.enum.to_json()
213
214         if self.type == IntegerType:
215             if self.min is not None:
216                 json['minInteger'] = self.min
217             if self.max is not None:
218                 json['maxInteger'] = self.max
219         elif self.type == RealType:
220             if self.min is not None:
221                 json['minReal'] = self.min
222             if self.max is not None:
223                 json['maxReal'] = self.max
224         elif self.type == StringType:
225             if self.min_length != 0:
226                 json['minLength'] = self.min_length
227             if self.max_length != sys.maxint:
228                 json['maxLength'] = self.max_length
229         elif self.type == UuidType:
230             if self.ref_table_name:
231                 json['refTable'] = self.ref_table_name
232                 if self.ref_type != 'strong':
233                     json['refType'] = self.ref_type
234         return json
235
236     def copy(self):
237         base = BaseType(self.type, self.enum.copy(), self.min, self.max,
238                         self.min_length, self.max_length, self.ref_table_name)
239         base.ref_table = self.ref_table
240         return base
241
242     def is_valid(self):
243         if self.type in (VoidType, BooleanType, UuidType):
244             return True
245         elif self.type in (IntegerType, RealType):
246             return self.min is None or self.max is None or self.min <= self.max
247         elif self.type == StringType:
248             return self.min_length <= self.max_length
249         else:
250             return False
251
252     def has_constraints(self):
253         return (self.enum is not None or self.min is not None or
254                 self.max is not None or
255                 self.min_length != 0 or self.max_length != sys.maxint or
256                 self.ref_table_name is not None)
257
258     def without_constraints(self):
259         return BaseType(self.type)
260
261     @staticmethod
262     def get_enum_type(atomic_type):
263         """Returns the type of the 'enum' member for a BaseType whose
264         'type' is 'atomic_type'."""
265         return Type(BaseType(atomic_type), None, 1, sys.maxint)
266
267     def is_ref(self):
268         return self.type == UuidType and self.ref_table_name is not None
269
270     def is_strong_ref(self):
271         return self.is_ref() and self.ref_type == 'strong'
272
273     def is_weak_ref(self):
274         return self.is_ref() and self.ref_type == 'weak'
275
276     def toEnglish(self, escapeLiteral=returnUnchanged):
277         if self.type == UuidType and self.ref_table_name:
278             s = escapeLiteral(self.ref_table_name)
279             if self.ref_type == 'weak':
280                 s = "weak reference to " + s
281             return s
282         else:
283             return self.type.to_string()
284
285     def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
286                              escapeNumber=returnUnchanged):
287         if self.enum:
288             literals = [value.toEnglish(escapeLiteral)
289                         for value in self.enum.values]
290             if len(literals) == 1:
291                 english = 'must be %s' % (literals[0])
292             elif len(literals) == 2:
293                 english = 'either %s or %s' % (literals[0], literals[1])
294             else:
295                 english = 'one of %s, %s, or %s' % (literals[0],
296                                                     ', '.join(literals[1:-1]),
297                                                     literals[-1])
298         elif self.min is not None and self.max is not None:
299             if self.type == IntegerType:
300                 english = 'in range %s to %s' % (
301                     escapeNumber(commafy(self.min)),
302                     escapeNumber(commafy(self.max)))
303             else:
304                 english = 'in range %s to %s' % (
305                     escapeNumber("%g" % self.min),
306                     escapeNumber("%g" % self.max))
307         elif self.min is not None:
308             if self.type == IntegerType:
309                 english = 'at least %s' % escapeNumber(commafy(self.min))
310             else:
311                 english = 'at least %s' % escapeNumber("%g" % self.min)
312         elif self.max is not None:
313             if self.type == IntegerType:
314                 english = 'at most %s' % escapeNumber(commafy(self.max))
315             else:
316                 english = 'at most %s' % escapeNumber("%g" % self.max)
317         elif self.min_length != 0 and self.max_length != sys.maxint:
318             if self.min_length == self.max_length:
319                 english = ('exactly %s characters long'
320                            % commafy(self.min_length))
321             else:
322                 english = ('between %s and %s characters long'
323                         % (commafy(self.min_length),
324                            commafy(self.max_length)))
325         elif self.min_length != 0:
326             return 'at least %s characters long' % commafy(self.min_length)
327         elif self.max_length != sys.maxint:
328             english = 'at most %s characters long' % commafy(self.max_length)
329         else:
330             english = ''
331
332         return english
333
334     def toCType(self, prefix):
335         if self.ref_table_name:
336             return "struct %s%s *" % (prefix, self.ref_table_name.lower())
337         else:
338             return {IntegerType: 'int64_t ',
339                     RealType: 'double ',
340                     UuidType: 'struct uuid ',
341                     BooleanType: 'bool ',
342                     StringType: 'char *'}[self.type]
343
344     def toAtomicType(self):
345         return "OVSDB_TYPE_%s" % self.type.to_string().upper()
346
347     def copyCValue(self, dst, src):
348         args = {'dst': dst, 'src': src}
349         if self.ref_table_name:
350             return ("%(dst)s = %(src)s->header_.uuid;") % args
351         elif self.type == StringType:
352             return "%(dst)s = xstrdup(%(src)s);" % args
353         else:
354             return "%(dst)s = %(src)s;" % args
355
356     def assign_c_value_casting_away_const(self, dst, src):
357         args = {'dst': dst, 'src': src}
358         if self.ref_table_name:
359             return ("%(dst)s = %(src)s->header_.uuid;") % args
360         elif self.type == StringType:
361             return "%(dst)s = CONST_CAST(char *, %(src)s);" % args
362         else:
363             return "%(dst)s = %(src)s;" % args
364
365     def initCDefault(self, var, is_optional):
366         if self.ref_table_name:
367             return "%s = NULL;" % var
368         elif self.type == StringType and not is_optional:
369             return '%s = "";' % var
370         else:
371             pattern = {IntegerType: '%s = 0;',
372                        RealType: '%s = 0.0;',
373                        UuidType: 'uuid_zero(&%s);',
374                        BooleanType: '%s = false;',
375                        StringType: '%s = NULL;'}[self.type]
376             return pattern % var
377
378     def cInitBaseType(self, indent, var):
379         stmts = []
380         stmts.append('ovsdb_base_type_init(&%s, %s);' % (
381                 var, self.toAtomicType()))
382         if self.enum:
383             stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
384                          % (var, var))
385             stmts += self.enum.cInitDatum("%s.enum_" % var)
386         if self.type == IntegerType:
387             if self.min is not None:
388                 stmts.append('%s.u.integer.min = INT64_C(%d);'
389                         % (var, self.min))
390             if self.max is not None:
391                 stmts.append('%s.u.integer.max = INT64_C(%d);'
392                         % (var, self.max))
393         elif self.type == RealType:
394             if self.min is not None:
395                 stmts.append('%s.u.real.min = %d;' % (var, self.min))
396             if self.max is not None:
397                 stmts.append('%s.u.real.max = %d;' % (var, self.max))
398         elif self.type == StringType:
399             if self.min_length is not None:
400                 stmts.append('%s.u.string.minLen = %d;'
401                         % (var, self.min_length))
402             if self.max_length != sys.maxint:
403                 stmts.append('%s.u.string.maxLen = %d;'
404                         % (var, self.max_length))
405         elif self.type == UuidType:
406             if self.ref_table_name is not None:
407                 stmts.append('%s.u.uuid.refTableName = "%s";'
408                         % (var, escapeCString(self.ref_table_name)))
409                 stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
410                         % (var, self.ref_type.upper()))
411         return '\n'.join([indent + stmt for stmt in stmts])
412
413
414 class Type(object):
415     DEFAULT_MIN = 1
416     DEFAULT_MAX = 1
417
418     def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
419         self.key = key
420         self.value = value
421         self.n_min = n_min
422         self.n_max = n_max
423
424     def copy(self):
425         if self.value is None:
426             value = None
427         else:
428             value = self.value.copy()
429         return Type(self.key.copy(), value, self.n_min, self.n_max)
430
431     def __eq__(self, other):
432         if not isinstance(other, Type):
433             return NotImplemented
434         return (self.key == other.key and self.value == other.value and
435                 self.n_min == other.n_min and self.n_max == other.n_max)
436
437     def __ne__(self, other):
438         if not isinstance(other, Type):
439             return NotImplemented
440         else:
441             return not (self == other)
442
443     def is_valid(self):
444         return (self.key.type != VoidType and self.key.is_valid() and
445                 (self.value is None or
446                  (self.value.type != VoidType and self.value.is_valid())) and
447                 self.n_min <= 1 <= self.n_max)
448
449     def is_scalar(self):
450         return self.n_min == 1 and self.n_max == 1 and not self.value
451
452     def is_optional(self):
453         return self.n_min == 0 and self.n_max == 1
454
455     def is_composite(self):
456         return self.n_max > 1
457
458     def is_set(self):
459         return self.value is None and (self.n_min != 1 or self.n_max != 1)
460
461     def is_map(self):
462         return self.value is not None
463
464     def is_smap(self):
465         return (self.is_map()
466                 and self.key.type == StringType
467                 and self.value.type == StringType)
468
469     def is_optional_pointer(self):
470         return (self.is_optional() and not self.value
471                 and (self.key.type == StringType or self.key.ref_table_name))
472
473     @staticmethod
474     def __n_from_json(json, default):
475         if json is None:
476             return default
477         elif type(json) == int and 0 <= json <= sys.maxint:
478             return json
479         else:
480             raise error.Error("bad min or max value", json)
481
482     @staticmethod
483     def from_json(json):
484         if type(json) in [str, unicode]:
485             return Type(BaseType.from_json(json))
486
487         parser = ovs.db.parser.Parser(json, "ovsdb type")
488         key_json = parser.get("key", [dict, str, unicode])
489         value_json = parser.get_optional("value", [dict, str, unicode])
490         min_json = parser.get_optional("min", [int])
491         max_json = parser.get_optional("max", [int, str, unicode])
492         parser.finish()
493
494         key = BaseType.from_json(key_json)
495         if value_json:
496             value = BaseType.from_json(value_json)
497         else:
498             value = None
499
500         n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
501
502         if max_json == 'unlimited':
503             n_max = sys.maxint
504         else:
505             n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
506
507         type_ = Type(key, value, n_min, n_max)
508         if not type_.is_valid():
509             raise error.Error("ovsdb type fails constraint checks", json)
510         return type_
511
512     def to_json(self):
513         if self.is_scalar() and not self.key.has_constraints():
514             return self.key.to_json()
515
516         json = {"key": self.key.to_json()}
517         if self.value is not None:
518             json["value"] = self.value.to_json()
519         if self.n_min != Type.DEFAULT_MIN:
520             json["min"] = self.n_min
521         if self.n_max == sys.maxint:
522             json["max"] = "unlimited"
523         elif self.n_max != Type.DEFAULT_MAX:
524             json["max"] = self.n_max
525         return json
526
527     def toEnglish(self, escapeLiteral=returnUnchanged):
528         keyName = self.key.toEnglish(escapeLiteral)
529         if self.value:
530             valueName = self.value.toEnglish(escapeLiteral)
531
532         if self.is_scalar():
533             return keyName
534         elif self.is_optional():
535             if self.value:
536                 return "optional %s-%s pair" % (keyName, valueName)
537             else:
538                 return "optional %s" % keyName
539         else:
540             if self.n_max == sys.maxint:
541                 if self.n_min:
542                     quantity = "%s or more " % commafy(self.n_min)
543                 else:
544                     quantity = ""
545             elif self.n_min:
546                 quantity = "%s to %s " % (commafy(self.n_min),
547                                           commafy(self.n_max))
548             else:
549                 quantity = "up to %s " % commafy(self.n_max)
550
551             if self.value:
552                 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
553             else:
554                 if keyName.endswith('s'):
555                     plural = keyName + "es"
556                 else:
557                     plural = keyName + "s"
558                 return "set of %s%s" % (quantity, plural)
559
560     def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
561                              escapeNumber=returnUnchanged):
562         constraints = []
563         keyConstraints = self.key.constraintsToEnglish(escapeLiteral,
564                                                        escapeNumber)
565         if keyConstraints:
566             if self.value:
567                 constraints.append('key %s' % keyConstraints)
568             else:
569                 constraints.append(keyConstraints)
570
571         if self.value:
572             valueConstraints = self.value.constraintsToEnglish(escapeLiteral,
573                                                                escapeNumber)
574             if valueConstraints:
575                 constraints.append('value %s' % valueConstraints)
576
577         return ', '.join(constraints)
578
579     def cDeclComment(self):
580         if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
581             return "\t/* Always nonnull. */"
582         else:
583             return ""
584
585     def cInitType(self, indent, var):
586         initKey = self.key.cInitBaseType(indent, "%s.key" % var)
587         if self.value:
588             initValue = self.value.cInitBaseType(indent, "%s.value" % var)
589         else:
590             initValue = ('%sovsdb_base_type_init(&%s.value, '
591                          'OVSDB_TYPE_VOID);' % (indent, var))
592         initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
593         if self.n_max == sys.maxint:
594             n_max = "UINT_MAX"
595         else:
596             n_max = self.n_max
597         initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
598         return "\n".join((initKey, initValue, initMin, initMax))