1 # Copyright (c) 2009, 2010, 2011 Nicira Networks
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
20 from ovs.db import types
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)
28 class DbSchema(object):
29 """Schema for an OVSDB database."""
31 def __init__(self, name, version, tables):
33 self.version = version
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():
44 # Find the "ref_table"s referenced by "ref_table_name"s.
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")
54 def __root_set_size(self):
55 """Returns the number of tables in the schema's root set."""
57 for table in self.tables.itervalues():
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])
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'
77 for tableName, tableJson in tablesJson.iteritems():
78 _check_id(tableName, json)
79 tables[tableName] = TableSchema.from_json(tableJson, tableName)
81 return DbSchema(name, version, tables)
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)
91 for table in self.tables.itervalues():
92 tables[table.name] = table.to_json(default_is_root)
93 json = {"name": self.name, "tables": tables}
95 json["version"] = self.version
99 return DbSchema.from_json(self.to_json())
101 def __follow_ref_table(self, column, base, base_name):
102 if not base or base.type != types.UuidType or not base.ref_table_name:
105 base.ref_table = self.tables.get(base.ref_table_name)
106 if not base.ref_table:
107 raise error.Error("column %s %s refers to undefined table %s"
108 % (column.name, base_name, base.ref_table_name),
111 if base.is_strong_ref() and not base.ref_table.is_root:
112 # We cannot allow a strong reference to a non-root table to be
113 # ephemeral: if it is the only reference to a row, then replaying
114 # the database log from disk will cause the referenced row to be
115 # deleted, even though it did exist in memory. If there are
116 # references to that row later in the log (to modify it, to delete
117 # it, or just to point to it), then this will yield a transaction
119 column.persistent = True
121 class IdlSchema(DbSchema):
122 def __init__(self, name, version, tables, idlPrefix, idlHeader):
123 DbSchema.__init__(self, name, version, tables)
124 self.idlPrefix = idlPrefix
125 self.idlHeader = idlHeader
129 parser = ovs.db.parser.Parser(json, "IDL schema")
130 idlPrefix = parser.get("idlPrefix", [str, unicode])
131 idlHeader = parser.get("idlHeader", [str, unicode])
134 del subjson["idlPrefix"]
135 del subjson["idlHeader"]
136 schema = DbSchema.from_json(subjson)
138 return IdlSchema(schema.name, schema.version, schema.tables,
139 idlPrefix, idlHeader)
141 def column_set_from_json(json, columns):
143 return tuple(columns)
144 elif type(json) != list:
145 raise error.Error("array of distinct column names expected", json)
147 for column_name in json:
148 if type(column_name) not in [str, unicode]:
149 raise error.Error("array of distinct column names expected",
151 elif column_name not in columns:
152 raise error.Error("%s is not a valid column name"
154 if len(set(json)) != len(json):
156 raise error.Error("array of distinct column names expected", json)
157 return tuple([columns[column_name] for column_name in json])
159 class TableSchema(object):
160 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
161 is_root=True, indexes=[]):
163 self.columns = columns
164 self.mutable = mutable
165 self.max_rows = max_rows
166 self.is_root = is_root
167 self.indexes = indexes
170 def from_json(json, name):
171 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
172 columns_json = parser.get("columns", [dict])
173 mutable = parser.get_optional("mutable", [bool], True)
174 max_rows = parser.get_optional("maxRows", [int])
175 is_root = parser.get_optional("isRoot", [bool], False)
176 indexes_json = parser.get_optional("indexes", [list], [])
180 max_rows = sys.maxint
182 raise error.Error("maxRows must be at least 1", json)
185 raise error.Error("table must have at least one column", json)
188 for column_name, column_json in columns_json.iteritems():
189 _check_id(column_name, json)
190 columns[column_name] = ColumnSchema.from_json(column_json,
194 for index_json in indexes_json:
195 index = column_set_from_json(index_json, columns)
197 raise error.Error("index must have at least one column", json)
198 elif len(index) == 1:
199 index[0].unique = True
201 if not column.persistent:
202 raise error.Error("ephemeral columns (such as %s) may "
203 "not be indexed" % column.name, json)
204 indexes.append(index)
206 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
208 def to_json(self, default_is_root=False):
209 """Returns this table schema serialized into JSON.
211 The "isRoot" member is included in the JSON only if its value would
212 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
213 false, because ordinarily a table would be not be part of the root set
214 if its "isRoot" member is omitted. However, garbage collection was not
215 orginally included in OVSDB, so in older schemas that do not include
216 any "isRoot" members, every table is implicitly part of the root set.
217 To serialize such a schema in a way that can be read by older OVSDB
218 tools, specify 'default_is_root' as True.
222 json["mutable"] = False
223 if default_is_root != self.is_root:
224 json["isRoot"] = self.is_root
226 json["columns"] = columns = {}
227 for column in self.columns.itervalues():
228 if not column.name.startswith("_"):
229 columns[column.name] = column.to_json()
231 if self.max_rows != sys.maxint:
232 json["maxRows"] = self.max_rows
236 for index in self.indexes:
237 json["indexes"].append([column.name for column in index])
241 class ColumnSchema(object):
242 def __init__(self, name, mutable, persistent, type_):
244 self.mutable = mutable
245 self.persistent = persistent
250 def from_json(json, name):
251 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
252 mutable = parser.get_optional("mutable", [bool], True)
253 ephemeral = parser.get_optional("ephemeral", [bool], False)
254 type_ = types.Type.from_json(parser.get("type", [dict, str, unicode]))
257 return ColumnSchema(name, mutable, not ephemeral, type_)
260 json = {"type": self.type.to_json()}
262 json["mutable"] = False
263 if not self.persistent:
264 json["ephemeral"] = True