e0e0daf5ec60b7bd093c4da8247676cbeadf2fec
[sliver-openvswitch.git] / python / ovs / db / schema.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 sys
17
18 from ovs.db import error
19 import ovs.db.parser
20 from ovs.db import types
21
22 class DbSchema(object):
23     """Schema for an OVSDB database."""
24
25     def __init__(self, name, version, tables):
26         self.name = name
27         self.version = version
28         self.tables = tables
29
30         # Validate that all ref_tables refer to the names of tables
31         # that exist.
32         for table in self.tables.itervalues():
33             for column in table.columns.itervalues():
34                 self.__check_ref_table(column, column.type.key, "key")
35                 self.__check_ref_table(column, column.type.value, "value")
36
37         # "isRoot" was not part of the original schema definition.  Before it
38         # was added, there was no support for garbage collection.  So, for
39         # backward compatibility, if the root set is empty then assume that
40         # every table is in the root set.
41         if self.__root_set_size() == 0:
42             for table in self.tables.itervalues():
43                 table.is_root = True
44
45     def __root_set_size(self):
46         """Returns the number of tables in the schema's root set."""
47         n_root = 0
48         for table in self.tables.itervalues():
49             if table.is_root:
50                 n_root += 1
51         return n_root
52
53     @staticmethod
54     def from_json(json):
55         parser = ovs.db.parser.Parser(json, "database schema")
56         name = parser.get("name", ['id'])
57         version = parser.get_optional("version", [unicode])
58         parser.get_optional("cksum", [unicode])
59         tablesJson = parser.get("tables", [dict])
60         parser.finish()
61
62         if (version is not None and
63             not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
64             raise error.Error("schema version \"%s\" not in format x.y.z"
65                               % version)
66
67         tables = {}
68         for tableName, tableJson in tablesJson.iteritems():
69             if tableName.startswith('_'):
70                 raise error.Error("names beginning with \"_\" are reserved",
71                                   json)
72             elif not ovs.db.parser.is_identifier(tableName):
73                 raise error.Error("name must be a valid id", json)
74             tables[tableName] = TableSchema.from_json(tableJson, tableName)
75
76         return DbSchema(name, version, tables)
77
78     def to_json(self):
79         # "isRoot" was not part of the original schema definition.  Before it
80         # was added, there was no support for garbage collection.  So, for
81         # backward compatibility, if every table is in the root set then do not
82         # output "isRoot" in table schemas.
83         default_is_root = self.__root_set_size() == len(self.tables)
84
85         tables = {}
86         for table in self.tables.itervalues():
87             tables[table.name] = table.to_json(default_is_root)
88         json = {"name": self.name, "tables": tables}
89         if self.version:
90             json["version"] = self.version
91         return json
92
93     def __check_ref_table(self, column, base, base_name):
94         if (base and base.type == types.UuidType and base.ref_table and
95             base.ref_table not in self.tables):
96             raise error.Error("column %s %s refers to undefined table %s"
97                               % (column.name, base_name, base.ref_table),
98                               tag="syntax error")
99
100 class IdlSchema(DbSchema):
101     def __init__(self, name, version, tables, idlPrefix, idlHeader):
102         DbSchema.__init__(self, name, version, tables)
103         self.idlPrefix = idlPrefix
104         self.idlHeader = idlHeader
105
106     @staticmethod
107     def from_json(json):
108         parser = ovs.db.parser.Parser(json, "IDL schema")
109         idlPrefix = parser.get("idlPrefix", [unicode])
110         idlHeader = parser.get("idlHeader", [unicode])
111
112         subjson = dict(json)
113         del subjson["idlPrefix"]
114         del subjson["idlHeader"]
115         schema = DbSchema.from_json(subjson)
116
117         return IdlSchema(schema.name, schema.version, schema.tables,
118                          idlPrefix, idlHeader)
119
120 class TableSchema(object):
121     def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
122                  is_root=True):
123         self.name = name
124         self.columns = columns
125         self.mutable = mutable
126         self.max_rows = max_rows
127         self.is_root = is_root
128
129     @staticmethod
130     def from_json(json, name):
131         parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
132         columnsJson = parser.get("columns", [dict])
133         mutable = parser.get_optional("mutable", [bool], True)
134         max_rows = parser.get_optional("maxRows", [int])
135         is_root = parser.get_optional("isRoot", [bool], False)
136         parser.finish()
137
138         if max_rows == None:
139             max_rows = sys.maxint
140         elif max_rows <= 0:
141             raise error.Error("maxRows must be at least 1", json)
142
143         if not columnsJson:
144             raise error.Error("table must have at least one column", json)
145
146         columns = {}
147         for columnName, columnJson in columnsJson.iteritems():
148             if columnName.startswith('_'):
149                 raise error.Error("names beginning with \"_\" are reserved",
150                                   json)
151             elif not ovs.db.parser.is_identifier(columnName):
152                 raise error.Error("name must be a valid id", json)
153             columns[columnName] = ColumnSchema.from_json(columnJson,
154                                                          columnName)
155
156         return TableSchema(name, columns, mutable, max_rows, is_root)
157
158     def to_json(self, default_is_root=False):
159         """Returns this table schema serialized into JSON.
160
161         The "isRoot" member is included in the JSON only if its value would
162         differ from 'default_is_root'.  Ordinarily 'default_is_root' should be
163         false, because ordinarily a table would be not be part of the root set
164         if its "isRoot" member is omitted.  However, garbage collection was not
165         orginally included in OVSDB, so in older schemas that do not include
166         any "isRoot" members, every table is implicitly part of the root set.
167         To serialize such a schema in a way that can be read by older OVSDB
168         tools, specify 'default_is_root' as True.
169         """
170         json = {}
171         if not self.mutable:
172             json["mutable"] = False
173         if default_is_root != self.is_root:
174             json["isRoot"] = self.is_root
175
176         json["columns"] = columns = {}
177         for column in self.columns.itervalues():
178             if not column.name.startswith("_"):
179                 columns[column.name] = column.to_json()
180
181         if self.max_rows != sys.maxint:
182             json["maxRows"] = self.max_rows
183
184         return json
185
186 class ColumnSchema(object):
187     def __init__(self, name, mutable, persistent, type):
188         self.name = name
189         self.mutable = mutable
190         self.persistent = persistent
191         self.type = type
192
193     @staticmethod
194     def from_json(json, name):
195         parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
196         mutable = parser.get_optional("mutable", [bool], True)
197         ephemeral = parser.get_optional("ephemeral", [bool], False)
198         type = types.Type.from_json(parser.get("type", [dict, unicode]))
199         parser.finish()
200
201         return ColumnSchema(name, mutable, not ephemeral, type)
202
203     def to_json(self):
204         json = {"type": self.type.to_json()}
205         if not self.mutable:
206             json["mutable"] = False
207         if not self.persistent:
208             json["ephemeral"] = True
209         return json
210