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
98 def __follow_ref_table(self, column, base, base_name):
99 if not base or base.type != types.UuidType or not base.ref_table_name:
102 base.ref_table = self.tables.get(base.ref_table_name)
103 if not base.ref_table:
104 raise error.Error("column %s %s refers to undefined table %s"
105 % (column.name, base_name, base.ref_table_name),
108 if base.is_strong_ref() and not base.ref_table.is_root:
109 # We cannot allow a strong reference to a non-root table to be
110 # ephemeral: if it is the only reference to a row, then replaying
111 # the database log from disk will cause the referenced row to be
112 # deleted, even though it did exist in memory. If there are
113 # references to that row later in the log (to modify it, to delete
114 # it, or just to point to it), then this will yield a transaction
116 column.persistent = True
118 class IdlSchema(DbSchema):
119 def __init__(self, name, version, tables, idlPrefix, idlHeader):
120 DbSchema.__init__(self, name, version, tables)
121 self.idlPrefix = idlPrefix
122 self.idlHeader = idlHeader
126 parser = ovs.db.parser.Parser(json, "IDL schema")
127 idlPrefix = parser.get("idlPrefix", [str, unicode])
128 idlHeader = parser.get("idlHeader", [str, unicode])
131 del subjson["idlPrefix"]
132 del subjson["idlHeader"]
133 schema = DbSchema.from_json(subjson)
135 return IdlSchema(schema.name, schema.version, schema.tables,
136 idlPrefix, idlHeader)
138 def column_set_from_json(json, columns):
140 return tuple(columns)
141 elif type(json) != list:
142 raise error.Error("array of distinct column names expected", json)
144 for column_name in json:
145 if type(column_name) not in [str, unicode]:
146 raise error.Error("array of distinct column names expected",
148 elif column_name not in columns:
149 raise error.Error("%s is not a valid column name"
151 if len(set(json)) != len(json):
153 raise error.Error("array of distinct column names expected", json)
154 return tuple([columns[column_name] for column_name in json])
156 class TableSchema(object):
157 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
158 is_root=True, indexes=[]):
160 self.columns = columns
161 self.mutable = mutable
162 self.max_rows = max_rows
163 self.is_root = is_root
164 self.indexes = indexes
167 def from_json(json, name):
168 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
169 columns_json = parser.get("columns", [dict])
170 mutable = parser.get_optional("mutable", [bool], True)
171 max_rows = parser.get_optional("maxRows", [int])
172 is_root = parser.get_optional("isRoot", [bool], False)
173 indexes_json = parser.get_optional("indexes", [list], [])
177 max_rows = sys.maxint
179 raise error.Error("maxRows must be at least 1", json)
182 raise error.Error("table must have at least one column", json)
185 for column_name, column_json in columns_json.iteritems():
186 _check_id(column_name, json)
187 columns[column_name] = ColumnSchema.from_json(column_json,
191 for index_json in indexes_json:
192 index = column_set_from_json(index_json, columns)
194 raise error.Error("index must have at least one column", json)
195 elif len(index) == 1:
196 index[0].unique = True
198 if not column.persistent:
199 raise error.Error("ephemeral columns (such as %s) may "
200 "not be indexed" % column.name, json)
201 indexes.append(index)
203 return TableSchema(name, columns, mutable, max_rows, is_root, indexes)
205 def to_json(self, default_is_root=False):
206 """Returns this table schema serialized into JSON.
208 The "isRoot" member is included in the JSON only if its value would
209 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
210 false, because ordinarily a table would be not be part of the root set
211 if its "isRoot" member is omitted. However, garbage collection was not
212 orginally included in OVSDB, so in older schemas that do not include
213 any "isRoot" members, every table is implicitly part of the root set.
214 To serialize such a schema in a way that can be read by older OVSDB
215 tools, specify 'default_is_root' as True.
219 json["mutable"] = False
220 if default_is_root != self.is_root:
221 json["isRoot"] = self.is_root
223 json["columns"] = columns = {}
224 for column in self.columns.itervalues():
225 if not column.name.startswith("_"):
226 columns[column.name] = column.to_json()
228 if self.max_rows != sys.maxint:
229 json["maxRows"] = self.max_rows
233 for index in self.indexes:
234 json["indexes"].append([column.name for column in index])
238 class ColumnSchema(object):
239 def __init__(self, name, mutable, persistent, type_):
241 self.mutable = mutable
242 self.persistent = persistent
247 def from_json(json, name):
248 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
249 mutable = parser.get_optional("mutable", [bool], True)
250 ephemeral = parser.get_optional("ephemeral", [bool], False)
251 type_ = types.Type.from_json(parser.get("type", [dict, str, unicode]))
254 return ColumnSchema(name, mutable, not ephemeral, type_)
257 json = {"type": self.type.to_json()}
259 json["mutable"] = False
260 if not self.persistent:
261 json["ephemeral"] = True