]> git.proxmox.com Git - ovs.git/blame - ovsdb/file.c
Merge tag 'v2.15.0' into master-dfsg
[ovs.git] / ovsdb / file.c
CommitLineData
120fb2ca 1/* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2016, 2017 Nicira, Inc.
bd06962a
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 "file.h"
19
ada496b5 20#include <errno.h>
bd06962a 21#include <fcntl.h>
ada496b5 22#include <unistd.h>
bd06962a 23
17d18afb 24#include "bitmap.h"
bd06962a
BP
25#include "column.h"
26#include "log.h"
ee89ea7b 27#include "openvswitch/json.h"
ada496b5 28#include "lockfile.h"
bd06962a
BP
29#include "ovsdb.h"
30#include "ovsdb-error.h"
31#include "row.h"
ada496b5 32#include "socket-util.h"
1b1d2e6d 33#include "storage.h"
bd06962a 34#include "table.h"
d171b584 35#include "timeval.h"
bd06962a 36#include "transaction.h"
2ccd66f5 37#include "unixctl.h"
bd06962a
BP
38#include "uuid.h"
39#include "util.h"
e6211adc 40#include "openvswitch/vlog.h"
bd06962a 41
d98e6007 42VLOG_DEFINE_THIS_MODULE(ovsdb_file);
5136ce49 43
a3d573ed
BP
44/* A transaction being converted to JSON for writing to a file. */
45struct ovsdb_file_txn {
46 struct json *json; /* JSON for the whole transaction. */
47 struct json *table_json; /* JSON for 'table''s transaction. */
48 struct ovsdb_table *table; /* Table described in 'table_json'. */
49};
50
51static void ovsdb_file_txn_init(struct ovsdb_file_txn *);
52static void ovsdb_file_txn_add_row(struct ovsdb_file_txn *,
53 const struct ovsdb_row *old,
17d18afb
BP
54 const struct ovsdb_row *new,
55 const unsigned long int *changed);
bd06962a 56
2ccd66f5
IM
57/* If set to 'true', file transactions will contain difference between
58 * datums of old and new rows and not the whole new datum for the column. */
59static bool use_column_diff = true;
60
61static void
62ovsdb_file_column_diff_enable(struct unixctl_conn *conn, int argc OVS_UNUSED,
63 const char *argv[] OVS_UNUSED,
64 void *arg OVS_UNUSED)
65{
66 use_column_diff = true;
67 unixctl_command_reply(conn, NULL);
68}
69
70void
71ovsdb_file_column_diff_disable(void)
72{
73 if (!use_column_diff) {
74 return;
75 }
76 use_column_diff = false;
77 unixctl_command_register("ovsdb/file/column-diff-enable", "",
78 0, 0, ovsdb_file_column_diff_enable, NULL);
79}
80
1e19e50e
BP
81static struct ovsdb_error *
82ovsdb_file_update_row_from_json(struct ovsdb_row *row, bool converting,
2ccd66f5 83 bool row_contains_diff,
1e19e50e
BP
84 const struct json *json)
85{
86 struct ovsdb_table_schema *schema = row->table->schema;
87 struct ovsdb_error *error;
88 struct shash_node *node;
89
90 if (json->type != JSON_OBJECT) {
91 return ovsdb_syntax_error(json, NULL, "row must be JSON object");
92 }
93
94 SHASH_FOR_EACH (node, json_object(json)) {
95 const char *column_name = node->name;
96 const struct ovsdb_column *column;
97 struct ovsdb_datum datum;
98
99 column = ovsdb_table_schema_get_column(schema, column_name);
100 if (!column) {
101 if (converting) {
102 continue;
103 }
104 return ovsdb_syntax_error(json, "unknown column",
105 "No column %s in table %s.",
106 column_name, schema->name);
107 }
108
109 error = ovsdb_datum_from_json(&datum, &column->type, node->data, NULL);
110 if (error) {
111 return error;
112 }
2ccd66f5
IM
113 if (row_contains_diff
114 && !ovsdb_datum_is_default(&row->fields[column->index],
115 &column->type)) {
116 struct ovsdb_datum new_datum;
117
118 error = ovsdb_datum_apply_diff(&new_datum,
119 &row->fields[column->index],
120 &datum, &column->type);
121 ovsdb_datum_destroy(&datum, &column->type);
122 if (error) {
123 return error;
124 }
125 ovsdb_datum_swap(&datum, &new_datum);
126 }
1e19e50e
BP
127 ovsdb_datum_swap(&row->fields[column->index], &datum);
128 ovsdb_datum_destroy(&datum, &column->type);
129 }
130
131 return NULL;
132}
133
bd06962a
BP
134static struct ovsdb_error *
135ovsdb_file_txn_row_from_json(struct ovsdb_txn *txn, struct ovsdb_table *table,
2ccd66f5 136 bool converting, bool row_contains_diff,
bd06962a
BP
137 const struct uuid *row_uuid, struct json *json)
138{
139 const struct ovsdb_row *row = ovsdb_table_get_row(table, row_uuid);
140 if (json->type == JSON_NULL) {
141 if (!row) {
142 return ovsdb_syntax_error(NULL, NULL, "transaction deletes "
143 "row "UUID_FMT" that does not exist",
144 UUID_ARGS(row_uuid));
145 }
146 ovsdb_txn_row_delete(txn, row);
147 return NULL;
148 } else if (row) {
1e19e50e 149 return ovsdb_file_update_row_from_json(ovsdb_txn_row_modify(txn, row),
2ccd66f5
IM
150 converting, row_contains_diff,
151 json);
bd06962a
BP
152 } else {
153 struct ovsdb_error *error;
154 struct ovsdb_row *new;
155
156 new = ovsdb_row_create(table);
157 *ovsdb_row_get_uuid_rw(new) = *row_uuid;
2ccd66f5
IM
158 error = ovsdb_file_update_row_from_json(new, converting,
159 row_contains_diff, json);
bd06962a
BP
160 if (error) {
161 ovsdb_row_destroy(new);
3697c062
BP
162 } else {
163 ovsdb_txn_row_insert(txn, new);
bd06962a 164 }
bd06962a
BP
165 return error;
166 }
167}
168
169static struct ovsdb_error *
170ovsdb_file_txn_table_from_json(struct ovsdb_txn *txn,
1e19e50e 171 struct ovsdb_table *table,
2ccd66f5
IM
172 bool converting,
173 bool row_contains_diff,
174 struct json *json)
bd06962a
BP
175{
176 struct shash_node *node;
177
178 if (json->type != JSON_OBJECT) {
179 return ovsdb_syntax_error(json, NULL, "object expected");
180 }
181
fa37affa 182 SHASH_FOR_EACH (node, json->object) {
bd06962a
BP
183 const char *uuid_string = node->name;
184 struct json *txn_row_json = node->data;
185 struct ovsdb_error *error;
186 struct uuid row_uuid;
187
188 if (!uuid_from_string(&row_uuid, uuid_string)) {
189 return ovsdb_syntax_error(json, NULL, "\"%s\" is not a valid UUID",
190 uuid_string);
191 }
192
1e19e50e 193 error = ovsdb_file_txn_row_from_json(txn, table, converting,
2ccd66f5 194 row_contains_diff,
1e19e50e 195 &row_uuid, txn_row_json);
bd06962a
BP
196 if (error) {
197 return error;
198 }
199 }
200
201 return NULL;
202}
203
ada496b5
BP
204/* Converts 'json' to an ovsdb_txn for 'db', storing the new transaction in
205 * '*txnp'. Returns NULL if successful, otherwise an error.
206 *
207 * If 'converting' is true, then unknown table and column names are ignored
208 * (which can ease upgrading and downgrading schemas); otherwise, they are
2958f35b 209 * treated as errors. */
1b1d2e6d 210struct ovsdb_error *
bd06962a 211ovsdb_file_txn_from_json(struct ovsdb *db, const struct json *json,
2958f35b 212 bool converting, struct ovsdb_txn **txnp)
bd06962a
BP
213{
214 struct ovsdb_error *error;
215 struct shash_node *node;
216 struct ovsdb_txn *txn;
217
218 *txnp = NULL;
ada496b5 219
bd06962a
BP
220 if (json->type != JSON_OBJECT) {
221 return ovsdb_syntax_error(json, NULL, "object expected");
222 }
223
2ccd66f5
IM
224 struct json *is_diff = shash_find_data(json->object, "_is_diff");
225 bool row_contains_diff = false;
226
227 if (is_diff && is_diff->type == JSON_TRUE) {
228 row_contains_diff = true;
229 }
230
bd06962a 231 txn = ovsdb_txn_create(db);
fa37affa 232 SHASH_FOR_EACH (node, json->object) {
bd06962a 233 const char *table_name = node->name;
ada496b5 234 struct json *node_json = node->data;
bd06962a
BP
235 struct ovsdb_table *table;
236
237 table = shash_find_data(&db->tables, table_name);
238 if (!table) {
d171b584 239 if (!strcmp(table_name, "_date")
ada496b5 240 && node_json->type == JSON_INTEGER) {
ada496b5 241 continue;
2ccd66f5
IM
242 } else if (!strcmp(table_name, "_is_diff")
243 && (node_json->type == JSON_TRUE
244 || node_json->type == JSON_FALSE)) {
245 continue;
ada496b5 246 } else if (!strcmp(table_name, "_comment") || converting) {
d171b584
BP
247 continue;
248 }
249
bd06962a
BP
250 error = ovsdb_syntax_error(json, "unknown table",
251 "No table named %s.", table_name);
252 goto error;
253 }
254
1e19e50e 255 error = ovsdb_file_txn_table_from_json(txn, table, converting,
2ccd66f5 256 row_contains_diff, node_json);
bd06962a
BP
257 if (error) {
258 goto error;
259 }
260 }
261 *txnp = txn;
262 return NULL;
263
264error:
265 ovsdb_txn_abort(txn);
266 return error;
267}
1e19e50e 268
1b1d2e6d
BP
269static struct ovsdb_error * OVS_WARN_UNUSED_RESULT
270ovsdb_convert_table(struct ovsdb_txn *txn,
271 const struct ovsdb_table *src_table,
272 struct ovsdb_table *dst_table)
1e19e50e 273{
1b1d2e6d
BP
274 const struct ovsdb_row *src_row;
275 HMAP_FOR_EACH (src_row, hmap_node, &src_table->rows) {
276 struct ovsdb_row *dst_row = ovsdb_row_create(dst_table);
277 *ovsdb_row_get_uuid_rw(dst_row) = *ovsdb_row_get_uuid(src_row);
1e19e50e 278
1b1d2e6d
BP
279 struct shash_node *node;
280 SHASH_FOR_EACH (node, &src_table->schema->columns) {
281 const struct ovsdb_column *src_column = node->data;
282 if (src_column->index == OVSDB_COL_UUID ||
283 src_column->index == OVSDB_COL_VERSION) {
284 continue;
285 }
1e19e50e 286
1b1d2e6d
BP
287 const struct ovsdb_column *dst_column
288 = shash_find_data(&dst_table->schema->columns,
289 src_column->name);
290 if (!dst_column) {
291 continue;
292 }
1e19e50e 293
dee6478d
DS
294 ovsdb_datum_destroy(&dst_row->fields[dst_column->index],
295 &dst_column->type);
296
1b1d2e6d
BP
297 struct ovsdb_error *error = ovsdb_datum_convert(
298 &dst_row->fields[dst_column->index], &dst_column->type,
299 &src_row->fields[src_column->index], &src_column->type);
300 if (error) {
dee6478d 301 ovsdb_datum_init_empty(&dst_row->fields[dst_column->index]);
1b1d2e6d
BP
302 ovsdb_row_destroy(dst_row);
303 return error;
304 }
1e19e50e 305 }
1e19e50e 306
1b1d2e6d 307 ovsdb_txn_row_insert(txn, dst_row);
1e19e50e 308 }
1b1d2e6d 309 return NULL;
1e19e50e 310}
ada496b5 311
1b1d2e6d
BP
312/* Copies the data in 'src', converts it into the schema specified in
313 * 'new_schema', and puts it into a newly created, unbacked database, and
314 * stores a pointer to the new database in '*dstp'. Returns null if
315 * successful, otherwise an error; on error, stores NULL in '*dstp'. */
316struct ovsdb_error * OVS_WARN_UNUSED_RESULT
317ovsdb_convert(const struct ovsdb *src, const struct ovsdb_schema *new_schema,
318 struct ovsdb **dstp)
ada496b5 319{
1b1d2e6d
BP
320 struct ovsdb *dst = ovsdb_create(ovsdb_schema_clone(new_schema),
321 ovsdb_storage_create_unbacked());
322 struct ovsdb_txn *txn = ovsdb_txn_create(dst);
323 struct ovsdb_error *error = NULL;
e1ebc8ce 324
1b1d2e6d
BP
325 struct shash_node *node;
326 SHASH_FOR_EACH (node, &src->tables) {
327 const char *table_name = node->name;
328 struct ovsdb_table *src_table = node->data;
329 struct ovsdb_table *dst_table = shash_find_data(&dst->tables,
330 table_name);
331 if (!dst_table) {
332 continue;
333 }
bd06962a 334
1b1d2e6d
BP
335 error = ovsdb_convert_table(txn, src_table, dst_table);
336 if (error) {
337 goto error;
338 }
ada496b5
BP
339 }
340
1b1d2e6d
BP
341 error = ovsdb_txn_replay_commit(txn);
342 if (error) {
343 txn = NULL; /* ovsdb_txn_replay_commit() already aborted. */
344 goto error;
345 }
ada496b5 346
1b1d2e6d 347 *dstp = dst;
ada496b5 348 return NULL;
bd06962a 349
1b1d2e6d
BP
350error:
351 ovsdb_destroy(dst);
352 if (txn) {
353 ovsdb_txn_abort(txn);
354 }
355 *dstp = NULL;
356 return error;
357}
358\f
bd06962a 359static bool
afe20d5c
BP
360ovsdb_file_change_cb(const struct ovsdb_row *old,
361 const struct ovsdb_row *new,
362 const unsigned long int *changed,
363 void *ftxn_)
a3d573ed
BP
364{
365 struct ovsdb_file_txn *ftxn = ftxn_;
17d18afb 366 ovsdb_file_txn_add_row(ftxn, old, new, changed);
a3d573ed
BP
367 return true;
368}
369
4d0a31b6 370struct json *
1b1d2e6d 371ovsdb_to_txn_json(const struct ovsdb *db, const char *comment)
4d0a31b6 372{
1b1d2e6d
BP
373 struct ovsdb_file_txn ftxn;
374
375 ovsdb_file_txn_init(&ftxn);
376
377 struct shash_node *node;
378 SHASH_FOR_EACH (node, &db->tables) {
379 const struct ovsdb_table *table = node->data;
380 const struct ovsdb_row *row;
381
382 HMAP_FOR_EACH (row, hmap_node, &table->rows) {
383 ovsdb_file_txn_add_row(&ftxn, NULL, row, NULL);
384 }
4d0a31b6 385 }
1b1d2e6d
BP
386
387 return ovsdb_file_txn_annotate(ftxn.json, comment);
4d0a31b6
BP
388}
389
53178986
BP
390/* Returns 'txn' transformed into the JSON format that is used in OVSDB files.
391 * (But the caller must use ovsdb_file_txn_annotate() to add the _comment and
392 * _date members.) If 'txn' doesn't actually change anything, returns NULL */
1b1d2e6d 393struct json *
53178986 394ovsdb_file_txn_to_json(const struct ovsdb_txn *txn)
a3d573ed 395{
a3d573ed
BP
396 struct ovsdb_file_txn ftxn;
397
398 ovsdb_file_txn_init(&ftxn);
afe20d5c 399 ovsdb_txn_for_each_change(txn, ovsdb_file_change_cb, &ftxn);
53178986
BP
400 return ftxn.json;
401}
402
1b1d2e6d
BP
403struct json *
404ovsdb_file_txn_annotate(struct json *json, const char *comment)
ada496b5 405{
1b1d2e6d
BP
406 if (!json) {
407 json = json_object_create();
ada496b5 408 }
1b1d2e6d
BP
409 if (comment) {
410 json_object_put_string(json, "_comment", comment);
120fb2ca 411 }
2ccd66f5
IM
412 if (use_column_diff) {
413 json_object_put(json, "_is_diff", json_boolean_create(true));
414 }
1b1d2e6d
BP
415 json_object_put(json, "_date", json_integer_create(time_wall_msec()));
416 return json;
a3d573ed 417}
a3d573ed
BP
418\f
419static void
420ovsdb_file_txn_init(struct ovsdb_file_txn *ftxn)
421{
422 ftxn->json = NULL;
423 ftxn->table_json = NULL;
424 ftxn->table = NULL;
425}
426
427static void
428ovsdb_file_txn_add_row(struct ovsdb_file_txn *ftxn,
429 const struct ovsdb_row *old,
17d18afb
BP
430 const struct ovsdb_row *new,
431 const unsigned long int *changed)
bd06962a 432{
bd06962a
BP
433 struct json *row;
434
435 if (!new) {
436 row = json_null_create();
437 } else {
438 struct shash_node *node;
439
88942565 440 row = old ? NULL : json_object_create();
bd06962a
BP
441 SHASH_FOR_EACH (node, &new->table->schema->columns) {
442 const struct ovsdb_column *column = node->data;
443 const struct ovsdb_type *type = &column->type;
444 unsigned int idx = column->index;
2ccd66f5
IM
445 struct ovsdb_datum datum;
446 struct json *column_json;
bd06962a
BP
447
448 if (idx != OVSDB_COL_UUID && column->persistent
c532bf9d 449 && (old
17d18afb 450 ? bitmap_is_set(changed, idx)
c532bf9d 451 : !ovsdb_datum_is_default(&new->fields[idx], type)))
bd06962a 452 {
2ccd66f5
IM
453 if (old && use_column_diff) {
454 ovsdb_datum_diff(&datum, &old->fields[idx],
455 &new->fields[idx], type);
456 column_json = ovsdb_datum_to_json(&datum, type);
457 ovsdb_datum_destroy(&datum, type);
458 } else {
459 column_json = ovsdb_datum_to_json(&new->fields[idx], type);
460 }
bd06962a
BP
461 if (!row) {
462 row = json_object_create();
463 }
2ccd66f5 464 json_object_put(row, column->name, column_json);
bd06962a
BP
465 }
466 }
467 }
468
469 if (row) {
470 struct ovsdb_table *table = new ? new->table : old->table;
471 char uuid[UUID_LEN + 1];
472
a3d573ed 473 if (table != ftxn->table) {
bd06962a 474 /* Create JSON object for transaction overall. */
a3d573ed
BP
475 if (!ftxn->json) {
476 ftxn->json = json_object_create();
bd06962a
BP
477 }
478
479 /* Create JSON object for transaction on this table. */
a3d573ed
BP
480 ftxn->table_json = json_object_create();
481 ftxn->table = table;
482 json_object_put(ftxn->json, table->schema->name, ftxn->table_json);
bd06962a
BP
483 }
484
485 /* Add row to transaction for this table. */
486 snprintf(uuid, sizeof uuid,
487 UUID_FMT, UUID_ARGS(ovsdb_row_get_uuid(new ? new : old)));
a3d573ed 488 json_object_put(ftxn->table_json, uuid, row);
bd06962a 489 }
bd06962a 490}
53178986 491\f
1b1d2e6d
BP
492static struct ovsdb *
493ovsdb_file_read__(const char *filename, bool rw,
494 struct ovsdb_schema *new_schema)
53178986 495{
1b1d2e6d
BP
496 struct ovsdb_storage *storage = ovsdb_storage_open_standalone(filename,
497 rw);
498 struct ovsdb_schema *schema = ovsdb_storage_read_schema(storage);
499 if (new_schema) {
500 ovsdb_schema_destroy(schema);
501 schema = new_schema;
502 }
503 struct ovsdb *ovsdb = ovsdb_create(schema, storage);
504 for (;;) {
505 /* Read a transaction. Bail if end-of-file. */
506 struct json *txn_json;
507 struct ovsdb_schema *schema2;
508 struct ovsdb_error *error = ovsdb_storage_read(storage, &schema2,
509 &txn_json, NULL);
510 if (error) {
511 ovs_fatal(0, "%s", ovsdb_error_to_string_free(error));
53178986 512 }
1b1d2e6d
BP
513 ovs_assert(!schema2);
514 if (!txn_json) {
515 break;
53178986
BP
516 }
517
1b1d2e6d
BP
518 /* Apply transaction to database. */
519 struct ovsdb_txn *txn;
520 error = ovsdb_file_txn_from_json(ovsdb, txn_json, new_schema != NULL,
521 &txn);
53178986 522 if (error) {
1b1d2e6d 523 ovs_fatal(0, "%s", ovsdb_error_to_string_free(error));
53178986 524 }
53178986 525 json_destroy(txn_json);
1b1d2e6d
BP
526
527 error = ovsdb_txn_replay_commit(txn);
53178986 528 if (error) {
1b1d2e6d
BP
529 ovsdb_storage_unread(storage);
530 break;
53178986
BP
531 }
532 }
1b1d2e6d
BP
533 return ovsdb;
534}
53178986 535
1b1d2e6d
BP
536/* Reads 'filename' as a standalone database. Returns the new database. On
537 * error, prints a message on stderr and terminates the process.
538 *
539 * If 'rw' is true, opens the database for read/write access, otherwise
540 * read-only.
541 *
542 * Consumes 'schema'. */
543struct ovsdb *
544ovsdb_file_read(const char *filename, bool rw)
545{
546 return ovsdb_file_read__(filename, rw, NULL);
547}
53178986 548
1b1d2e6d
BP
549/* Reads 'filename' as a standalone database, using 'schema' in place of the
550 * schema embedded in the file. Returns the new database. On error,
551 * prints a message on stderr and terminates the process.
552 *
553 * Consumes 'schema'. */
554struct ovsdb *
555ovsdb_file_read_as_schema(const char *filename, struct ovsdb_schema *schema)
556{
557 return ovsdb_file_read__(filename, false, schema);
53178986 558}