1 /* Copyright (c) 2009, 2010, 2011, 2012, 2013, 2016, 2017 Nicira, Inc.
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.
27 #include "openvswitch/json.h"
30 #include "ovsdb-error.h"
32 #include "socket-util.h"
36 #include "transaction.h"
40 #include "openvswitch/vlog.h"
42 VLOG_DEFINE_THIS_MODULE(ovsdb_file
);
44 /* A transaction being converted to JSON for writing to a file. */
45 struct 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'. */
51 static void ovsdb_file_txn_init(struct ovsdb_file_txn
*);
52 static void ovsdb_file_txn_add_row(struct ovsdb_file_txn
*,
53 const struct ovsdb_row
*old
,
54 const struct ovsdb_row
*new,
55 const unsigned long int *changed
);
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. */
59 static bool use_column_diff
= true;
62 ovsdb_file_column_diff_enable(struct unixctl_conn
*conn
, int argc OVS_UNUSED
,
63 const char *argv
[] OVS_UNUSED
,
66 use_column_diff
= true;
67 unixctl_command_reply(conn
, NULL
);
71 ovsdb_file_column_diff_disable(void)
73 if (!use_column_diff
) {
76 use_column_diff
= false;
77 unixctl_command_register("ovsdb/file/column-diff-enable", "",
78 0, 0, ovsdb_file_column_diff_enable
, NULL
);
81 static struct ovsdb_error
*
82 ovsdb_file_update_row_from_json(struct ovsdb_row
*row
, bool converting
,
83 bool row_contains_diff
,
84 const struct json
*json
)
86 struct ovsdb_table_schema
*schema
= row
->table
->schema
;
87 struct ovsdb_error
*error
;
88 struct shash_node
*node
;
90 if (json
->type
!= JSON_OBJECT
) {
91 return ovsdb_syntax_error(json
, NULL
, "row must be JSON object");
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
;
99 column
= ovsdb_table_schema_get_column(schema
, column_name
);
104 return ovsdb_syntax_error(json
, "unknown column",
105 "No column %s in table %s.",
106 column_name
, schema
->name
);
109 error
= ovsdb_datum_from_json(&datum
, &column
->type
, node
->data
, NULL
);
113 if (row_contains_diff
114 && !ovsdb_datum_is_default(&row
->fields
[column
->index
],
116 struct ovsdb_datum new_datum
;
118 error
= ovsdb_datum_apply_diff(&new_datum
,
119 &row
->fields
[column
->index
],
120 &datum
, &column
->type
);
121 ovsdb_datum_destroy(&datum
, &column
->type
);
125 ovsdb_datum_swap(&datum
, &new_datum
);
127 ovsdb_datum_swap(&row
->fields
[column
->index
], &datum
);
128 ovsdb_datum_destroy(&datum
, &column
->type
);
134 static struct ovsdb_error
*
135 ovsdb_file_txn_row_from_json(struct ovsdb_txn
*txn
, struct ovsdb_table
*table
,
136 bool converting
, bool row_contains_diff
,
137 const struct uuid
*row_uuid
, struct json
*json
)
139 const struct ovsdb_row
*row
= ovsdb_table_get_row(table
, row_uuid
);
140 if (json
->type
== JSON_NULL
) {
142 return ovsdb_syntax_error(NULL
, NULL
, "transaction deletes "
143 "row "UUID_FMT
" that does not exist",
144 UUID_ARGS(row_uuid
));
146 ovsdb_txn_row_delete(txn
, row
);
149 return ovsdb_file_update_row_from_json(ovsdb_txn_row_modify(txn
, row
),
150 converting
, row_contains_diff
,
153 struct ovsdb_error
*error
;
154 struct ovsdb_row
*new;
156 new = ovsdb_row_create(table
);
157 *ovsdb_row_get_uuid_rw(new) = *row_uuid
;
158 error
= ovsdb_file_update_row_from_json(new, converting
,
159 row_contains_diff
, json
);
161 ovsdb_row_destroy(new);
163 ovsdb_txn_row_insert(txn
, new);
169 static struct ovsdb_error
*
170 ovsdb_file_txn_table_from_json(struct ovsdb_txn
*txn
,
171 struct ovsdb_table
*table
,
173 bool row_contains_diff
,
176 struct shash_node
*node
;
178 if (json
->type
!= JSON_OBJECT
) {
179 return ovsdb_syntax_error(json
, NULL
, "object expected");
182 SHASH_FOR_EACH (node
, json
->object
) {
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
;
188 if (!uuid_from_string(&row_uuid
, uuid_string
)) {
189 return ovsdb_syntax_error(json
, NULL
, "\"%s\" is not a valid UUID",
193 error
= ovsdb_file_txn_row_from_json(txn
, table
, converting
,
195 &row_uuid
, txn_row_json
);
204 /* Converts 'json' to an ovsdb_txn for 'db', storing the new transaction in
205 * '*txnp'. Returns NULL if successful, otherwise an error.
207 * If 'converting' is true, then unknown table and column names are ignored
208 * (which can ease upgrading and downgrading schemas); otherwise, they are
209 * treated as errors. */
211 ovsdb_file_txn_from_json(struct ovsdb
*db
, const struct json
*json
,
212 bool converting
, struct ovsdb_txn
**txnp
)
214 struct ovsdb_error
*error
;
215 struct shash_node
*node
;
216 struct ovsdb_txn
*txn
;
220 if (json
->type
!= JSON_OBJECT
) {
221 return ovsdb_syntax_error(json
, NULL
, "object expected");
224 struct json
*is_diff
= shash_find_data(json
->object
, "_is_diff");
225 bool row_contains_diff
= false;
227 if (is_diff
&& is_diff
->type
== JSON_TRUE
) {
228 row_contains_diff
= true;
231 txn
= ovsdb_txn_create(db
);
232 SHASH_FOR_EACH (node
, json
->object
) {
233 const char *table_name
= node
->name
;
234 struct json
*node_json
= node
->data
;
235 struct ovsdb_table
*table
;
237 table
= shash_find_data(&db
->tables
, table_name
);
239 if (!strcmp(table_name
, "_date")
240 && node_json
->type
== JSON_INTEGER
) {
242 } else if (!strcmp(table_name
, "_is_diff")
243 && (node_json
->type
== JSON_TRUE
244 || node_json
->type
== JSON_FALSE
)) {
246 } else if (!strcmp(table_name
, "_comment") || converting
) {
250 error
= ovsdb_syntax_error(json
, "unknown table",
251 "No table named %s.", table_name
);
255 error
= ovsdb_file_txn_table_from_json(txn
, table
, converting
,
256 row_contains_diff
, node_json
);
265 ovsdb_txn_abort(txn
);
269 static struct ovsdb_error
* OVS_WARN_UNUSED_RESULT
270 ovsdb_convert_table(struct ovsdb_txn
*txn
,
271 const struct ovsdb_table
*src_table
,
272 struct ovsdb_table
*dst_table
)
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
);
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
) {
287 const struct ovsdb_column
*dst_column
288 = shash_find_data(&dst_table
->schema
->columns
,
294 ovsdb_datum_destroy(&dst_row
->fields
[dst_column
->index
],
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
);
301 ovsdb_datum_init_empty(&dst_row
->fields
[dst_column
->index
]);
302 ovsdb_row_destroy(dst_row
);
307 ovsdb_txn_row_insert(txn
, dst_row
);
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'. */
316 struct ovsdb_error
* OVS_WARN_UNUSED_RESULT
317 ovsdb_convert(const struct ovsdb
*src
, const struct ovsdb_schema
*new_schema
,
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
;
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
,
335 error
= ovsdb_convert_table(txn
, src_table
, dst_table
);
341 error
= ovsdb_txn_replay_commit(txn
);
343 txn
= NULL
; /* ovsdb_txn_replay_commit() already aborted. */
353 ovsdb_txn_abort(txn
);
360 ovsdb_file_change_cb(const struct ovsdb_row
*old
,
361 const struct ovsdb_row
*new,
362 const unsigned long int *changed
,
365 struct ovsdb_file_txn
*ftxn
= ftxn_
;
366 ovsdb_file_txn_add_row(ftxn
, old
, new, changed
);
371 ovsdb_to_txn_json(const struct ovsdb
*db
, const char *comment
)
373 struct ovsdb_file_txn ftxn
;
375 ovsdb_file_txn_init(&ftxn
);
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
;
382 HMAP_FOR_EACH (row
, hmap_node
, &table
->rows
) {
383 ovsdb_file_txn_add_row(&ftxn
, NULL
, row
, NULL
);
387 return ovsdb_file_txn_annotate(ftxn
.json
, comment
);
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 */
394 ovsdb_file_txn_to_json(const struct ovsdb_txn
*txn
)
396 struct ovsdb_file_txn ftxn
;
398 ovsdb_file_txn_init(&ftxn
);
399 ovsdb_txn_for_each_change(txn
, ovsdb_file_change_cb
, &ftxn
);
404 ovsdb_file_txn_annotate(struct json
*json
, const char *comment
)
407 json
= json_object_create();
410 json_object_put_string(json
, "_comment", comment
);
412 if (use_column_diff
) {
413 json_object_put(json
, "_is_diff", json_boolean_create(true));
415 json_object_put(json
, "_date", json_integer_create(time_wall_msec()));
420 ovsdb_file_txn_init(struct ovsdb_file_txn
*ftxn
)
423 ftxn
->table_json
= NULL
;
428 ovsdb_file_txn_add_row(struct ovsdb_file_txn
*ftxn
,
429 const struct ovsdb_row
*old
,
430 const struct ovsdb_row
*new,
431 const unsigned long int *changed
)
436 row
= json_null_create();
438 struct shash_node
*node
;
440 row
= old
? NULL
: json_object_create();
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
;
445 struct ovsdb_datum datum
;
446 struct json
*column_json
;
448 if (idx
!= OVSDB_COL_UUID
&& column
->persistent
450 ? bitmap_is_set(changed
, idx
)
451 : !ovsdb_datum_is_default(&new->fields
[idx
], type
)))
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
);
459 column_json
= ovsdb_datum_to_json(&new->fields
[idx
], type
);
462 row
= json_object_create();
464 json_object_put(row
, column
->name
, column_json
);
470 struct ovsdb_table
*table
= new ? new->table
: old
->table
;
471 char uuid
[UUID_LEN
+ 1];
473 if (table
!= ftxn
->table
) {
474 /* Create JSON object for transaction overall. */
476 ftxn
->json
= json_object_create();
479 /* Create JSON object for transaction on this table. */
480 ftxn
->table_json
= json_object_create();
482 json_object_put(ftxn
->json
, table
->schema
->name
, ftxn
->table_json
);
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
)));
488 json_object_put(ftxn
->table_json
, uuid
, row
);
492 static struct ovsdb
*
493 ovsdb_file_read__(const char *filename
, bool rw
,
494 struct ovsdb_schema
*new_schema
)
496 struct ovsdb_storage
*storage
= ovsdb_storage_open_standalone(filename
,
498 struct ovsdb_schema
*schema
= ovsdb_storage_read_schema(storage
);
500 ovsdb_schema_destroy(schema
);
503 struct ovsdb
*ovsdb
= ovsdb_create(schema
, storage
);
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
,
511 ovs_fatal(0, "%s", ovsdb_error_to_string_free(error
));
513 ovs_assert(!schema2
);
518 /* Apply transaction to database. */
519 struct ovsdb_txn
*txn
;
520 error
= ovsdb_file_txn_from_json(ovsdb
, txn_json
, new_schema
!= NULL
,
523 ovs_fatal(0, "%s", ovsdb_error_to_string_free(error
));
525 json_destroy(txn_json
);
527 error
= ovsdb_txn_replay_commit(txn
);
529 ovsdb_storage_unread(storage
);
536 /* Reads 'filename' as a standalone database. Returns the new database. On
537 * error, prints a message on stderr and terminates the process.
539 * If 'rw' is true, opens the database for read/write access, otherwise
542 * Consumes 'schema'. */
544 ovsdb_file_read(const char *filename
, bool rw
)
546 return ovsdb_file_read__(filename
, rw
, NULL
);
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.
553 * Consumes 'schema'. */
555 ovsdb_file_read_as_schema(const char *filename
, struct ovsdb_schema
*schema
)
557 return ovsdb_file_read__(filename
, false, schema
);