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.
18 from ovs.db import error
23 def _check_id(name, json):
24 if name.startswith('_'):
25 raise error.Error('names beginning with "_" are reserved', json)
26 elif not ovs.db.parser.is_identifier(name):
27 raise error.Error("name must be a valid id", json)
30 class DbSchema(object):
31 """Schema for an OVSDB database."""
33 def __init__(self, name, version, tables):
35 self.version = version
38 # "isRoot" was not part of the original schema definition. Before it
39 # was added, there was no support for garbage collection. So, for
40 # backward compatibility, if the root set is empty then assume that
41 # every table is in the root set.
42 if self.__root_set_size() == 0:
43 for table in self.tables.itervalues():
46 # Find the "ref_table"s referenced by "ref_table_name"s.
48 # Also force certain columns to be persistent, as explained in
49 # __check_ref_table(). This requires 'is_root' to be known, so this
50 # must follow the loop updating 'is_root' above.
51 for table in self.tables.itervalues():
52 for column in table.columns.itervalues():
53 self.__follow_ref_table(column, column.type.key, "key")
54 self.__follow_ref_table(column, column.type.value, "value")
56 def __root_set_size(self):
57 """Returns the number of tables in the schema's root set."""
59 for table in self.tables.itervalues():
66 parser = ovs.db.parser.Parser(json, "database schema")
67 name = parser.get("name", ['id'])
68 version = parser.get_optional("version", [str, unicode])
69 parser.get_optional("cksum", [str, unicode])
70 tablesJson = parser.get("tables", [dict])
73 if (version is not None and
74 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
75 raise error.Error('schema version "%s" not in format x.y.z'
79 for tableName, tableJson in tablesJson.iteritems():
80 _check_id(tableName, json)
81 tables[tableName] = TableSchema.from_json(tableJson, tableName)
83 return DbSchema(name, version, tables)
86 # "isRoot" was not part of the original schema definition. Before it
87 # was added, there was no support for garbage collection. So, for
88 # backward compatibility, if every table is in the root set then do not
89 # output "isRoot" in table schemas.
90 default_is_root = self.__root_set_size() == len(self.tables)
93 for table in self.tables.itervalues():
94 tables[table.name] = table.to_json(default_is_root)
95 json = {"name": self.name, "tables": tables}
97 json["version"] = self.version
101 return DbSchema.from_json(self.to_json())
103 def __follow_ref_table(self, column, base, base_name):
104 if (not base or base.type != ovs.db.types.UuidType
105 or not base.ref_table_name):
108 base.ref_table = self.tables.get(base.ref_table_name)
109 if not base.ref_table:
110 raise error.Error("column %s %s refers to undefined table %s"
111 % (column.name, base_name, base.ref_table_name),
114 if base.is_strong_ref() and not base.ref_table.is_root:
115 # We cannot allow a strong reference to a non-root table to be
116 # ephemeral: if it is the only reference to a row, then replaying
117 # the database log from disk will cause the referenced row to be
118 # deleted, even though it did exist in memory. If there are
119 # references to that row later in the log (to modify it, to delete
120 # it, or just to point to it), then this will yield a transaction
122 column.persistent = True
125 class IdlSchema(DbSchema):
126 def __init__(self, name, version, tables, idlPrefix, idlHeader):
127 DbSchema.__init__(self, name, version, tables)
128 self.idlPrefix = idlPrefix
129 self.idlHeader = idlHeader
133 parser = ovs.db.parser.Parser(json, "IDL schema")
134 idlPrefix = parser.get("idlPrefix", [str, unicode])
135 idlHeader = parser.get("idlHeader", [str, unicode])
138 del subjson["idlPrefix"]
139 del subjson["idlHeader"]
140 schema = DbSchema.from_json(subjson)
142 return IdlSchema(schema.name, schema.version, schema.tables,
143 idlPrefix, idlHeader)
146 def column_set_from_json(json, columns):
148 return tuple(columns)
149 elif type(json) != list:
150 raise error.Error("array of distinct column names expected", json)
152 for column_name in json:
153 if type(column_name) not in [str, unicode]:
154 raise error.Error("array of distinct column names expected",
156 elif column_name not in columns:
157 raise error.Error("%s is not a valid column name"
159 if len(set(json)) != len(json):
161 raise error.Error("array of distinct column names expected", json)
162 return tuple([columns[column_name] for column_name in json])
165 class TableSchema(object):
166 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
167 is_root=True, indexes=[]):
169 self.columns = columns
170 self.mutable = mutable
171 self.max_rows = max_rows
172 self.is_root = is_root
173 self.indexes = indexes
176 def from_json(json, name):
177 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
178 columns_json = parser.get("columns", [dict])
179 mutable = parser.get_optional("mutable", [bool], True)
180 max_rows = parser.get_optional("maxRows", [int])
181 is_root = parser.get_optional("isRoot", [bool], False)
182 indexes_json = parser.get_optional("indexes", [list], [])
186 max_rows = sys.maxint
188 raise error.Error("maxRows must be at least 1", json)
191 raise error.Error("table must have at least one column", json)
194 for column_name, column_json in columns_json.iteritems():
195 _check_id(column_name, json)
196 columns[column_name] = ColumnSchema.from_json(column_json,
200 for index_json in indexes_json:
201 index = column_set_from_json(index_json, columns)
203 raise error.Error("index must have at least one column", json)
204 elif len(index) == 1:
205 index[0].unique = True
207 if not column.persistent:
208 raise error.Error("ephemeral columns (such as %s) may "
209 "not be indexed" % column.name, json)
210 indexes.append(index)
212 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
214 def to_json(self, default_is_root=False):
215 """Returns this table schema serialized into JSON.
217 The "isRoot" member is included in the JSON only if its value would
218 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
219 false, because ordinarily a table would be not be part of the root set
220 if its "isRoot" member is omitted. However, garbage collection was not
221 originally included in OVSDB, so in older schemas that do not include
222 any "isRoot" members, every table is implicitly part of the root set.
223 To serialize such a schema in a way that can be read by older OVSDB
224 tools, specify 'default_is_root' as True.
228 json["mutable"] = False
229 if default_is_root != self.is_root:
230 json["isRoot"] = self.is_root
232 json["columns"] = columns = {}
233 for column in self.columns.itervalues():
234 if not column.name.startswith("_"):
235 columns[column.name] = column.to_json()
237 if self.max_rows != sys.maxint:
238 json["maxRows"] = self.max_rows
242 for index in self.indexes:
243 json["indexes"].append([column.name for column in index])
248 class ColumnSchema(object):
249 def __init__(self, name, mutable, persistent, type_):
251 self.mutable = mutable
252 self.persistent = persistent
257 def from_json(json, name):
258 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
259 mutable = parser.get_optional("mutable", [bool], True)
260 ephemeral = parser.get_optional("ephemeral", [bool], False)
261 type_ = ovs.db.types.Type.from_json(parser.get("type",
262 [dict, str, unicode]))
265 return ColumnSchema(name, mutable, not ephemeral, type_)
268 json = {"type": self.type.to_json()}
270 json["mutable"] = False
271 if not self.persistent:
272 json["ephemeral"] = True