ovsdb: Implement ovsdb-tool commands "compact" and "convert".
[sliver-openvswitch.git] / ovsdb / file.c
1 /* Copyright (c) 2009, 2010 Nicira Networks
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 "file.h"
19
20 #include <assert.h>
21 #include <fcntl.h>
22
23 #include "column.h"
24 #include "log.h"
25 #include "json.h"
26 #include "ovsdb.h"
27 #include "ovsdb-error.h"
28 #include "row.h"
29 #include "table.h"
30 #include "timeval.h"
31 #include "transaction.h"
32 #include "uuid.h"
33 #include "util.h"
34
35 #define THIS_MODULE VLM_ovsdb_file
36 #include "vlog.h"
37
38 /* A transaction being converted to JSON for writing to a file. */
39 struct ovsdb_file_txn {
40     struct json *json;          /* JSON for the whole transaction. */
41     struct json *table_json;    /* JSON for 'table''s transaction. */
42     struct ovsdb_table *table;  /* Table described in 'table_json'.  */
43 };
44
45 static void ovsdb_file_txn_init(struct ovsdb_file_txn *);
46 static void ovsdb_file_txn_add_row(struct ovsdb_file_txn *,
47                                    const struct ovsdb_row *old,
48                                    const struct ovsdb_row *new);
49 static struct ovsdb_error *ovsdb_file_txn_commit(struct json *,
50                                                  const char *comment,
51                                                  bool durable,
52                                                  struct ovsdb_log *);
53
54 static struct ovsdb_error *ovsdb_file_open__(const char *file_name,
55                                              const struct ovsdb_schema *,
56                                              bool read_only, struct ovsdb **);
57 static struct ovsdb_error *ovsdb_file_txn_from_json(struct ovsdb *,
58                                                     const struct json *,
59                                                     bool converting,
60                                                     struct ovsdb_txn **);
61 static void ovsdb_file_replica_create(struct ovsdb *, struct ovsdb_log *);
62
63 /* Opens database 'file_name' and stores a pointer to the new database in
64  * '*dbp'.  If 'read_only' is false, then the database will be locked and
65  * changes to the database will be written to disk.  If 'read_only' is true,
66  * the database will not be locked and changes to the database will persist
67  * only as long as the "struct ovsdb".
68  *
69  * On success, returns NULL.  On failure, returns an ovsdb_error (which the
70  * caller must destroy) and sets '*dbp' to NULL. */
71 struct ovsdb_error *
72 ovsdb_file_open(const char *file_name, bool read_only, struct ovsdb **dbp)
73 {
74     return ovsdb_file_open__(file_name, NULL, read_only, dbp);
75 }
76
77 /* Opens database 'file_name' with an alternate schema.  The specified 'schema'
78  * is used to interpret the data in 'file_name', ignoring the schema actually
79  * stored in the file.  Data in the file for tables or columns that do not
80  * exist in 'schema' are ignored, but the ovsdb file format must otherwise be
81  * observed, including column constraints.
82  *
83  * This function can be useful for upgrading or downgrading databases to
84  * "almost-compatible" formats.
85  *
86  * The database will not be locked.  Changes to the database will persist only
87  * as long as the "struct ovsdb".
88  *
89  * On success, stores a pointer to the new database in '*dbp' and returns a
90  * null pointer.  On failure, returns an ovsdb_error (which the caller must
91  * destroy) and sets '*dbp' to NULL. */
92 struct ovsdb_error *
93 ovsdb_file_open_as_schema(const char *file_name,
94                           const struct ovsdb_schema *schema,
95                           struct ovsdb **dbp)
96 {
97     return ovsdb_file_open__(file_name, schema, true, dbp);
98 }
99
100 static struct ovsdb_error *
101 ovsdb_file_open__(const char *file_name,
102                   const struct ovsdb_schema *alternate_schema,
103                   bool read_only, struct ovsdb **dbp)
104 {
105     enum ovsdb_log_open_mode open_mode;
106     struct ovsdb_schema *schema;
107     struct ovsdb_error *error;
108     struct ovsdb_log *log;
109     struct json *json;
110     struct ovsdb *db;
111
112     open_mode = read_only ? OVSDB_LOG_READ_ONLY : OVSDB_LOG_READ_WRITE;
113     error = ovsdb_log_open(file_name, open_mode, -1, &log);
114     if (error) {
115         return error;
116     }
117
118     error = ovsdb_log_read(log, &json);
119     if (error) {
120         return error;
121     } else if (!json) {
122         return ovsdb_io_error(EOF, "%s: database file contains no schema",
123                               file_name);
124     }
125
126     if (alternate_schema) {
127         schema = ovsdb_schema_clone(alternate_schema);
128     } else {
129         error = ovsdb_schema_from_json(json, &schema);
130         if (error) {
131             json_destroy(json);
132             return ovsdb_wrap_error(error,
133                                     "failed to parse \"%s\" as ovsdb schema",
134                                     file_name);
135         }
136     }
137     json_destroy(json);
138
139     db = ovsdb_create(schema);
140     while ((error = ovsdb_log_read(log, &json)) == NULL && json) {
141         struct ovsdb_txn *txn;
142
143         error = ovsdb_file_txn_from_json(db, json, alternate_schema != NULL,
144                                          &txn);
145         json_destroy(json);
146         if (error) {
147             break;
148         }
149
150         ovsdb_txn_commit(txn, false);
151     }
152     if (error) {
153         char *msg = ovsdb_error_to_string(error);
154         VLOG_WARN("%s", msg);
155         free(msg);
156
157         ovsdb_error_destroy(error);
158     }
159
160     if (!read_only) {
161         ovsdb_file_replica_create(db, log);
162     } else {
163         ovsdb_log_close(log);
164     }
165
166     *dbp = db;
167     return NULL;
168 }
169
170 static struct ovsdb_error *
171 ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
172                                 const struct json *json)
173 {
174     struct ovsdb_table_schema *schema = row->table->schema;
175     struct ovsdb_error *error;
176     struct shash_node *node;
177
178     if (json->type != JSON_OBJECT) {
179         return ovsdb_syntax_error(json, NULL, "row must be JSON object");
180     }
181
182     SHASH_FOR_EACH (node, json_object(json)) {
183         const char *column_name = node->name;
184         const struct ovsdb_column *column;
185         struct ovsdb_datum datum;
186
187         column = ovsdb_table_schema_get_column(schema, column_name);
188         if (!column) {
189             if (converting) {
190                 continue;
191             }
192             return ovsdb_syntax_error(json, "unknown column",
193                                       "No column %s in table %s.",
194                                       column_name, schema->name);
195         }
196
197         error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
198         if (error) {
199             return error;
200         }
201         ovsdb_datum_swap(&row->fields[column->index], &datum);
202         ovsdb_datum_destroy(&datum, &column->type);
203     }
204
205     return NULL;
206 }
207
208 static struct ovsdb_error *
209 ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
210                              bool converting,
211                              const struct uuid *row_uuid, struct json *json)
212 {
213     const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
214     if (json->type == JSON_NULL) {
215         if (!row) {
216             return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
217                                       "row "UUID_FMT" that does not exist",
218                                       UUID_ARGS(row_uuid));
219         }
220         ovsdb_txn_row_delete(txn, row);
221         return NULL;
222     } else if (row) {
223         return ovsdb_file_update_row_from_json(ovsdb_txn_row_modify(txn, row),
224                                                converting, json);
225     } else {
226         struct ovsdb_error *error;
227         struct ovsdb_row *new;
228
229         new = ovsdb_row_create(table);
230         *ovsdb_row_get_uuid_rw(new) = *row_uuid;
231         error = ovsdb_file_update_row_from_json(new, converting, json);
232         if (error) {
233             ovsdb_row_destroy(new);
234         }
235
236         ovsdb_txn_row_insert(txn, new);
237
238         return error;
239     }
240 }
241
242 static struct ovsdb_error *
243 ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
244                                struct ovsdb_table *table,
245                                bool converting, struct json *json)
246 {
247     struct shash_node *node;
248
249     if (json->type != JSON_OBJECT) {
250         return ovsdb_syntax_error(json, NULL, "object expected");
251     }
252
253     SHASH_FOR_EACH (node, json->u.object) {
254         const char *uuid_string = node->name;
255         struct json *txn_row_json = node->data;
256         struct ovsdb_error *error;
257         struct uuid row_uuid;
258
259         if (!uuid_from_string(&row_uuid, uuid_string)) {
260             return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
261                                       uuid_string);
262         }
263
264         error = ovsdb_file_txn_row_from_json(txn, table, converting,
265                                              &row_uuid, txn_row_json);
266         if (error) {
267             return error;
268         }
269     }
270
271     return NULL;
272 }
273
274 static struct ovsdb_error *
275 ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
276                          bool converting, struct ovsdb_txn **txnp)
277 {
278     struct ovsdb_error *error;
279     struct shash_node *node;
280     struct ovsdb_txn *txn;
281
282     *txnp = NULL;
283     if (json->type != JSON_OBJECT) {
284         return ovsdb_syntax_error(json, NULL, "object expected");
285     }
286
287     txn = ovsdb_txn_create(db);
288     SHASH_FOR_EACH (node, json->u.object) {
289         const char *table_name = node->name;
290         struct json *txn_table_json = node->data;
291         struct ovsdb_table *table;
292
293         table = shash_find_data(&db->tables, table_name);
294         if (!table) {
295             if (!strcmp(table_name, "_date")
296                 || !strcmp(table_name, "_comment")
297                 || converting) {
298                 continue;
299             }
300
301             error = ovsdb_syntax_error(json, "unknown table",
302                                        "No table named %s.", table_name);
303             goto error;
304         }
305
306         error = ovsdb_file_txn_table_from_json(txn, table, converting,
307                                                txn_table_json);
308         if (error) {
309             goto error;
310         }
311     }
312     *txnp = txn;
313     return NULL;
314
315 error:
316     ovsdb_txn_abort(txn);
317     return error;
318 }
319
320 /* Saves a snapshot of 'db''s current contents as 'file_name'.  If 'comment' is
321  * nonnull, then it is added along with the data contents and can be viewed
322  * with "ovsdb-tool show-log".
323  *
324  * 'locking' is passed along to ovsdb_log_open() untouched. */
325 struct ovsdb_error *
326 ovsdb_file_save_copy(const char *file_name, int locking,
327                      const char *comment, const struct ovsdb *db)
328 {
329     const struct shash_node *node;
330     struct ovsdb_file_txn ftxn;
331     struct ovsdb_error *error;
332     struct ovsdb_log *log;
333     struct json *json;
334
335     error = ovsdb_log_open(file_name, OVSDB_LOG_CREATE, locking, &log);
336     if (error) {
337         return error;
338     }
339
340     /* Write schema. */
341     json = ovsdb_schema_to_json(db->schema);
342     error = ovsdb_log_write(log, json);
343     json_destroy(json);
344     if (error) {
345         goto exit;
346     }
347
348     /* Write data. */
349     ovsdb_file_txn_init(&ftxn);
350     SHASH_FOR_EACH (node, &db->tables) {
351         const struct ovsdb_table *table = node->data;
352         const struct ovsdb_row *row;
353
354         HMAP_FOR_EACH (row, struct ovsdb_row, hmap_node, &table->rows) {
355             ovsdb_file_txn_add_row(&ftxn, NULL, row);
356         }
357     }
358     error = ovsdb_file_txn_commit(ftxn.json, comment, true, log);
359
360 exit:
361     ovsdb_log_close(log);
362     if (error) {
363         remove(file_name);
364     }
365     return error;
366 }
367 \f
368 /* Replica implementation. */
369
370 struct ovsdb_file_replica {
371     struct ovsdb_replica replica;
372     struct ovsdb_log *log;
373 };
374
375 static const struct ovsdb_replica_class ovsdb_file_replica_class;
376
377 static void
378 ovsdb_file_replica_create(struct ovsdb *db, struct ovsdb_log *log)
379 {
380     struct ovsdb_file_replica *r = xmalloc(sizeof *r);
381     ovsdb_replica_init(&r->replica, &ovsdb_file_replica_class);
382     r->log = log;
383     ovsdb_add_replica(db, &r->replica);
384
385 }
386
387 static struct ovsdb_file_replica *
388 ovsdb_file_replica_cast(struct ovsdb_replica *replica)
389 {
390     assert(replica->class == &ovsdb_file_replica_class);
391     return CONTAINER_OF(replica, struct ovsdb_file_replica, replica);
392 }
393
394 static bool
395 ovsdb_file_replica_change_cb(const struct ovsdb_row *old,
396                              const struct ovsdb_row *new,
397                              void *ftxn_)
398 {
399     struct ovsdb_file_txn *ftxn = ftxn_;
400     ovsdb_file_txn_add_row(ftxn, old, new);
401     return true;
402 }
403
404 static struct ovsdb_error *
405 ovsdb_file_replica_commit(struct ovsdb_replica *r_,
406                           const struct ovsdb_txn *txn, bool durable)
407 {
408     struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
409     struct ovsdb_file_txn ftxn;
410
411     ovsdb_file_txn_init(&ftxn);
412     ovsdb_txn_for_each_change(txn, ovsdb_file_replica_change_cb, &ftxn);
413     if (!ftxn.json) {
414         /* Nothing to commit. */
415         return NULL;
416     }
417
418     return ovsdb_file_txn_commit(ftxn.json, ovsdb_txn_get_comment(txn),
419                                  durable, r->log);
420 }
421
422 static void
423 ovsdb_file_replica_destroy(struct ovsdb_replica *r_)
424 {
425     struct ovsdb_file_replica *r = ovsdb_file_replica_cast(r_);
426
427     ovsdb_log_close(r->log);
428     free(r);
429 }
430
431 static const struct ovsdb_replica_class ovsdb_file_replica_class = {
432     ovsdb_file_replica_commit,
433     ovsdb_file_replica_destroy
434 };
435 \f
436 static void
437 ovsdb_file_txn_init(struct ovsdb_file_txn *ftxn)
438 {
439     ftxn->json = NULL;
440     ftxn->table_json = NULL;
441     ftxn->table = NULL;
442 }
443
444 static void
445 ovsdb_file_txn_add_row(struct ovsdb_file_txn *ftxn,
446                        const struct ovsdb_row *old,
447                        const struct ovsdb_row *new)
448 {
449     struct json *row;
450
451     if (!new) {
452         row = json_null_create();
453     } else {
454         struct shash_node *node;
455
456         row = old ? NULL : json_object_create();
457         SHASH_FOR_EACH (node, &new->table->schema->columns) {
458             const struct ovsdb_column *column = node->data;
459             const struct ovsdb_type *type = &column->type;
460             unsigned int idx = column->index;
461
462             if (idx != OVSDB_COL_UUID && column->persistent
463                 && (old
464                     ? !ovsdb_datum_equals(&old->fields[idx], &new->fields[idx],
465                                           type)
466                     : !ovsdb_datum_is_default(&new->fields[idx], type)))
467             {
468                 if (!row) {
469                     row = json_object_create();
470                 }
471                 json_object_put(row, column->name,
472                                 ovsdb_datum_to_json(&new->fields[idx], type));
473             }
474         }
475     }
476
477     if (row) {
478         struct ovsdb_table *table = new ? new->table : old->table;
479         char uuid[UUID_LEN + 1];
480
481         if (table != ftxn->table) {
482             /* Create JSON object for transaction overall. */
483             if (!ftxn->json) {
484                 ftxn->json = json_object_create();
485             }
486
487             /* Create JSON object for transaction on this table. */
488             ftxn->table_json = json_object_create();
489             ftxn->table = table;
490             json_object_put(ftxn->json, table->schema->name, ftxn->table_json);
491         }
492
493         /* Add row to transaction for this table. */
494         snprintf(uuid, sizeof uuid,
495                  UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
496         json_object_put(ftxn->table_json, uuid, row);
497     }
498 }
499
500 static struct ovsdb_error *
501 ovsdb_file_txn_commit(struct json *json, const char *comment,
502                       bool durable, struct ovsdb_log *log)
503 {
504     struct ovsdb_error *error;
505
506     if (!json) {
507         json = json_object_create();
508     }
509     if (comment) {
510         json_object_put_string(json, "_comment", comment);
511     }
512     json_object_put(json, "_date", json_integer_create(time_now()));
513
514     error = ovsdb_log_write(log, json);
515     json_destroy(json);
516     if (error) {
517         return ovsdb_wrap_error(error, "writing transaction failed");
518     }
519
520     if (durable) {
521         error = ovsdb_log_commit(log);
522         if (error) {
523             return ovsdb_wrap_error(error, "committing transaction failed");
524         }
525     }
526
527     return NULL;
528 }