Global replace of Nicira Networks.
[sliver-openvswitch.git] / python / ovs / db / types.py
1 # Copyright (c) 2009, 2010, 2011, 2012 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) == 2:
291                 english = 'either %s or %s' % (literals[0], literals[1])
292             else:
293                 english = 'one of %s, %s, or %s' % (literals[0],
294                                                     ', '.join(literals[1:-1]),
295                                                     literals[-1])
296         elif self.min is not None and self.max is not None:
297             if self.type == IntegerType:
298                 english = 'in range %s to %s' % (
299                     escapeNumber(commafy(self.min)),
300                     escapeNumber(commafy(self.max)))
301             else:
302                 english = 'in range %s to %s' % (
303                     escapeNumber("%g" % self.min),
304                     escapeNumber("%g" % self.max))
305         elif self.min is not None:
306             if self.type == IntegerType:
307                 english = 'at least %s' % escapeNumber(commafy(self.min))
308             else:
309                 english = 'at least %s' % escapeNumber("%g" % self.min)
310         elif self.max is not None:
311             if self.type == IntegerType:
312                 english = 'at most %s' % escapeNumber(commafy(self.max))
313             else:
314                 english = 'at most %s' % escapeNumber("%g" % self.max)
315         elif self.min_length != 0 and self.max_length != sys.maxint:
316             if self.min_length == self.max_length:
317                 english = ('exactly %s characters long'
318                            % commafy(self.min_length))
319             else:
320                 english = ('between %s and %s characters long'
321                         % (commafy(self.min_length),
322                            commafy(self.max_length)))
323         elif self.min_length != 0:
324             return 'at least %s characters long' % commafy(self.min_length)
325         elif self.max_length != sys.maxint:
326             english = 'at most %s characters long' % commafy(self.max_length)
327         else:
328             english = ''
329
330         return english
331
332     def toCType(self, prefix):
333         if self.ref_table_name:
334             return "struct %s%s *" % (prefix, self.ref_table_name.lower())
335         else:
336             return {IntegerType: 'int64_t ',
337                     RealType: 'double ',
338                     UuidType: 'struct uuid ',
339                     BooleanType: 'bool ',
340                     StringType: 'char *'}[self.type]
341
342     def toAtomicType(self):
343         return "OVSDB_TYPE_%s" % self.type.to_string().upper()
344
345     def copyCValue(self, dst, src):
346         args = {'dst': dst, 'src': src}
347         if self.ref_table_name:
348             return ("%(dst)s = %(src)s->header_.uuid;") % args
349         elif self.type == StringType:
350             return "%(dst)s = xstrdup(%(src)s);" % args
351         else:
352             return "%(dst)s = %(src)s;" % args
353
354     def initCDefault(self, var, is_optional):
355         if self.ref_table_name:
356             return "%s = NULL;" % var
357         elif self.type == StringType and not is_optional:
358             return '%s = "";' % var
359         else:
360             pattern = {IntegerType: '%s = 0;',
361                        RealType: '%s = 0.0;',
362                        UuidType: 'uuid_zero(&%s);',
363                        BooleanType: '%s = false;',
364                        StringType: '%s = NULL;'}[self.type]
365             return pattern % var
366
367     def cInitBaseType(self, indent, var):
368         stmts = []
369         stmts.append('ovsdb_base_type_init(&%s, %s);' % (
370                 var, self.toAtomicType()))
371         if self.enum:
372             stmts.append("%s.enum_ = xmalloc(sizeof *%s.enum_);"
373                          % (var, var))
374             stmts += self.enum.cInitDatum("%s.enum_" % var)
375         if self.type == IntegerType:
376             if self.min is not None:
377                 stmts.append('%s.u.integer.min = INT64_C(%d);'
378                         % (var, self.min))
379             if self.max is not None:
380                 stmts.append('%s.u.integer.max = INT64_C(%d);'
381                         % (var, self.max))
382         elif self.type == RealType:
383             if self.min is not None:
384                 stmts.append('%s.u.real.min = %d;' % (var, self.min))
385             if self.max is not None:
386                 stmts.append('%s.u.real.max = %d;' % (var, self.max))
387         elif self.type == StringType:
388             if self.min_length is not None:
389                 stmts.append('%s.u.string.minLen = %d;'
390                         % (var, self.min_length))
391             if self.max_length != sys.maxint:
392                 stmts.append('%s.u.string.maxLen = %d;'
393                         % (var, self.max_length))
394         elif self.type == UuidType:
395             if self.ref_table_name is not None:
396                 stmts.append('%s.u.uuid.refTableName = "%s";'
397                         % (var, escapeCString(self.ref_table_name)))
398                 stmts.append('%s.u.uuid.refType = OVSDB_REF_%s;'
399                         % (var, self.ref_type.upper()))
400         return '\n'.join([indent + stmt for stmt in stmts])
401
402
403 class Type(object):
404     DEFAULT_MIN = 1
405     DEFAULT_MAX = 1
406
407     def __init__(self, key, value=None, n_min=DEFAULT_MIN, n_max=DEFAULT_MAX):
408         self.key = key
409         self.value = value
410         self.n_min = n_min
411         self.n_max = n_max
412
413     def copy(self):
414         if self.value is None:
415             value = None
416         else:
417             value = self.value.copy()
418         return Type(self.key.copy(), value, self.n_min, self.n_max)
419
420     def __eq__(self, other):
421         if not isinstance(other, Type):
422             return NotImplemented
423         return (self.key == other.key and self.value == other.value and
424                 self.n_min == other.n_min and self.n_max == other.n_max)
425
426     def __ne__(self, other):
427         if not isinstance(other, Type):
428             return NotImplemented
429         else:
430             return not (self == other)
431
432     def is_valid(self):
433         return (self.key.type != VoidType and self.key.is_valid() and
434                 (self.value is None or
435                  (self.value.type != VoidType and self.value.is_valid())) and
436                 self.n_min <= 1 <= self.n_max)
437
438     def is_scalar(self):
439         return self.n_min == 1 and self.n_max == 1 and not self.value
440
441     def is_optional(self):
442         return self.n_min == 0 and self.n_max == 1
443
444     def is_composite(self):
445         return self.n_max > 1
446
447     def is_set(self):
448         return self.value is None and (self.n_min != 1 or self.n_max != 1)
449
450     def is_map(self):
451         return self.value is not None
452
453     def is_optional_pointer(self):
454         return (self.is_optional() and not self.value
455                 and (self.key.type == StringType or self.key.ref_table_name))
456
457     @staticmethod
458     def __n_from_json(json, default):
459         if json is None:
460             return default
461         elif type(json) == int and 0 <= json <= sys.maxint:
462             return json
463         else:
464             raise error.Error("bad min or max value", json)
465
466     @staticmethod
467     def from_json(json):
468         if type(json) in [str, unicode]:
469             return Type(BaseType.from_json(json))
470
471         parser = ovs.db.parser.Parser(json, "ovsdb type")
472         key_json = parser.get("key", [dict, str, unicode])
473         value_json = parser.get_optional("value", [dict, str, unicode])
474         min_json = parser.get_optional("min", [int])
475         max_json = parser.get_optional("max", [int, str, unicode])
476         parser.finish()
477
478         key = BaseType.from_json(key_json)
479         if value_json:
480             value = BaseType.from_json(value_json)
481         else:
482             value = None
483
484         n_min = Type.__n_from_json(min_json, Type.DEFAULT_MIN)
485
486         if max_json == 'unlimited':
487             n_max = sys.maxint
488         else:
489             n_max = Type.__n_from_json(max_json, Type.DEFAULT_MAX)
490
491         type_ = Type(key, value, n_min, n_max)
492         if not type_.is_valid():
493             raise error.Error("ovsdb type fails constraint checks", json)
494         return type_
495
496     def to_json(self):
497         if self.is_scalar() and not self.key.has_constraints():
498             return self.key.to_json()
499
500         json = {"key": self.key.to_json()}
501         if self.value is not None:
502             json["value"] = self.value.to_json()
503         if self.n_min != Type.DEFAULT_MIN:
504             json["min"] = self.n_min
505         if self.n_max == sys.maxint:
506             json["max"] = "unlimited"
507         elif self.n_max != Type.DEFAULT_MAX:
508             json["max"] = self.n_max
509         return json
510
511     def toEnglish(self, escapeLiteral=returnUnchanged):
512         keyName = self.key.toEnglish(escapeLiteral)
513         if self.value:
514             valueName = self.value.toEnglish(escapeLiteral)
515
516         if self.is_scalar():
517             return keyName
518         elif self.is_optional():
519             if self.value:
520                 return "optional %s-%s pair" % (keyName, valueName)
521             else:
522                 return "optional %s" % keyName
523         else:
524             if self.n_max == sys.maxint:
525                 if self.n_min:
526                     quantity = "%s or more " % commafy(self.n_min)
527                 else:
528                     quantity = ""
529             elif self.n_min:
530                 quantity = "%s to %s " % (commafy(self.n_min),
531                                           commafy(self.n_max))
532             else:
533                 quantity = "up to %s " % commafy(self.n_max)
534
535             if self.value:
536                 return "map of %s%s-%s pairs" % (quantity, keyName, valueName)
537             else:
538                 if keyName.endswith('s'):
539                     plural = keyName + "es"
540                 else:
541                     plural = keyName + "s"
542                 return "set of %s%s" % (quantity, plural)
543
544     def constraintsToEnglish(self, escapeLiteral=returnUnchanged,
545                              escapeNumber=returnUnchanged):
546         constraints = []
547         keyConstraints = self.key.constraintsToEnglish(escapeLiteral,
548                                                        escapeNumber)
549         if keyConstraints:
550             if self.value:
551                 constraints.append('key %s' % keyConstraints)
552             else:
553                 constraints.append(keyConstraints)
554
555         if self.value:
556             valueConstraints = self.value.constraintsToEnglish(escapeLiteral,
557                                                                escapeNumber)
558             if valueConstraints:
559                 constraints.append('value %s' % valueConstraints)
560
561         return ', '.join(constraints)
562
563     def cDeclComment(self):
564         if self.n_min == 1 and self.n_max == 1 and self.key.type == StringType:
565             return "\t/* Always nonnull. */"
566         else:
567             return ""
568
569     def cInitType(self, indent, var):
570         initKey = self.key.cInitBaseType(indent, "%s.key" % var)
571         if self.value:
572             initValue = self.value.cInitBaseType(indent, "%s.value" % var)
573         else:
574             initValue = ('%sovsdb_base_type_init(&%s.value, '
575                          'OVSDB_TYPE_VOID);' % (indent, var))
576         initMin = "%s%s.n_min = %s;" % (indent, var, self.n_min)
577         if self.n_max == sys.maxint:
578             n_max = "UINT_MAX"
579         else:
580             n_max = self.n_max
581         initMax = "%s%s.n_max = %s;" % (indent, var, n_max)
582         return "\n".join((initKey, initValue, initMin, initMax))