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 class DbSchema(object):
23 """Schema for an OVSDB database."""
25 def __init__(self, name, version, tables):
27 self.version = version
30 # "isRoot" was not part of the original schema definition. Before it
31 # was added, there was no support for garbage collection. So, for
32 # backward compatibility, if the root set is empty then assume that
33 # every table is in the root set.
34 if self.__root_set_size() == 0:
35 for table in self.tables.itervalues():
38 # Validate that all ref_tables refer to the names of tables
41 # Also force certain columns to be persistent, as explained in
42 # __check_ref_table(). This requires 'is_root' to be known, so this
43 # must follow the loop updating 'is_root' above.
44 for table in self.tables.itervalues():
45 for column in table.columns.itervalues():
46 self.__check_ref_table(column, column.type.key, "key")
47 self.__check_ref_table(column, column.type.value, "value")
49 def __root_set_size(self):
50 """Returns the number of tables in the schema's root set."""
52 for table in self.tables.itervalues():
59 parser = ovs.db.parser.Parser(json, "database schema")
60 name = parser.get("name", ['id'])
61 version = parser.get_optional("version", [unicode])
62 parser.get_optional("cksum", [unicode])
63 tablesJson = parser.get("tables", [dict])
66 if (version is not None and
67 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
68 raise error.Error("schema version \"%s\" not in format x.y.z"
72 for tableName, tableJson in tablesJson.iteritems():
73 if tableName.startswith('_'):
74 raise error.Error("names beginning with \"_\" are reserved",
76 elif not ovs.db.parser.is_identifier(tableName):
77 raise error.Error("name must be a valid id", json)
78 tables[tableName] = TableSchema.from_json(tableJson, tableName)
80 return DbSchema(name, version, tables)
83 # "isRoot" was not part of the original schema definition. Before it
84 # was added, there was no support for garbage collection. So, for
85 # backward compatibility, if every table is in the root set then do not
86 # output "isRoot" in table schemas.
87 default_is_root = self.__root_set_size() == len(self.tables)
90 for table in self.tables.itervalues():
91 tables[table.name] = table.to_json(default_is_root)
92 json = {"name": self.name, "tables": tables}
94 json["version"] = self.version
97 def __check_ref_table(self, column, base, base_name):
98 if not base or base.type != types.UuidType or not base.ref_table:
101 ref_table = self.tables.get(base.ref_table)
103 raise error.Error("column %s %s refers to undefined table %s"
104 % (column.name, base_name, base.ref_table),
107 if base.is_strong_ref() and not ref_table.is_root:
108 # We cannot allow a strong reference to a non-root table to be
109 # ephemeral: if it is the only reference to a row, then replaying
110 # the database log from disk will cause the referenced row to be
111 # deleted, even though it did exist in memory. If there are
112 # references to that row later in the log (to modify it, to delete
113 # it, or just to point to it), then this will yield a transaction
115 column.persistent = True
117 class IdlSchema(DbSchema):
118 def __init__(self, name, version, tables, idlPrefix, idlHeader):
119 DbSchema.__init__(self, name, version, tables)
120 self.idlPrefix = idlPrefix
121 self.idlHeader = idlHeader
125 parser = ovs.db.parser.Parser(json, "IDL schema")
126 idlPrefix = parser.get("idlPrefix", [unicode])
127 idlHeader = parser.get("idlHeader", [unicode])
130 del subjson["idlPrefix"]
131 del subjson["idlHeader"]
132 schema = DbSchema.from_json(subjson)
134 return IdlSchema(schema.name, schema.version, schema.tables,
135 idlPrefix, idlHeader)
137 def column_set_from_json(json, columns):
139 return tuple(columns)
140 elif type(json) != list:
141 raise error.Error("array of distinct column names expected", json)
143 for column_name in json:
144 if type(column_name) not in [str, unicode]:
145 raise error.Error("array of distinct column names expected",
147 elif column_name not in columns:
148 raise error.Error("%s is not a valid column name"
150 if len(set(json)) != len(json):
152 raise error.Error("array of distinct column names expected", json)
153 return tuple([columns[column_name] for column_name in json])
155 class TableSchema(object):
156 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
157 is_root=True, indexes=[]):
159 self.columns = columns
160 self.mutable = mutable
161 self.max_rows = max_rows
162 self.is_root = is_root
163 self.indexes = indexes
166 def from_json(json, name):
167 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
168 columns_json = parser.get("columns", [dict])
169 mutable = parser.get_optional("mutable", [bool], True)
170 max_rows = parser.get_optional("maxRows", [int])
171 is_root = parser.get_optional("isRoot", [bool], False)
172 indexes_json = parser.get_optional("indexes", [list], [])
176 max_rows = sys.maxint
178 raise error.Error("maxRows must be at least 1", json)
181 raise error.Error("table must have at least one column", json)
184 for column_name, column_json in columns_json.iteritems():
185 if column_name.startswith('_'):
186 raise error.Error("names beginning with \"_\" are reserved",
188 elif not ovs.db.parser.is_identifier(column_name):
189 raise error.Error("name must be a valid id", 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, 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