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 # Validate that all ref_tables refer to the names of tables
47 # Also force certain columns to be persistent, as explained in
48 # __check_ref_table(). This requires 'is_root' to be known, so this
49 # must follow the loop updating 'is_root' above.
50 for table in self.tables.itervalues():
51 for column in table.columns.itervalues():
52 self.__check_ref_table(column, column.type.key, "key")
53 self.__check_ref_table(column, column.type.value, "value")
55 def __root_set_size(self):
56 """Returns the number of tables in the schema's root set."""
58 for table in self.tables.itervalues():
65 parser = ovs.db.parser.Parser(json, "database schema")
66 name = parser.get("name", ['id'])
67 version = parser.get_optional("version", [unicode])
68 parser.get_optional("cksum", [unicode])
69 tablesJson = parser.get("tables", [dict])
72 if (version is not None and
73 not re.match('[0-9]+\.[0-9]+\.[0-9]+$', version)):
74 raise error.Error('schema version "%s" not in format x.y.z'
78 for tableName, tableJson in tablesJson.iteritems():
79 _check_id(tableName, json)
80 tables[tableName] = TableSchema.from_json(tableJson, tableName)
82 return DbSchema(name, version, tables)
85 # "isRoot" was not part of the original schema definition. Before it
86 # was added, there was no support for garbage collection. So, for
87 # backward compatibility, if every table is in the root set then do not
88 # output "isRoot" in table schemas.
89 default_is_root = self.__root_set_size() == len(self.tables)
92 for table in self.tables.itervalues():
93 tables[table.name] = table.to_json(default_is_root)
94 json = {"name": self.name, "tables": tables}
96 json["version"] = self.version
99 def __check_ref_table(self, column, base, base_name):
100 if not base or base.type != types.UuidType or not base.ref_table:
103 ref_table = self.tables.get(base.ref_table)
105 raise error.Error("column %s %s refers to undefined table %s"
106 % (column.name, base_name, base.ref_table),
109 if base.is_strong_ref() and not ref_table.is_root:
110 # We cannot allow a strong reference to a non-root table to be
111 # ephemeral: if it is the only reference to a row, then replaying
112 # the database log from disk will cause the referenced row to be
113 # deleted, even though it did exist in memory. If there are
114 # references to that row later in the log (to modify it, to delete
115 # it, or just to point to it), then this will yield a transaction
117 column.persistent = True
119 class IdlSchema(DbSchema):
120 def __init__(self, name, version, tables, idlPrefix, idlHeader):
121 DbSchema.__init__(self, name, version, tables)
122 self.idlPrefix = idlPrefix
123 self.idlHeader = idlHeader
127 parser = ovs.db.parser.Parser(json, "IDL schema")
128 idlPrefix = parser.get("idlPrefix", [unicode])
129 idlHeader = parser.get("idlHeader", [unicode])
132 del subjson["idlPrefix"]
133 del subjson["idlHeader"]
134 schema = DbSchema.from_json(subjson)
136 return IdlSchema(schema.name, schema.version, schema.tables,
137 idlPrefix, idlHeader)
139 def column_set_from_json(json, columns):
141 return tuple(columns)
142 elif type(json) != list:
143 raise error.Error("array of distinct column names expected", json)
145 for column_name in json:
146 if type(column_name) not in [str, unicode]:
147 raise error.Error("array of distinct column names expected",
149 elif column_name not in columns:
150 raise error.Error("%s is not a valid column name"
152 if len(set(json)) != len(json):
154 raise error.Error("array of distinct column names expected", json)
155 return tuple([columns[column_name] for column_name in json])
157 class TableSchema(object):
158 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
159 is_root=True, indexes=[]):
161 self.columns = columns
162 self.mutable = mutable
163 self.max_rows = max_rows
164 self.is_root = is_root
165 self.indexes = indexes
168 def from_json(json, name):
169 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
170 columns_json = parser.get("columns", [dict])
171 mutable = parser.get_optional("mutable", [bool], True)
172 max_rows = parser.get_optional("maxRows", [int])
173 is_root = parser.get_optional("isRoot", [bool], False)
174 indexes_json = parser.get_optional("indexes", [list], [])
178 max_rows = sys.maxint
180 raise error.Error("maxRows must be at least 1", json)
183 raise error.Error("table must have at least one column", json)
186 for column_name, column_json in columns_json.iteritems():
187 _check_id(column_name, json)
188 columns[column_name] = ColumnSchema.from_json(column_json,
192 for index_json in indexes_json:
193 index = column_set_from_json(index_json, columns)
195 raise error.Error("index must have at least one column", json)
196 elif len(index) == 1:
197 index[0].unique = True
199 if not column.persistent:
200 raise error.Error("ephemeral columns (such as %s) may "
201 "not be indexed" % column.name, json)
202 indexes.append(index)
204 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
206 def to_json(self, default_is_root=False):
207 """Returns this table schema serialized into JSON.
209 The "isRoot" member is included in the JSON only if its value would
210 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
211 false, because ordinarily a table would be not be part of the root set
212 if its "isRoot" member is omitted. However, garbage collection was not
213 orginally included in OVSDB, so in older schemas that do not include
214 any "isRoot" members, every table is implicitly part of the root set.
215 To serialize such a schema in a way that can be read by older OVSDB
216 tools, specify 'default_is_root' as True.
220 json["mutable"] = False
221 if default_is_root != self.is_root:
222 json["isRoot"] = self.is_root
224 json["columns"] = columns = {}
225 for column in self.columns.itervalues():
226 if not column.name.startswith("_"):
227 columns[column.name] = column.to_json()
229 if self.max_rows != sys.maxint:
230 json["maxRows"] = self.max_rows
234 for index in self.indexes:
235 json["indexes"].append([column.name for column in index])
239 class ColumnSchema(object):
240 def __init__(self, name, mutable, persistent, type_):
242 self.mutable = mutable
243 self.persistent = persistent
248 def from_json(json, name):
249 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
250 mutable = parser.get_optional("mutable", [bool], True)
251 ephemeral = parser.get_optional("ephemeral", [bool], False)
252 type = types.Type.from_json(parser.get("type", [dict, unicode]))
255 return ColumnSchema(name, mutable, not ephemeral, type)
258 json = {"type": self.type.to_json()}
260 json["mutable"] = False
261 if not self.persistent:
262 json["ephemeral"] = True