]>
Commit | Line | Data |
---|---|---|
e0edde6f | 1 | /* Copyright (c) 2009, 2010, 2011 Nicira, Inc. |
f85f8ebb BP |
2 | * |
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: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
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. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | ||
18 | #include "ovsdb.h" | |
19 | ||
0d0f05b9 | 20 | #include "column.h" |
f85f8ebb BP |
21 | #include "json.h" |
22 | #include "ovsdb-error.h" | |
23 | #include "ovsdb-parser.h" | |
0d0f05b9 | 24 | #include "ovsdb-types.h" |
f85f8ebb BP |
25 | #include "table.h" |
26 | #include "transaction.h" | |
27 | ||
f85f8ebb | 28 | struct ovsdb_schema * |
6aa09313 | 29 | ovsdb_schema_create(const char *name, const char *version, const char *cksum) |
f85f8ebb BP |
30 | { |
31 | struct ovsdb_schema *schema; | |
32 | ||
33 | schema = xzalloc(sizeof *schema); | |
34 | schema->name = xstrdup(name); | |
8159b984 | 35 | schema->version = xstrdup(version); |
6aa09313 | 36 | schema->cksum = xstrdup(cksum); |
f85f8ebb BP |
37 | shash_init(&schema->tables); |
38 | ||
39 | return schema; | |
40 | } | |
41 | ||
58985e09 BP |
42 | struct ovsdb_schema * |
43 | ovsdb_schema_clone(const struct ovsdb_schema *old) | |
44 | { | |
45 | struct ovsdb_schema *new; | |
46 | struct shash_node *node; | |
47 | ||
6aa09313 | 48 | new = ovsdb_schema_create(old->name, old->version, old->cksum); |
58985e09 BP |
49 | SHASH_FOR_EACH (node, &old->tables) { |
50 | const struct ovsdb_table_schema *ts = node->data; | |
51 | ||
52 | shash_add(&new->tables, node->name, ovsdb_table_schema_clone(ts)); | |
53 | } | |
54 | return new; | |
55 | } | |
56 | ||
f85f8ebb BP |
57 | void |
58 | ovsdb_schema_destroy(struct ovsdb_schema *schema) | |
59 | { | |
60 | struct shash_node *node; | |
61 | ||
271915d3 BP |
62 | if (!schema) { |
63 | return; | |
64 | } | |
65 | ||
f85f8ebb BP |
66 | SHASH_FOR_EACH (node, &schema->tables) { |
67 | ovsdb_table_schema_destroy(node->data); | |
68 | } | |
69 | shash_destroy(&schema->tables); | |
f85f8ebb | 70 | free(schema->name); |
8159b984 | 71 | free(schema->version); |
6aa09313 | 72 | free(schema->cksum); |
f85f8ebb BP |
73 | free(schema); |
74 | } | |
75 | ||
76 | struct ovsdb_error * | |
77 | ovsdb_schema_from_file(const char *file_name, struct ovsdb_schema **schemap) | |
78 | { | |
79 | struct ovsdb_schema *schema; | |
80 | struct ovsdb_error *error; | |
81 | struct json *json; | |
82 | ||
83 | *schemap = NULL; | |
84 | json = json_from_file(file_name); | |
85 | if (json->type == JSON_STRING) { | |
86 | error = ovsdb_error("failed to read schema", | |
87 | "\"%s\" could not be read as JSON (%s)", | |
88 | file_name, json_string(json)); | |
89 | json_destroy(json); | |
90 | return error; | |
91 | } | |
92 | ||
93 | error = ovsdb_schema_from_json(json, &schema); | |
e084f690 | 94 | json_destroy(json); |
f85f8ebb | 95 | if (error) { |
f85f8ebb BP |
96 | return ovsdb_wrap_error(error, |
97 | "failed to parse \"%s\" as ovsdb schema", | |
98 | file_name); | |
99 | } | |
100 | ||
101 | *schemap = schema; | |
102 | return NULL; | |
103 | } | |
104 | ||
0d0f05b9 | 105 | static struct ovsdb_error * WARN_UNUSED_RESULT |
42a49b96 | 106 | ovsdb_schema_check_ref_table(struct ovsdb_column *column, |
0d0f05b9 BP |
107 | const struct shash *tables, |
108 | const struct ovsdb_base_type *base, | |
109 | const char *base_name) | |
110 | { | |
42a49b96 BP |
111 | struct ovsdb_table_schema *refTable; |
112 | ||
113 | if (base->type != OVSDB_TYPE_UUID || !base->u.uuid.refTableName) { | |
114 | return NULL; | |
115 | } | |
116 | ||
117 | refTable = shash_find_data(tables, base->u.uuid.refTableName); | |
118 | if (!refTable) { | |
0d0f05b9 BP |
119 | return ovsdb_syntax_error(NULL, NULL, |
120 | "column %s %s refers to undefined table %s", | |
121 | column->name, base_name, | |
122 | base->u.uuid.refTableName); | |
0d0f05b9 | 123 | } |
42a49b96 BP |
124 | |
125 | if (ovsdb_base_type_is_strong_ref(base) && !refTable->is_root) { | |
126 | /* We cannot allow a strong reference to a non-root table to be | |
127 | * ephemeral: if it is the only reference to a row, then replaying the | |
128 | * database log from disk will cause the referenced row to be deleted, | |
129 | * even though it did exist in memory. If there are references to that | |
130 | * row later in the log (to modify it, to delete it, or just to point | |
131 | * to it), then this will yield a transaction error. */ | |
132 | column->persistent = true; | |
133 | } | |
134 | ||
135 | return NULL; | |
0d0f05b9 BP |
136 | } |
137 | ||
8159b984 BP |
138 | static bool |
139 | is_valid_version(const char *s) | |
140 | { | |
141 | int n = -1; | |
1883ed0f | 142 | ignore(sscanf(s, "%*[0-9].%*[0-9].%*[0-9]%n", &n)); |
8159b984 BP |
143 | return n != -1 && s[n] == '\0'; |
144 | } | |
145 | ||
c5f341ab BP |
146 | /* Returns the number of tables in 'schema''s root set. */ |
147 | static size_t | |
148 | root_set_size(const struct ovsdb_schema *schema) | |
149 | { | |
150 | struct shash_node *node; | |
85dcedff | 151 | size_t n_root = 0; |
c5f341ab BP |
152 | |
153 | SHASH_FOR_EACH (node, &schema->tables) { | |
154 | struct ovsdb_table_schema *table = node->data; | |
155 | ||
156 | n_root += table->is_root; | |
157 | } | |
158 | return n_root; | |
159 | } | |
160 | ||
f85f8ebb BP |
161 | struct ovsdb_error * |
162 | ovsdb_schema_from_json(struct json *json, struct ovsdb_schema **schemap) | |
163 | { | |
164 | struct ovsdb_schema *schema; | |
6aa09313 | 165 | const struct json *name, *tables, *version_json, *cksum; |
f85f8ebb BP |
166 | struct ovsdb_error *error; |
167 | struct shash_node *node; | |
168 | struct ovsdb_parser parser; | |
8159b984 | 169 | const char *version; |
f85f8ebb BP |
170 | |
171 | *schemap = NULL; | |
172 | ||
173 | ovsdb_parser_init(&parser, json, "database schema"); | |
174 | name = ovsdb_parser_member(&parser, "name", OP_ID); | |
8159b984 BP |
175 | version_json = ovsdb_parser_member(&parser, "version", |
176 | OP_STRING | OP_OPTIONAL); | |
6aa09313 | 177 | cksum = ovsdb_parser_member(&parser, "cksum", OP_STRING | OP_OPTIONAL); |
f85f8ebb BP |
178 | tables = ovsdb_parser_member(&parser, "tables", OP_OBJECT); |
179 | error = ovsdb_parser_finish(&parser); | |
180 | if (error) { | |
181 | return error; | |
182 | } | |
183 | ||
8159b984 BP |
184 | if (version_json) { |
185 | version = json_string(version_json); | |
186 | if (!is_valid_version(version)) { | |
187 | return ovsdb_syntax_error(json, NULL, "schema version \"%s\" not " | |
188 | "in format x.y.z", version); | |
189 | } | |
190 | } else { | |
191 | /* Backward compatibility with old databases. */ | |
192 | version = ""; | |
193 | } | |
194 | ||
6aa09313 BP |
195 | schema = ovsdb_schema_create(json_string(name), version, |
196 | cksum ? json_string(cksum) : ""); | |
f85f8ebb BP |
197 | SHASH_FOR_EACH (node, json_object(tables)) { |
198 | struct ovsdb_table_schema *table; | |
199 | ||
200 | if (node->name[0] == '_') { | |
201 | error = ovsdb_syntax_error(json, NULL, "names beginning with " | |
202 | "\"_\" are reserved"); | |
b966380b BP |
203 | } else if (!ovsdb_parser_is_id(node->name)) { |
204 | error = ovsdb_syntax_error(json, NULL, "name must be a valid id"); | |
f85f8ebb BP |
205 | } else { |
206 | error = ovsdb_table_schema_from_json(node->data, node->name, | |
207 | &table); | |
208 | } | |
209 | if (error) { | |
210 | ovsdb_schema_destroy(schema); | |
211 | return error; | |
212 | } | |
213 | ||
214 | shash_add(&schema->tables, table->name, table); | |
215 | } | |
0d0f05b9 | 216 | |
42a49b96 BP |
217 | /* "isRoot" was not part of the original schema definition. Before it was |
218 | * added, there was no support for garbage collection. So, for backward | |
219 | * compatibility, if the root set is empty then assume that every table is | |
220 | * in the root set. */ | |
221 | if (root_set_size(schema) == 0) { | |
222 | SHASH_FOR_EACH (node, &schema->tables) { | |
223 | struct ovsdb_table_schema *table = node->data; | |
224 | ||
225 | table->is_root = true; | |
226 | } | |
227 | } | |
228 | ||
229 | /* Validate that all refTables refer to the names of tables that exist. | |
230 | * | |
231 | * Also force certain columns to be persistent, as explained in | |
232 | * ovsdb_schema_check_ref_table(). This requires 'is_root' to be known, so | |
233 | * this must follow the loop updating 'is_root' above. */ | |
0d0f05b9 BP |
234 | SHASH_FOR_EACH (node, &schema->tables) { |
235 | struct ovsdb_table_schema *table = node->data; | |
236 | struct shash_node *node2; | |
237 | ||
238 | SHASH_FOR_EACH (node2, &table->columns) { | |
239 | struct ovsdb_column *column = node2->data; | |
240 | ||
241 | error = ovsdb_schema_check_ref_table(column, &schema->tables, | |
242 | &column->type.key, "key"); | |
243 | if (!error) { | |
244 | error = ovsdb_schema_check_ref_table(column, &schema->tables, | |
245 | &column->type.value, | |
246 | "value"); | |
247 | } | |
248 | if (error) { | |
249 | ovsdb_schema_destroy(schema); | |
250 | return error; | |
251 | } | |
252 | } | |
253 | } | |
254 | ||
f85f8ebb | 255 | *schemap = schema; |
e3c17733 | 256 | return NULL; |
f85f8ebb BP |
257 | } |
258 | ||
259 | struct json * | |
260 | ovsdb_schema_to_json(const struct ovsdb_schema *schema) | |
261 | { | |
262 | struct json *json, *tables; | |
263 | struct shash_node *node; | |
c5f341ab | 264 | bool default_is_root; |
f85f8ebb BP |
265 | |
266 | json = json_object_create(); | |
267 | json_object_put_string(json, "name", schema->name); | |
8159b984 BP |
268 | if (schema->version[0]) { |
269 | json_object_put_string(json, "version", schema->version); | |
270 | } | |
6aa09313 BP |
271 | if (schema->cksum[0]) { |
272 | json_object_put_string(json, "cksum", schema->cksum); | |
273 | } | |
f85f8ebb | 274 | |
c5f341ab BP |
275 | /* "isRoot" was not part of the original schema definition. Before it was |
276 | * added, there was no support for garbage collection. So, for backward | |
277 | * compatibility, if every table is in the root set then do not output | |
278 | * "isRoot" in table schemas. */ | |
279 | default_is_root = root_set_size(schema) == shash_count(&schema->tables); | |
280 | ||
f85f8ebb BP |
281 | tables = json_object_create(); |
282 | ||
283 | SHASH_FOR_EACH (node, &schema->tables) { | |
284 | struct ovsdb_table_schema *table = node->data; | |
285 | json_object_put(tables, table->name, | |
c5f341ab | 286 | ovsdb_table_schema_to_json(table, default_is_root)); |
f85f8ebb BP |
287 | } |
288 | json_object_put(json, "tables", tables); | |
289 | ||
290 | return json; | |
291 | } | |
403e3a25 BP |
292 | |
293 | /* Returns true if 'a' and 'b' specify equivalent schemas, false if they | |
294 | * differ. */ | |
295 | bool | |
296 | ovsdb_schema_equal(const struct ovsdb_schema *a, | |
297 | const struct ovsdb_schema *b) | |
298 | { | |
299 | /* This implementation is simple, stupid, and slow, but I doubt that it | |
300 | * will ever require much maintenance. */ | |
301 | struct json *ja = ovsdb_schema_to_json(a); | |
302 | struct json *jb = ovsdb_schema_to_json(b); | |
303 | bool equals = json_equal(ja, jb); | |
304 | json_destroy(ja); | |
305 | json_destroy(jb); | |
306 | ||
307 | return equals; | |
308 | } | |
f85f8ebb | 309 | \f |
0d0f05b9 BP |
310 | static void |
311 | ovsdb_set_ref_table(const struct shash *tables, | |
312 | struct ovsdb_base_type *base) | |
313 | { | |
314 | if (base->type == OVSDB_TYPE_UUID && base->u.uuid.refTableName) { | |
315 | struct ovsdb_table *table; | |
316 | ||
317 | table = shash_find_data(tables, base->u.uuid.refTableName); | |
318 | base->u.uuid.refTable = table; | |
319 | } | |
320 | } | |
321 | ||
f85f8ebb | 322 | struct ovsdb * |
bd06962a | 323 | ovsdb_create(struct ovsdb_schema *schema) |
f85f8ebb BP |
324 | { |
325 | struct shash_node *node; | |
326 | struct ovsdb *db; | |
327 | ||
328 | db = xmalloc(sizeof *db); | |
329 | db->schema = schema; | |
bd06962a | 330 | list_init(&db->replicas); |
f85f8ebb BP |
331 | list_init(&db->triggers); |
332 | db->run_triggers = false; | |
333 | ||
334 | shash_init(&db->tables); | |
335 | SHASH_FOR_EACH (node, &schema->tables) { | |
336 | struct ovsdb_table_schema *ts = node->data; | |
337 | shash_add(&db->tables, node->name, ovsdb_table_create(ts)); | |
338 | } | |
339 | ||
0d0f05b9 BP |
340 | /* Set all the refTables. */ |
341 | SHASH_FOR_EACH (node, &schema->tables) { | |
342 | struct ovsdb_table_schema *table = node->data; | |
343 | struct shash_node *node2; | |
344 | ||
345 | SHASH_FOR_EACH (node2, &table->columns) { | |
346 | struct ovsdb_column *column = node2->data; | |
347 | ||
348 | ovsdb_set_ref_table(&db->tables, &column->type.key); | |
349 | ovsdb_set_ref_table(&db->tables, &column->type.value); | |
350 | } | |
351 | } | |
352 | ||
f85f8ebb BP |
353 | return db; |
354 | } | |
355 | ||
f85f8ebb BP |
356 | void |
357 | ovsdb_destroy(struct ovsdb *db) | |
358 | { | |
359 | if (db) { | |
360 | struct shash_node *node; | |
361 | ||
bd06962a BP |
362 | /* Remove all the replicas. */ |
363 | while (!list_is_empty(&db->replicas)) { | |
364 | struct ovsdb_replica *r | |
365 | = CONTAINER_OF(list_pop_back(&db->replicas), | |
366 | struct ovsdb_replica, node); | |
367 | ovsdb_remove_replica(db, r); | |
368 | } | |
369 | ||
f85f8ebb BP |
370 | /* Delete all the tables. This also deletes their schemas. */ |
371 | SHASH_FOR_EACH (node, &db->tables) { | |
372 | struct ovsdb_table *table = node->data; | |
373 | ovsdb_table_destroy(table); | |
374 | } | |
375 | shash_destroy(&db->tables); | |
376 | ||
1248fcd2 BP |
377 | /* The schemas, but not the table that points to them, were deleted in |
378 | * the previous step, so we need to clear out the table. We can't | |
379 | * destroy the table, because ovsdb_schema_destroy() will do that. */ | |
380 | shash_clear(&db->schema->tables); | |
f85f8ebb BP |
381 | |
382 | ovsdb_schema_destroy(db->schema); | |
f85f8ebb BP |
383 | free(db); |
384 | } | |
385 | } | |
386 | ||
387 | struct ovsdb_table * | |
388 | ovsdb_get_table(const struct ovsdb *db, const char *name) | |
389 | { | |
390 | return shash_find_data(&db->tables, name); | |
391 | } | |
bd06962a BP |
392 | \f |
393 | void | |
394 | ovsdb_replica_init(struct ovsdb_replica *r, | |
395 | const struct ovsdb_replica_class *class) | |
396 | { | |
397 | r->class = class; | |
398 | } | |
399 | ||
400 | void | |
401 | ovsdb_add_replica(struct ovsdb *db, struct ovsdb_replica *r) | |
402 | { | |
403 | list_push_back(&db->replicas, &r->node); | |
404 | } | |
405 | ||
406 | void | |
c69ee87c | 407 | ovsdb_remove_replica(struct ovsdb *db OVS_UNUSED, struct ovsdb_replica *r) |
bd06962a BP |
408 | { |
409 | list_remove(&r->node); | |
410 | (r->class->destroy)(r); | |
411 | } |