]> git.proxmox.com Git - ovs.git/blob - python/ovs/db/schema.py
7e18564e0367436d98b51b6590ba8dd7c8e3f5e4
[ovs.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 def _check_id(name, json):
23 if name.startswith('_'):
24 raise error.Error('names beginning with "_" are reserved', json)
25 elif not ovs.db.parser.is_identifier(name):
26 raise error.Error("name must be a valid id", json)
27
28 class DbSchema(object):
29 """Schema for an OVSDB database."""
30
31 def __init__(self, name, version, tables):
32 self.name = name
33 self.version = version
34 self.tables = tables
35
36 # "isRoot" was not part of the original schema definition. Before it
37 # was added, there was no support for garbage collection. So, for
38 # backward compatibility, if the root set is empty then assume that
39 # every table is in the root set.
40 if self.__root_set_size() == 0:
41 for table in self.tables.itervalues():
42 table.is_root = True
43
44 # Find the "ref_table"s referenced by "ref_table_name"s.
45 #
46 # Also force certain columns to be persistent, as explained in
47 # __check_ref_table(). This requires 'is_root' to be known, so this
48 # must follow the loop updating 'is_root' above.
49 for table in self.tables.itervalues():
50 for column in table.columns.itervalues():
51 self.__follow_ref_table(column, column.type.key, "key")
52 self.__follow_ref_table(column, column.type.value, "value")
53
54 def __root_set_size(self):
55 """Returns the number of tables in the schema's root set."""
56 n_root = 0
57 for table in self.tables.itervalues():
58 if table.is_root:
59 n_root += 1
60 return n_root
61
62 @staticmethod
63 def from_json(json):
64 parser = ovs.db.parser.Parser(json, "database schema")
65 name = parser.get("name", ['id'])
66 version = parser.get_optional("version", [str, unicode])
67 parser.get_optional("cksum", [str, unicode])
68 tablesJson = parser.get("tables", [dict])
69 parser.finish()
70
71 if (version is not None and
72 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
73 raise error.Error('schema version "%s" not in format x.y.z'
74 % version)
75
76 tables = {}
77 for tableName, tableJson in tablesJson.iteritems():
78 _check_id(tableName, json)
79 tables[tableName] = TableSchema.from_json(tableJson, tableName)
80
81 return DbSchema(name, version, tables)
82
83 def to_json(self):
84 # "isRoot" was not part of the original schema definition. Before it
85 # was added, there was no support for garbage collection. So, for
86 # backward compatibility, if every table is in the root set then do not
87 # output "isRoot" in table schemas.
88 default_is_root = self.__root_set_size() == len(self.tables)
89
90 tables = {}
91 for table in self.tables.itervalues():
92 tables[table.name] = table.to_json(default_is_root)
93 json = {"name": self.name, "tables": tables}
94 if self.version:
95 json["version"] = self.version
96 return json
97
98 def __follow_ref_table(self, column, base, base_name):
99 if not base or base.type != types.UuidType or not base.ref_table_name:
100 return
101
102 base.ref_table = self.tables.get(base.ref_table_name)
103 if not base.ref_table:
104 raise error.Error("column %s %s refers to undefined table %s"
105 % (column.name, base_name, base.ref_table_name),
106 tag="syntax error")
107
108 if base.is_strong_ref() and not base.ref_table.is_root:
109 # We cannot allow a strong reference to a non-root table to be
110 # ephemeral: if it is the only reference to a row, then replaying
111 # the database log from disk will cause the referenced row to be
112 # deleted, even though it did exist in memory. If there are
113 # references to that row later in the log (to modify it, to delete
114 # it, or just to point to it), then this will yield a transaction
115 # error.
116 column.persistent = True
117
118 class IdlSchema(DbSchema):
119 def __init__(self, name, version, tables, idlPrefix, idlHeader):
120 DbSchema.__init__(self, name, version, tables)
121 self.idlPrefix = idlPrefix
122 self.idlHeader = idlHeader
123
124 @staticmethod
125 def from_json(json):
126 parser = ovs.db.parser.Parser(json, "IDL schema")
127 idlPrefix = parser.get("idlPrefix", [str, unicode])
128 idlHeader = parser.get("idlHeader", [str, unicode])
129
130 subjson = dict(json)
131 del subjson["idlPrefix"]
132 del subjson["idlHeader"]
133 schema = DbSchema.from_json(subjson)
134
135 return IdlSchema(schema.name, schema.version, schema.tables,
136 idlPrefix, idlHeader)
137
138 def column_set_from_json(json, columns):
139 if json is None:
140 return tuple(columns)
141 elif type(json) != list:
142 raise error.Error("array of distinct column names expected", json)
143 else:
144 for column_name in json:
145 if type(column_name) not in [str, unicode]:
146 raise error.Error("array of distinct column names expected",
147 json)
148 elif column_name not in columns:
149 raise error.Error("%s is not a valid column name"
150 % column_name, json)
151 if len(set(json)) != len(json):
152 # Duplicate.
153 raise error.Error("array of distinct column names expected", json)
154 return tuple([columns[column_name] for column_name in json])
155
156 class TableSchema(object):
157 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
158 is_root=True, indexes=[]):
159 self.name = name
160 self.columns = columns
161 self.mutable = mutable
162 self.max_rows = max_rows
163 self.is_root = is_root
164 self.indexes = indexes
165
166 @staticmethod
167 def from_json(json, name):
168 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
169 columns_json = parser.get("columns", [dict])
170 mutable = parser.get_optional("mutable", [bool], True)
171 max_rows = parser.get_optional("maxRows", [int])
172 is_root = parser.get_optional("isRoot", [bool], False)
173 indexes_json = parser.get_optional("indexes", [list], [])
174 parser.finish()
175
176 if max_rows == None:
177 max_rows = sys.maxint
178 elif max_rows <= 0:
179 raise error.Error("maxRows must be at least 1", json)
180
181 if not columns_json:
182 raise error.Error("table must have at least one column", json)
183
184 columns = {}
185 for column_name, column_json in columns_json.iteritems():
186 _check_id(column_name, json)
187 columns[column_name] = ColumnSchema.from_json(column_json,
188 column_name)
189
190 indexes = []
191 for index_json in indexes_json:
192 index = column_set_from_json(index_json, columns)
193 if not index:
194 raise error.Error("index must have at least one column", json)
195 elif len(index) == 1:
196 index[0].unique = True
197 for column in index:
198 if not column.persistent:
199 raise error.Error("ephemeral columns (such as %s) may "
200 "not be indexed" % column.name, json)
201 indexes.append(index)
202
203 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
204
205 def to_json(self, default_is_root=False):
206 """Returns this table schema serialized into JSON.
207
208 The "isRoot" member is included in the JSON only if its value would
209 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
210 false, because ordinarily a table would be not be part of the root set
211 if its "isRoot" member is omitted. However, garbage collection was not
212 orginally included in OVSDB, so in older schemas that do not include
213 any "isRoot" members, every table is implicitly part of the root set.
214 To serialize such a schema in a way that can be read by older OVSDB
215 tools, specify 'default_is_root' as True.
216 """
217 json = {}
218 if not self.mutable:
219 json["mutable"] = False
220 if default_is_root != self.is_root:
221 json["isRoot"] = self.is_root
222
223 json["columns"] = columns = {}
224 for column in self.columns.itervalues():
225 if not column.name.startswith("_"):
226 columns[column.name] = column.to_json()
227
228 if self.max_rows != sys.maxint:
229 json["maxRows"] = self.max_rows
230
231 if self.indexes:
232 json["indexes"] = []
233 for index in self.indexes:
234 json["indexes"].append([column.name for column in index])
235
236 return json
237
238 class ColumnSchema(object):
239 def __init__(self, name, mutable, persistent, type_):
240 self.name = name
241 self.mutable = mutable
242 self.persistent = persistent
243 self.type = type_
244 self.unique = False
245
246 @staticmethod
247 def from_json(json, name):
248 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
249 mutable = parser.get_optional("mutable", [bool], True)
250 ephemeral = parser.get_optional("ephemeral", [bool], False)
251 type_ = types.Type.from_json(parser.get("type", [dict, str, unicode]))
252 parser.finish()
253
254 return ColumnSchema(name, mutable, not ephemeral, type_)
255
256 def to_json(self):
257 json = {"type": self.type.to_json()}
258 if not self.mutable:
259 json["mutable"] = False
260 if not self.persistent:
261 json["ephemeral"] = True
262 return json
263