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 class TableSchema(object):
138 def __init__(self, name, columns, mutable=True, max_rows=sys.maxint,
141 self.columns = columns
142 self.mutable = mutable
143 self.max_rows = max_rows
144 self.is_root = is_root
147 def from_json(json, name):
148 parser = ovs.db.parser.Parser(json, "table schema for table %s" % name)
149 columnsJson = parser.get("columns", [dict])
150 mutable = parser.get_optional("mutable", [bool], True)
151 max_rows = parser.get_optional("maxRows", [int])
152 is_root = parser.get_optional("isRoot", [bool], False)
156 max_rows = sys.maxint
158 raise error.Error("maxRows must be at least 1", json)
161 raise error.Error("table must have at least one column", json)
164 for columnName, columnJson in columnsJson.iteritems():
165 if columnName.startswith('_'):
166 raise error.Error("names beginning with \"_\" are reserved",
168 elif not ovs.db.parser.is_identifier(columnName):
169 raise error.Error("name must be a valid id", json)
170 columns[columnName] = ColumnSchema.from_json(columnJson,
173 return TableSchema(name, columns, mutable, max_rows, is_root)
175 def to_json(self, default_is_root=False):
176 """Returns this table schema serialized into JSON.
178 The "isRoot" member is included in the JSON only if its value would
179 differ from 'default_is_root'. Ordinarily 'default_is_root' should be
180 false, because ordinarily a table would be not be part of the root set
181 if its "isRoot" member is omitted. However, garbage collection was not
182 orginally included in OVSDB, so in older schemas that do not include
183 any "isRoot" members, every table is implicitly part of the root set.
184 To serialize such a schema in a way that can be read by older OVSDB
185 tools, specify 'default_is_root' as True.
189 json["mutable"] = False
190 if default_is_root != self.is_root:
191 json["isRoot"] = self.is_root
193 json["columns"] = columns = {}
194 for column in self.columns.itervalues():
195 if not column.name.startswith("_"):
196 columns[column.name] = column.to_json()
198 if self.max_rows != sys.maxint:
199 json["maxRows"] = self.max_rows
203 class ColumnSchema(object):
204 def __init__(self, name, mutable, persistent, type):
206 self.mutable = mutable
207 self.persistent = persistent
211 def from_json(json, name):
212 parser = ovs.db.parser.Parser(json, "schema for column %s" % name)
213 mutable = parser.get_optional("mutable", [bool], True)
214 ephemeral = parser.get_optional("ephemeral", [bool], False)
215 type = types.Type.from_json(parser.get("type", [dict, unicode]))
218 return ColumnSchema(name, mutable, not ephemeral, type)
221 json = {"type": self.type.to_json()}
223 json["mutable"] = False
224 if not self.persistent:
225 json["ephemeral"] = True