1 # Copyright (c) 2009, 2010, 2011 Nicira, Inc.
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:
7 # http://www.apache.org/licenses/LICENSE-2.0
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.
20 from ovs.db import error
25 def _check_id(name, json):
26 if name.startswith('_'):
27 raise error.Error('names beginning with "_" are reserved', json)
28 elif not ovs.db.parser.is_identifier(name):
29 raise error.Error("name must be a valid id", json)
32 class DbSchema(object):
33 """Schema for an OVSDB database."""
35 def __init__(self, name, version, tables):
37 self.version = version
40 # "isRoot" was not part of the original schema definition. Before it
41 # was added, there was no support for garbage collection. So, for
42 # backward compatibility, if the root set is empty then assume that
43 # every table is in the root set.
44 if self.__root_set_size() == 0:
45 for table in six.itervalues(self.tables):
48 # Find the "ref_table"s referenced by "ref_table_name"s.
50 # Also force certain columns to be persistent, as explained in
51 # __check_ref_table(). This requires 'is_root' to be known, so this
52 # must follow the loop updating 'is_root' above.
53 for table in six.itervalues(self.tables):
54 for column in six.itervalues(table.columns):
55 self.__follow_ref_table(column, column.type.key, "key")
56 self.__follow_ref_table(column, column.type.value, "value")
58 def __root_set_size(self):
59 """Returns the number of tables in the schema's root set."""
61 for table in six.itervalues(self.tables):
68 parser = ovs.db.parser.Parser(json, "database schema")
69 name = parser.get("name", ['id'])
70 version = parser.get_optional("version", [str, unicode])
71 parser.get_optional("cksum", [str, unicode])
72 tablesJson = parser.get("tables", [dict])
75 if (version is not None and
76 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
77 raise error.Error('schema version "%s" not in format x.y.z'
81 for tableName, tableJson in six.iteritems(tablesJson):
82 _check_id(tableName, json)
83 tables[tableName] = TableSchema.from_json(tableJson, tableName)
85 return DbSchema(name, version, tables)
88 # "isRoot" was not part of the original schema definition. Before it
89 # was added, there was no support for garbage collection. So, for
90 # backward compatibility, if every table is in the root set then do not
91 # output "isRoot" in table schemas.
92 default_is_root = self.__root_set_size() == len(self.tables)
95 for table in six.itervalues(self.tables):
96 tables[table.name] = table.to_json(default_is_root)
97 json = {"name": self.name, "tables": tables}
99 json["version"] = self.version
103 return DbSchema.from_json(self.to_json())
105 def __follow_ref_table(self, column, base, base_name):
106 if (not base or base.type != ovs.db.types.UuidType
107 or not base.ref_table_name):
110 base.ref_table = self.tables.get(base.ref_table_name)
111 if not base.ref_table:
112 raise error.Error("column %s %s refers to undefined table %s"
113 % (column.name, base_name, base.ref_table_name),
116 if base.is_strong_ref() and not base.ref_table.is_root:
117 # We cannot allow a strong reference to a non-root table to be
118 # ephemeral: if it is the only reference to a row, then replaying
119 # the database log from disk will cause the referenced row to be
120 # deleted, even though it did exist in memory. If there are
121 # references to that row later in the log (to modify it, to delete
122 # it, or just to point to it), then this will yield a transaction
124 column.persistent = True
127 class IdlSchema(DbSchema):
128 def __init__(self, name, version, tables, idlPrefix, idlHeader):
129 DbSchema.__init__(self, name, version, tables)
130 self.idlPrefix = idlPrefix
131 self.idlHeader = idlHeader
135 parser = ovs.db.parser.Parser(json, "IDL schema")
136 idlPrefix = parser.get("idlPrefix", [str, unicode])
137 idlHeader = parser.get("idlHeader", [str, unicode])
140 del subjson["idlPrefix"]
141 del subjson["idlHeader"]
142 schema = DbSchema.from_json(subjson)
144 return IdlSchema(schema.name, schema.version, schema.tables,
145 idlPrefix, idlHeader)
148 def column_set_from_json(json, columns):
150 return tuple(columns)
151 elif type(json) != list:
152 raise error.Error("array of distinct column names expected", json)
154 for column_name in json:
155 if type(column_name) not in [str, unicode]:
156 raise error.Error("array of distinct column names expected",
158 elif column_name not in columns:
159 raise error.Error("%s is not a valid column name"
161 if len(set(json)) != len(json):
163 raise error.Error("array of distinct column names expected", json)
164 return tuple([columns[column_name] for column_name in json])
167 class TableSchema(object):
168 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
169 is_root=True, indexes=[]):
171 self.columns = columns
172 self.mutable = mutable
173 self.max_rows = max_rows
174 self.is_root = is_root
175 self.indexes = indexes
178 def from_json(json, name):
179 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
180 columns_json = parser.get("columns", [dict])
181 mutable = parser.get_optional("mutable", [bool], True)
182 max_rows = parser.get_optional("maxRows", [int])
183 is_root = parser.get_optional("isRoot", [bool], False)
184 indexes_json = parser.get_optional("indexes", [list], [])
188 max_rows = sys.maxint
190 raise error.Error("maxRows must be at least 1", json)
193 raise error.Error("table must have at least one column", json)
196 for column_name, column_json in six.iteritems(columns_json):
197 _check_id(column_name, json)
198 columns[column_name] = ColumnSchema.from_json(column_json,
202 for index_json in indexes_json:
203 index = column_set_from_json(index_json, columns)
205 raise error.Error("index must have at least one column", json)
206 elif len(index) == 1:
207 index[0].unique = True
209 if not column.persistent:
210 raise error.Error("ephemeral columns (such as %s) may "
211 "not be indexed" % column.name, json)
212 indexes.append(index)
214 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
216 def to_json(self, default_is_root=False):
217 """Returns this table schema serialized into JSON.
219 The "isRoot" member is included in the JSON only if its value would
220 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
221 false, because ordinarily a table would be not be part of the root set
222 if its "isRoot" member is omitted. However, garbage collection was not
223 originally included in OVSDB, so in older schemas that do not include
224 any "isRoot" members, every table is implicitly part of the root set.
225 To serialize such a schema in a way that can be read by older OVSDB
226 tools, specify 'default_is_root' as True.
230 json["mutable"] = False
231 if default_is_root != self.is_root:
232 json["isRoot"] = self.is_root
234 json["columns"] = columns = {}
235 for column in six.itervalues(self.columns):
236 if not column.name.startswith("_"):
237 columns[column.name] = column.to_json()
239 if self.max_rows != sys.maxint:
240 json["maxRows"] = self.max_rows
244 for index in self.indexes:
245 json["indexes"].append([column.name for column in index])
250 class ColumnSchema(object):
251 def __init__(self, name, mutable, persistent, type_):
253 self.mutable = mutable
254 self.persistent = persistent
259 def from_json(json, name):
260 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
261 mutable = parser.get_optional("mutable", [bool], True)
262 ephemeral = parser.get_optional("ephemeral", [bool], False)
263 type_ = ovs.db.types.Type.from_json(parser.get("type",
264 [dict, str, unicode]))
267 return ColumnSchema(name, mutable, not ephemeral, type_)
270 json = {"type": self.type.to_json()}
272 json["mutable"] = False
273 if not self.persistent:
274 json["ephemeral"] = True