]>
Commit | Line | Data |
---|---|---|
f85f8ebb | 1 | /* |
19b276cb | 2 | * Copyright (c) 2009, 2010, 2011, 2012, 2013, 2016, 2017 Nicira, Inc. |
f85f8ebb BP |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
5 | * you may not use this file except in compliance with the License. | |
6 | * You may obtain a copy of the License at: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
10 | * Unless required by applicable law or agreed to in writing, software | |
11 | * distributed under the License is distributed on an "AS IS" BASIS, | |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
13 | * See the License for the specific language governing permissions and | |
14 | * limitations under the License. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include <errno.h> | |
19 | #include <fcntl.h> | |
20 | #include <getopt.h> | |
21 | #include <signal.h> | |
22 | #include <stdlib.h> | |
23 | #include <string.h> | |
24 | ||
4e92542c | 25 | #include "column.h" |
f85f8ebb BP |
26 | #include "command-line.h" |
27 | #include "compiler.h" | |
e4476f74 | 28 | #include "dirs.h" |
3e8a2ad1 | 29 | #include "openvswitch/dynamic-string.h" |
8a777cf6 | 30 | #include "fatal-signal.h" |
bd06962a | 31 | #include "file.h" |
1b1d2e6d | 32 | #include "hash.h" |
1e19e50e | 33 | #include "lockfile.h" |
41709ccc | 34 | #include "log.h" |
1b1d2e6d | 35 | #include "openvswitch/hmap.h" |
ee89ea7b | 36 | #include "openvswitch/json.h" |
f85f8ebb | 37 | #include "ovsdb.h" |
4e92542c | 38 | #include "ovsdb-data.h" |
f85f8ebb | 39 | #include "ovsdb-error.h" |
1b1d2e6d BP |
40 | #include "ovsdb-parser.h" |
41 | #include "raft.h" | |
42 | #include "raft-private.h" | |
43 | #include "smap.h" | |
1e19e50e | 44 | #include "socket-util.h" |
1b1d2e6d | 45 | #include "storage.h" |
f85f8ebb BP |
46 | #include "table.h" |
47 | #include "timeval.h" | |
1b1d2e6d | 48 | #include "transaction.h" |
f85f8ebb | 49 | #include "util.h" |
e6211adc | 50 | #include "openvswitch/vlog.h" |
5136ce49 | 51 | |
c6782bb0 BP |
52 | /* -m, --more: Verbosity level for "show-log" command output. */ |
53 | static int show_log_verbosity; | |
54 | ||
d6db7b3c LR |
55 | /* --role: RBAC role to use for "transact" and "query" commands. */ |
56 | static const char *rbac_role; | |
57 | ||
1b1d2e6d BP |
58 | /* --cid: Cluster ID for "join-cluster" command. */ |
59 | static struct uuid cid; | |
60 | ||
5f383751 | 61 | static const struct ovs_cmdl_command *get_all_commands(void); |
f85f8ebb | 62 | |
cab50449 | 63 | OVS_NO_RETURN static void usage(void); |
f85f8ebb BP |
64 | static void parse_options(int argc, char *argv[]); |
65 | ||
e4476f74 BP |
66 | static const char *default_db(void); |
67 | static const char *default_schema(void); | |
68 | ||
f85f8ebb BP |
69 | int |
70 | main(int argc, char *argv[]) | |
71 | { | |
1636c761 | 72 | struct ovs_cmdl_context ctx = { .argc = 0, }; |
f85f8ebb | 73 | set_program_name(argv[0]); |
f85f8ebb | 74 | parse_options(argc, argv); |
8a777cf6 | 75 | fatal_ignore_sigpipe(); |
1b1d2e6d | 76 | fatal_signal_init(); |
1636c761 RB |
77 | ctx.argc = argc - optind; |
78 | ctx.argv = argv + optind; | |
79 | ovs_cmdl_run_command(&ctx, get_all_commands()); | |
f85f8ebb BP |
80 | return 0; |
81 | } | |
82 | ||
83 | static void | |
84 | parse_options(int argc, char *argv[]) | |
85 | { | |
d6db7b3c | 86 | enum { |
1b1d2e6d BP |
87 | OPT_RBAC_ROLE = UCHAR_MAX + 1, |
88 | OPT_CID | |
d6db7b3c | 89 | }; |
07fc4ed3 | 90 | static const struct option long_options[] = { |
e3c17733 | 91 | {"more", no_argument, NULL, 'm'}, |
d6db7b3c | 92 | {"rbac-role", required_argument, NULL, OPT_RBAC_ROLE}, |
1b1d2e6d | 93 | {"cid", required_argument, NULL, OPT_CID}, |
e3c17733 BP |
94 | {"verbose", optional_argument, NULL, 'v'}, |
95 | {"help", no_argument, NULL, 'h'}, | |
66fa2c88 | 96 | {"option", no_argument, NULL, 'o'}, |
e3c17733 BP |
97 | {"version", no_argument, NULL, 'V'}, |
98 | {NULL, 0, NULL, 0}, | |
f85f8ebb | 99 | }; |
5f383751 | 100 | char *short_options = ovs_cmdl_long_options_to_short_options(long_options); |
f85f8ebb BP |
101 | |
102 | for (;;) { | |
103 | int c; | |
104 | ||
105 | c = getopt_long(argc, argv, short_options, long_options, NULL); | |
106 | if (c == -1) { | |
107 | break; | |
108 | } | |
109 | ||
110 | switch (c) { | |
c6782bb0 BP |
111 | case 'm': |
112 | show_log_verbosity++; | |
113 | break; | |
114 | ||
d6db7b3c LR |
115 | case OPT_RBAC_ROLE: |
116 | rbac_role = optarg; | |
117 | break; | |
118 | ||
1b1d2e6d BP |
119 | case OPT_CID: |
120 | if (!uuid_from_string(&cid, optarg) || uuid_is_zero(&cid)) { | |
121 | ovs_fatal(0, "%s: not a valid UUID", optarg); | |
122 | } | |
123 | break; | |
124 | ||
f85f8ebb BP |
125 | case 'h': |
126 | usage(); | |
127 | ||
66fa2c88 | 128 | case 'o': |
5f383751 | 129 | ovs_cmdl_print_options(long_options); |
66fa2c88 AW |
130 | exit(EXIT_SUCCESS); |
131 | ||
f85f8ebb | 132 | case 'V': |
55d5bb44 | 133 | ovs_print_version(0, 0); |
f85f8ebb BP |
134 | exit(EXIT_SUCCESS); |
135 | ||
136 | case 'v': | |
137 | vlog_set_verbosity(optarg); | |
138 | break; | |
139 | ||
140 | case '?': | |
141 | exit(EXIT_FAILURE); | |
142 | ||
143 | default: | |
144 | abort(); | |
145 | } | |
146 | } | |
147 | free(short_options); | |
148 | } | |
149 | ||
150 | static void | |
151 | usage(void) | |
152 | { | |
153 | printf("%s: Open vSwitch database management utility\n" | |
154 | "usage: %s [OPTIONS] COMMAND [ARG...]\n" | |
e4476f74 | 155 | " create [DB [SCHEMA]] create DB with the given SCHEMA\n" |
1b1d2e6d BP |
156 | " create-cluster DB CONTENTS LOCAL\n" |
157 | " create clustered DB with given CONTENTS and LOCAL address\n" | |
158 | " [--cid=UUID] join-cluster DB NAME LOCAL REMOTE...\n" | |
159 | " join clustered DB with given NAME and LOCAL and REMOTE addrs\n" | |
e4476f74 BP |
160 | " compact [DB [DST]] compact DB in-place (or to DST)\n" |
161 | " convert [DB [SCHEMA [DST]]] convert DB to SCHEMA (to DST)\n" | |
439902cb | 162 | " db-name [DB] report name of schema used by DB\n" |
e4476f74 BP |
163 | " db-version [DB] report version of schema used by DB\n" |
164 | " db-cksum [DB] report checksum of schema used by DB\n" | |
1b1d2e6d BP |
165 | " db-cid DB report cluster ID of clustered DB\n" |
166 | " db-sid DB report server ID of clustered DB\n" | |
167 | " db-local-address DB report local address of clustered DB\n" | |
168 | " db-is-clustered DB test whether DB is clustered\n" | |
169 | " db-is-standalone DB test whether DB is standalone\n" | |
439902cb | 170 | " schema-name [SCHEMA] report SCHEMA's name\n" |
e4476f74 BP |
171 | " schema-version [SCHEMA] report SCHEMA's schema version\n" |
172 | " schema-cksum [SCHEMA] report SCHEMA's checksum\n" | |
1b1d2e6d | 173 | " compare-versions A OP B compare OVSDB schema version numbers\n" |
e4476f74 BP |
174 | " query [DB] TRNS execute read-only transaction on DB\n" |
175 | " transact [DB] TRNS execute read/write transaction on DB\n" | |
00de46f9 AG |
176 | " cluster-to-standalone DB DB Convert clustered DB to\n" |
177 | " standalone DB when cluster is down and cannot be\n" | |
178 | " revived\n" | |
e4476f74 BP |
179 | " [-m]... show-log [DB] print DB's log entries\n" |
180 | "The default DB is %s.\n" | |
181 | "The default SCHEMA is %s.\n", | |
182 | program_name, program_name, default_db(), default_schema()); | |
f85f8ebb | 183 | vlog_usage(); |
d6db7b3c LR |
184 | printf("\ |
185 | \nOther options:\n\ | |
186 | -m, --more increase show-log verbosity\n\ | |
187 | --rbac-role=ROLE RBAC role for transact and query commands\n\ | |
188 | -h, --help display this help message\n\ | |
189 | -V, --version display version information\n"); | |
f85f8ebb BP |
190 | exit(EXIT_SUCCESS); |
191 | } | |
e4476f74 BP |
192 | |
193 | static const char * | |
194 | default_db(void) | |
195 | { | |
196 | static char *db; | |
197 | if (!db) { | |
f973f2af | 198 | db = xasprintf("%s/conf.db", ovs_dbdir()); |
e4476f74 BP |
199 | } |
200 | return db; | |
201 | } | |
202 | ||
203 | static const char * | |
204 | default_schema(void) | |
205 | { | |
206 | static char *schema; | |
207 | if (!schema) { | |
208 | schema = xasprintf("%s/vswitch.ovsschema", ovs_pkgdatadir()); | |
209 | } | |
210 | return schema; | |
211 | } | |
f85f8ebb BP |
212 | \f |
213 | static struct json * | |
214 | parse_json(const char *s) | |
215 | { | |
216 | struct json *json = json_from_string(s); | |
217 | if (json->type == JSON_STRING) { | |
fa37affa | 218 | ovs_fatal(0, "\"%s\": %s", s, json->string); |
f85f8ebb BP |
219 | } |
220 | return json; | |
221 | } | |
222 | ||
223 | static void | |
224 | print_and_free_json(struct json *json) | |
225 | { | |
226 | char *string = json_to_string(json, JSSF_SORT); | |
227 | json_destroy(json); | |
228 | puts(string); | |
229 | free(string); | |
230 | } | |
231 | ||
232 | static void | |
233 | check_ovsdb_error(struct ovsdb_error *error) | |
234 | { | |
235 | if (error) { | |
236 | ovs_fatal(0, "%s", ovsdb_error_to_string(error)); | |
237 | } | |
238 | } | |
1b1d2e6d BP |
239 | |
240 | /* Opens the standalone database 'filename' and returns its schema. */ | |
241 | static struct ovsdb_schema * | |
242 | read_standalone_schema(const char *filename) | |
243 | { | |
244 | struct ovsdb_storage *storage = ovsdb_storage_open_standalone(filename, | |
245 | false); | |
246 | struct ovsdb_schema *schema = ovsdb_storage_read_schema(storage); | |
247 | ovsdb_storage_close(storage); | |
248 | return schema; | |
249 | } | |
f85f8ebb BP |
250 | \f |
251 | static void | |
1636c761 | 252 | do_create(struct ovs_cmdl_context *ctx) |
f85f8ebb | 253 | { |
1636c761 RB |
254 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
255 | const char *schema_file_name = ctx->argc >= 3 ? ctx->argv[2] : default_schema(); | |
f85f8ebb | 256 | struct ovsdb_schema *schema; |
41709ccc | 257 | struct ovsdb_log *log; |
f85f8ebb BP |
258 | struct json *json; |
259 | ||
260 | /* Read schema from file and convert to JSON. */ | |
261 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
262 | json = ovsdb_schema_to_json(schema); | |
90cc4071 | 263 | ovsdb_schema_destroy(schema); |
f85f8ebb BP |
264 | |
265 | /* Create database file. */ | |
19b276cb | 266 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC, |
1e0b7e94 | 267 | OVSDB_LOG_CREATE_EXCL, -1, &log)); |
1d0dead5 | 268 | check_ovsdb_error(ovsdb_log_write_and_free(log, json)); |
f70b61d3 | 269 | check_ovsdb_error(ovsdb_log_commit_block(log)); |
41709ccc | 270 | ovsdb_log_close(log); |
1b1d2e6d | 271 | } |
f85f8ebb | 272 | |
1b1d2e6d BP |
273 | static void |
274 | do_create_cluster(struct ovs_cmdl_context *ctx) | |
275 | { | |
276 | const char *db_file_name = ctx->argv[1]; | |
277 | const char *src_file_name = ctx->argv[2]; | |
278 | const char *local = ctx->argv[3]; | |
279 | ||
280 | struct ovsdb_schema *schema; | |
281 | struct json *data; | |
282 | ||
283 | struct ovsdb_error *error = ovsdb_schema_from_file(src_file_name, &schema); | |
284 | if (!error) { | |
285 | /* It's just a schema file. */ | |
286 | data = json_object_create(); | |
287 | } else { | |
288 | /* Not a schema file. Try reading it as a standalone database. */ | |
289 | ovsdb_error_destroy(error); | |
290 | ||
291 | struct ovsdb *ovsdb = ovsdb_file_read(src_file_name, false); | |
292 | char *comment = xasprintf("created from %s", src_file_name); | |
293 | data = ovsdb_to_txn_json(ovsdb, comment); | |
294 | free(comment); | |
295 | schema = ovsdb_schema_clone(ovsdb->schema); | |
296 | ovsdb_destroy(ovsdb); | |
297 | } | |
298 | ||
299 | ovsdb_schema_persist_ephemeral_columns(schema, src_file_name); | |
300 | ||
301 | struct json *schema_json = ovsdb_schema_to_json(schema); | |
302 | ||
303 | /* Create database file. */ | |
304 | struct json *snapshot = json_array_create_2(schema_json, data); | |
305 | check_ovsdb_error(raft_create_cluster(db_file_name, schema->name, | |
306 | local, snapshot)); | |
307 | ovsdb_schema_destroy(schema); | |
308 | json_destroy(snapshot); | |
f85f8ebb BP |
309 | } |
310 | ||
1b1d2e6d BP |
311 | static void |
312 | do_join_cluster(struct ovs_cmdl_context *ctx) | |
313 | { | |
314 | const char *db_file_name = ctx->argv[1]; | |
315 | const char *name = ctx->argv[2]; | |
316 | const char *local = ctx->argv[3]; | |
317 | ||
318 | /* Check for a plausible 'name'. */ | |
319 | if (!ovsdb_parser_is_id(name)) { | |
320 | ovs_fatal(0, "%s: not a valid schema name (use \"schema-name\" " | |
321 | "command to find the correct name)", name); | |
322 | } | |
323 | ||
324 | /* Create database file. */ | |
325 | struct sset remote_addrs = SSET_INITIALIZER(&remote_addrs); | |
326 | for (size_t i = 4; i < ctx->argc; i++) { | |
327 | sset_add(&remote_addrs, ctx->argv[i]); | |
328 | } | |
329 | check_ovsdb_error(raft_join_cluster(db_file_name, name, local, | |
330 | &remote_addrs, | |
331 | uuid_is_zero(&cid) ? NULL : &cid)); | |
332 | sset_destroy(&remote_addrs); | |
333 | } | |
334 | ||
335 | static struct ovsdb_error * | |
336 | write_standalone_db(const char *file_name, const char *comment, | |
337 | const struct ovsdb *db) | |
338 | { | |
339 | struct ovsdb_log *log; | |
340 | struct ovsdb_error *error = ovsdb_log_open(file_name, OVSDB_MAGIC, | |
341 | OVSDB_LOG_CREATE, false, &log); | |
342 | if (error) { | |
343 | return error; | |
344 | } | |
345 | ||
1d0dead5 | 346 | error = ovsdb_log_write_and_free(log, ovsdb_schema_to_json(db->schema)); |
1b1d2e6d | 347 | if (!error) { |
1d0dead5 | 348 | error = ovsdb_log_write_and_free(log, ovsdb_to_txn_json(db, comment)); |
1b1d2e6d BP |
349 | } |
350 | ovsdb_log_close(log); | |
351 | ||
352 | if (error) { | |
353 | remove(file_name); | |
354 | } | |
355 | return error; | |
356 | } | |
357 | ||
358 | /* Reads 'src_name' and writes it back, compacted, to 'dst_name', adding the | |
359 | * specified 'comment'. If 'new_schema' is nonull, converts the databse to | |
360 | * that schema. | |
361 | * | |
362 | * Standalone databases only. */ | |
1e19e50e | 363 | static void |
a35ae81c | 364 | compact_or_convert(const char *src_name_, const char *dst_name_, |
1b1d2e6d | 365 | struct ovsdb_schema *new_schema, const char *comment) |
1e19e50e | 366 | { |
a35ae81c | 367 | bool in_place = dst_name_ == NULL; |
1e19e50e | 368 | |
a35ae81c BP |
369 | /* Dereference symlinks for source and destination names. In the in-place |
370 | * case this ensures that, if the source name is a symlink, we replace its | |
371 | * target instead of replacing the symlink by a regular file. In the | |
372 | * non-in-place, this has the same effect for the destination name. */ | |
1b1d2e6d BP |
373 | char *src_name = follow_symlinks(src_name_); |
374 | char *dst_name = (in_place | |
375 | ? xasprintf("%s.tmp", src_name) | |
376 | : follow_symlinks(dst_name_)); | |
a35ae81c | 377 | |
aebfc869 | 378 | /* Lock the source, if we will be replacing it. */ |
1b1d2e6d | 379 | struct lockfile *src_lock = NULL; |
1e19e50e | 380 | if (in_place) { |
1b1d2e6d | 381 | int retval = lockfile_lock(src_name, &src_lock); |
aebfc869 BP |
382 | if (retval) { |
383 | ovs_fatal(retval, "%s: failed to lock lockfile", src_name); | |
384 | } | |
1e19e50e BP |
385 | } |
386 | ||
aebfc869 | 387 | /* Get (temporary) destination and lock it. */ |
1b1d2e6d BP |
388 | struct lockfile *dst_lock = NULL; |
389 | int retval = lockfile_lock(dst_name, &dst_lock); | |
1e19e50e BP |
390 | if (retval) { |
391 | ovs_fatal(retval, "%s: failed to lock lockfile", dst_name); | |
392 | } | |
393 | ||
394 | /* Save a copy. */ | |
1b1d2e6d BP |
395 | struct ovsdb *ovsdb = (new_schema |
396 | ? ovsdb_file_read_as_schema(src_name, new_schema) | |
397 | : ovsdb_file_read(src_name, false)); | |
398 | ovsdb_storage_close(ovsdb->storage); | |
399 | ovsdb->storage = NULL; | |
400 | check_ovsdb_error(write_standalone_db(dst_name, comment, ovsdb)); | |
401 | ovsdb_destroy(ovsdb); | |
1e19e50e BP |
402 | |
403 | /* Replace source. */ | |
404 | if (in_place) { | |
10c119c3 GS |
405 | #ifdef _WIN32 |
406 | unlink(src_name); | |
407 | #endif | |
1e19e50e BP |
408 | if (rename(dst_name, src_name)) { |
409 | ovs_fatal(errno, "failed to rename \"%s\" to \"%s\"", | |
410 | dst_name, src_name); | |
411 | } | |
412 | fsync_parent_dir(dst_name); | |
1e19e50e BP |
413 | lockfile_unlock(src_lock); |
414 | } | |
415 | ||
416 | lockfile_unlock(dst_lock); | |
e49190c4 | 417 | |
a35ae81c BP |
418 | free(src_name); |
419 | free(dst_name); | |
1e19e50e BP |
420 | } |
421 | ||
422 | static void | |
1636c761 | 423 | do_compact(struct ovs_cmdl_context *ctx) |
1e19e50e | 424 | { |
1636c761 RB |
425 | const char *db = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
426 | const char *target = ctx->argc >= 3 ? ctx->argv[2] : NULL; | |
e4476f74 | 427 | |
8a07709c | 428 | compact_or_convert(db, target, NULL, "compacted by ovsdb-tool "VERSION); |
1e19e50e BP |
429 | } |
430 | ||
431 | static void | |
1636c761 | 432 | do_convert(struct ovs_cmdl_context *ctx) |
1e19e50e | 433 | { |
1636c761 RB |
434 | const char *db = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
435 | const char *schema = ctx->argc >= 3 ? ctx->argv[2] : default_schema(); | |
436 | const char *target = ctx->argc >= 4 ? ctx->argv[3] : NULL; | |
1e19e50e BP |
437 | struct ovsdb_schema *new_schema; |
438 | ||
e4476f74 BP |
439 | check_ovsdb_error(ovsdb_schema_from_file(schema, &new_schema)); |
440 | compact_or_convert(db, target, new_schema, | |
8a07709c | 441 | "converted by ovsdb-tool "VERSION); |
1e19e50e BP |
442 | } |
443 | ||
403e3a25 | 444 | static void |
1636c761 | 445 | do_needs_conversion(struct ovs_cmdl_context *ctx) |
403e3a25 | 446 | { |
1636c761 RB |
447 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
448 | const char *schema_file_name = ctx->argc >= 3 ? ctx->argv[2] : default_schema(); | |
1b1d2e6d BP |
449 | struct ovsdb_schema *schema1 = read_standalone_schema(db_file_name); |
450 | struct ovsdb_schema *schema2; | |
403e3a25 | 451 | |
403e3a25 BP |
452 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema2)); |
453 | puts(ovsdb_schema_equal(schema1, schema2) ? "no" : "yes"); | |
454 | ovsdb_schema_destroy(schema1); | |
455 | ovsdb_schema_destroy(schema2); | |
456 | } | |
457 | ||
439902cb BP |
458 | static void |
459 | do_db_name(struct ovs_cmdl_context *ctx) | |
460 | { | |
461 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); | |
439902cb | 462 | |
1b1d2e6d BP |
463 | struct ovsdb_log *log; |
464 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC"|"RAFT_MAGIC, | |
465 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
466 | if (!strcmp(ovsdb_log_get_magic(log), OVSDB_MAGIC)) { | |
467 | struct json *schema_json; | |
468 | check_ovsdb_error(ovsdb_log_read(log, &schema_json)); | |
469 | ||
470 | struct ovsdb_schema *schema; | |
471 | check_ovsdb_error(ovsdb_schema_from_json(schema_json, &schema)); | |
472 | ||
473 | puts(schema->name); | |
474 | ||
475 | ovsdb_schema_destroy(schema); | |
476 | json_destroy(schema_json); | |
477 | } else if (!strcmp(ovsdb_log_get_magic(log), RAFT_MAGIC)) { | |
478 | struct raft_metadata md; | |
479 | check_ovsdb_error(raft_read_metadata(log, &md)); | |
480 | puts(md.name); | |
481 | raft_metadata_destroy(&md); | |
482 | } else { | |
483 | OVS_NOT_REACHED(); | |
484 | } | |
485 | ||
486 | ovsdb_log_close(log); | |
439902cb BP |
487 | } |
488 | ||
8159b984 | 489 | static void |
1636c761 | 490 | do_db_version(struct ovs_cmdl_context *ctx) |
8159b984 | 491 | { |
1636c761 | 492 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
1b1d2e6d | 493 | struct ovsdb_schema *schema = read_standalone_schema(db_file_name); |
8159b984 | 494 | |
e1ebc8ce BP |
495 | puts(schema->version); |
496 | ovsdb_schema_destroy(schema); | |
8159b984 BP |
497 | } |
498 | ||
6aa09313 | 499 | static void |
1636c761 | 500 | do_db_cksum(struct ovs_cmdl_context *ctx) |
6aa09313 | 501 | { |
1636c761 | 502 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); |
1b1d2e6d | 503 | struct ovsdb_schema *schema = read_standalone_schema(db_file_name); |
6aa09313 BP |
504 | puts(schema->cksum); |
505 | ovsdb_schema_destroy(schema); | |
506 | } | |
507 | ||
1b1d2e6d BP |
508 | static struct raft_metadata |
509 | read_cluster_metadata(const char *filename) | |
510 | { | |
511 | struct ovsdb_log *log; | |
512 | check_ovsdb_error(ovsdb_log_open(filename, OVSDB_MAGIC"|"RAFT_MAGIC, | |
513 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
514 | if (strcmp(ovsdb_log_get_magic(log), RAFT_MAGIC)) { | |
515 | ovs_fatal(0, "%s: not a clustered database", filename); | |
516 | } | |
517 | ||
518 | struct raft_metadata md; | |
519 | check_ovsdb_error(raft_read_metadata(log, &md)); | |
520 | ||
521 | ovsdb_log_close(log); | |
522 | ||
523 | return md; | |
524 | } | |
525 | ||
8159b984 | 526 | static void |
1b1d2e6d BP |
527 | do_db_cid(struct ovs_cmdl_context *ctx) |
528 | { | |
529 | const char *db_file_name = ctx->argv[1]; | |
530 | struct raft_metadata md = read_cluster_metadata(db_file_name); | |
531 | if (uuid_is_zero(&md.cid)) { | |
532 | fprintf(stderr, "%s: cluster ID not yet known\n", db_file_name); | |
533 | exit(2); | |
534 | } | |
535 | printf(UUID_FMT"\n", UUID_ARGS(&md.cid)); | |
536 | raft_metadata_destroy(&md); | |
537 | } | |
538 | ||
539 | static void | |
540 | do_db_sid(struct ovs_cmdl_context *ctx) | |
541 | { | |
542 | const char *db_file_name = ctx->argv[1]; | |
543 | struct raft_metadata md = read_cluster_metadata(db_file_name); | |
544 | printf(UUID_FMT"\n", UUID_ARGS(&md.sid)); | |
545 | raft_metadata_destroy(&md); | |
546 | } | |
547 | ||
548 | static void | |
549 | do_db_local_address(struct ovs_cmdl_context *ctx) | |
550 | { | |
551 | const char *db_file_name = ctx->argv[1]; | |
552 | struct raft_metadata md = read_cluster_metadata(db_file_name); | |
553 | puts(md.local); | |
554 | raft_metadata_destroy(&md); | |
555 | } | |
556 | ||
557 | static void | |
558 | do_db_has_magic(struct ovs_cmdl_context *ctx, const char *magic) | |
559 | { | |
560 | const char *filename = ctx->argv[1]; | |
561 | struct ovsdb_log *log; | |
562 | ||
563 | check_ovsdb_error(ovsdb_log_open(filename, OVSDB_MAGIC"|"RAFT_MAGIC, | |
564 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
565 | if (strcmp(ovsdb_log_get_magic(log), magic)) { | |
566 | exit(2); | |
567 | } | |
568 | } | |
569 | ||
570 | static void | |
571 | do_db_is_clustered(struct ovs_cmdl_context *ctx) | |
572 | { | |
573 | do_db_has_magic(ctx, RAFT_MAGIC); | |
574 | } | |
575 | ||
576 | static void | |
577 | do_db_is_standalone(struct ovs_cmdl_context *ctx) | |
578 | { | |
579 | do_db_has_magic(ctx, OVSDB_MAGIC); | |
580 | } | |
581 | ||
582 | static void | |
583 | do_schema_name(struct ovs_cmdl_context *ctx) | |
8159b984 | 584 | { |
1636c761 | 585 | const char *schema_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); |
8159b984 BP |
586 | struct ovsdb_schema *schema; |
587 | ||
588 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 589 | puts(schema->name); |
8159b984 BP |
590 | ovsdb_schema_destroy(schema); |
591 | } | |
592 | ||
6aa09313 | 593 | static void |
1b1d2e6d | 594 | do_schema_version(struct ovs_cmdl_context *ctx) |
6aa09313 | 595 | { |
1636c761 | 596 | const char *schema_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); |
6aa09313 BP |
597 | struct ovsdb_schema *schema; |
598 | ||
599 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 600 | puts(schema->version); |
6aa09313 BP |
601 | ovsdb_schema_destroy(schema); |
602 | } | |
603 | ||
439902cb | 604 | static void |
1b1d2e6d | 605 | do_schema_cksum(struct ovs_cmdl_context *ctx) |
439902cb BP |
606 | { |
607 | const char *schema_file_name | |
608 | = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); | |
609 | struct ovsdb_schema *schema; | |
610 | ||
611 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 612 | puts(schema->cksum); |
439902cb BP |
613 | ovsdb_schema_destroy(schema); |
614 | } | |
615 | ||
1b1d2e6d | 616 | /* Standalone databases only. */ |
f85f8ebb | 617 | static void |
1b1d2e6d | 618 | transact(struct ovs_cmdl_context *ctx, bool rw) |
f85f8ebb | 619 | { |
1b1d2e6d BP |
620 | const char *db_file_name = ctx->argc >= 3 ? ctx->argv[1] : default_db(); |
621 | const char *transaction = ctx->argv[ctx->argc - 1]; | |
f85f8ebb | 622 | |
1b1d2e6d BP |
623 | struct ovsdb *ovsdb = ovsdb_file_read(db_file_name, rw); |
624 | struct json *request = parse_json(transaction); | |
625 | struct json *result = ovsdb_execute(ovsdb, NULL, request, false, | |
626 | rbac_role, NULL, 0, NULL); | |
f85f8ebb BP |
627 | json_destroy(request); |
628 | ||
629 | print_and_free_json(result); | |
1b1d2e6d | 630 | ovsdb_destroy(ovsdb); |
f85f8ebb BP |
631 | } |
632 | ||
633 | static void | |
1636c761 | 634 | do_query(struct ovs_cmdl_context *ctx) |
f85f8ebb | 635 | { |
1b1d2e6d | 636 | transact(ctx, false); |
f85f8ebb BP |
637 | } |
638 | ||
639 | static void | |
1636c761 | 640 | do_transact(struct ovs_cmdl_context *ctx) |
f85f8ebb | 641 | { |
1b1d2e6d | 642 | transact(ctx, true); |
f85f8ebb BP |
643 | } |
644 | ||
c6782bb0 | 645 | static void |
1b1d2e6d | 646 | print_db_changes(struct shash *tables, struct smap *names, |
4e92542c | 647 | const struct ovsdb_schema *schema) |
c6782bb0 BP |
648 | { |
649 | struct shash_node *n1; | |
650 | ||
1b1d2e6d | 651 | int i = 0; |
c6782bb0 BP |
652 | SHASH_FOR_EACH (n1, tables) { |
653 | const char *table = n1->name; | |
4e92542c | 654 | struct ovsdb_table_schema *table_schema; |
c6782bb0 BP |
655 | struct json *rows = n1->data; |
656 | struct shash_node *n2; | |
657 | ||
658 | if (n1->name[0] == '_' || rows->type != JSON_OBJECT) { | |
659 | continue; | |
660 | } | |
661 | ||
1b1d2e6d BP |
662 | if (i++ == 0) { |
663 | putchar('\n'); | |
664 | } | |
665 | ||
666 | table_schema = schema ? shash_find_data(&schema->tables, table) : NULL; | |
c6782bb0 BP |
667 | SHASH_FOR_EACH (n2, json_object(rows)) { |
668 | const char *row_uuid = n2->name; | |
669 | struct json *columns = n2->data; | |
670 | struct shash_node *n3; | |
c6782bb0 | 671 | |
1b1d2e6d BP |
672 | const char *old_name = smap_get(names, row_uuid); |
673 | char *new_name = NULL; | |
c6782bb0 BP |
674 | if (columns->type == JSON_OBJECT) { |
675 | struct json *new_name_json; | |
676 | ||
677 | new_name_json = shash_find_data(json_object(columns), "name"); | |
678 | if (new_name_json) { | |
679 | new_name = json_to_string(new_name_json, JSSF_SORT); | |
c6782bb0 BP |
680 | } |
681 | } | |
682 | ||
5a0e4aec | 683 | printf(" table %s", table); |
c6782bb0 BP |
684 | |
685 | if (!old_name) { | |
686 | if (new_name) { | |
a0a15a00 | 687 | printf(" insert row %s (%.8s):\n", new_name, row_uuid); |
c6782bb0 BP |
688 | } else { |
689 | printf(" insert row %.8s:\n", row_uuid); | |
690 | } | |
691 | } else { | |
a0a15a00 | 692 | printf(" row %s (%.8s):\n", old_name, row_uuid); |
c6782bb0 BP |
693 | } |
694 | ||
695 | if (columns->type == JSON_OBJECT) { | |
696 | if (show_log_verbosity > 1) { | |
697 | SHASH_FOR_EACH (n3, json_object(columns)) { | |
698 | const char *column = n3->name; | |
4e92542c | 699 | const struct ovsdb_column *column_schema; |
c6782bb0 | 700 | struct json *value = n3->data; |
4e92542c BP |
701 | char *value_string = NULL; |
702 | ||
703 | column_schema = | |
704 | (table_schema | |
705 | ? shash_find_data(&table_schema->columns, column) | |
706 | : NULL); | |
707 | if (column_schema) { | |
4e92542c | 708 | const struct ovsdb_type *type; |
a15fce8e | 709 | struct ovsdb_error *error; |
4e92542c BP |
710 | struct ovsdb_datum datum; |
711 | ||
712 | type = &column_schema->type; | |
713 | error = ovsdb_datum_from_json(&datum, type, | |
714 | value, NULL); | |
715 | if (!error) { | |
716 | struct ds s; | |
717 | ||
718 | ds_init(&s); | |
719 | ovsdb_datum_to_string(&datum, type, &s); | |
720 | value_string = ds_steal_cstr(&s); | |
a15fce8e BP |
721 | } else { |
722 | ovsdb_error_destroy(error); | |
4e92542c BP |
723 | } |
724 | } | |
725 | if (!value_string) { | |
726 | value_string = json_to_string(value, JSSF_SORT); | |
727 | } | |
5a0e4aec | 728 | printf(" %s=%s\n", column, value_string); |
c6782bb0 BP |
729 | free(value_string); |
730 | } | |
731 | } | |
1b1d2e6d BP |
732 | |
733 | if (new_name && (!old_name || strcmp(old_name, new_name))) { | |
734 | smap_replace_nocopy(names, row_uuid, new_name); | |
735 | new_name = NULL; | |
736 | } else if (!old_name) { | |
737 | smap_add_nocopy(names, xstrdup(row_uuid), | |
738 | xmemdup0(row_uuid, 8)); | |
c6782bb0 BP |
739 | } |
740 | } else if (columns->type == JSON_NULL) { | |
5a0e4aec | 741 | printf(" delete row\n"); |
1b1d2e6d | 742 | smap_remove(names, row_uuid); |
c6782bb0 BP |
743 | } |
744 | ||
1b1d2e6d | 745 | free(new_name); |
c6782bb0 BP |
746 | } |
747 | } | |
748 | } | |
749 | ||
722f6301 | 750 | static void |
1b1d2e6d BP |
751 | print_change_record(const struct json *json, const struct ovsdb_schema *schema, |
752 | struct smap *names) | |
722f6301 | 753 | { |
1b1d2e6d BP |
754 | if (!json || json->type != JSON_OBJECT) { |
755 | return; | |
756 | } | |
722f6301 | 757 | |
1b1d2e6d BP |
758 | struct json *date, *comment; |
759 | ||
760 | date = shash_find_data(json_object(json), "_date"); | |
761 | if (date && date->type == JSON_INTEGER) { | |
762 | long long int t = json_integer(date); | |
763 | char *s; | |
764 | ||
765 | if (t < INT32_MAX) { | |
766 | /* Older versions of ovsdb wrote timestamps in seconds. */ | |
767 | t *= 1000; | |
768 | } | |
769 | ||
770 | s = xastrftime_msec(" %Y-%m-%d %H:%M:%S.###", t, true); | |
771 | fputs(s, stdout); | |
772 | free(s); | |
773 | } | |
774 | ||
775 | comment = shash_find_data(json_object(json), "_comment"); | |
776 | if (comment && comment->type == JSON_STRING) { | |
777 | printf(" \"%s\"", json_string(comment)); | |
778 | } | |
779 | ||
780 | if (show_log_verbosity > 0) { | |
781 | print_db_changes(json_object(json), names, schema); | |
782 | } | |
783 | } | |
784 | ||
785 | static void | |
786 | do_show_log_standalone(struct ovsdb_log *log) | |
787 | { | |
788 | struct smap names = SMAP_INITIALIZER(&names); | |
789 | struct ovsdb_schema *schema = NULL; | |
790 | ||
791 | for (unsigned int i = 0; ; i++) { | |
722f6301 BP |
792 | struct json *json; |
793 | ||
794 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
795 | if (!json) { | |
796 | break; | |
797 | } | |
798 | ||
799 | printf("record %u:", i); | |
4e92542c BP |
800 | if (i == 0) { |
801 | check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); | |
802 | printf(" \"%s\" schema, version=\"%s\", cksum=\"%s\"\n", | |
803 | schema->name, schema->version, schema->cksum); | |
1b1d2e6d BP |
804 | } else { |
805 | print_change_record(json, schema, &names); | |
806 | } | |
807 | json_destroy(json); | |
808 | putchar('\n'); | |
809 | } | |
810 | ||
811 | ovsdb_schema_destroy(schema); | |
812 | smap_destroy(&names); | |
813 | } | |
814 | ||
815 | static void | |
816 | print_servers(const char *name, const struct json *servers) | |
817 | { | |
818 | if (!servers) { | |
819 | return; | |
820 | } | |
722f6301 | 821 | |
1b1d2e6d | 822 | printf(" %s: ", name); |
1ff1065c | 823 | |
1b1d2e6d BP |
824 | const struct shash_node **nodes = shash_sort(json_object(servers)); |
825 | size_t n = shash_count(json_object(servers)); | |
826 | for (size_t i = 0; i < n; i++) { | |
827 | if (i > 0) { | |
828 | printf(", "); | |
829 | } | |
830 | ||
831 | const struct shash_node *node = nodes[i]; | |
832 | printf("%.4s(", node->name); | |
833 | ||
834 | const struct json *address = node->data; | |
835 | char *s = json_to_string(address, JSSF_SORT); | |
836 | fputs(s, stdout); | |
837 | free(s); | |
838 | ||
839 | putchar(')'); | |
840 | } | |
841 | free(nodes); | |
842 | putchar('\n'); | |
843 | } | |
844 | ||
845 | static void | |
846 | print_data(const char *prefix, const struct json *data, | |
847 | struct ovsdb_schema **schemap, struct smap *names) | |
848 | { | |
849 | if (!data) { | |
850 | return; | |
851 | } | |
852 | ||
853 | if (json_array(data)->n != 2) { | |
854 | printf(" ***invalid data***\n"); | |
855 | return; | |
856 | } | |
857 | ||
858 | const struct json *schema_json = json_array(data)->elems[0]; | |
859 | if (schema_json->type != JSON_NULL) { | |
860 | struct ovsdb_schema *schema; | |
861 | ||
862 | check_ovsdb_error(ovsdb_schema_from_json(schema_json, &schema)); | |
863 | printf(" %sschema: \"%s\", version=\"%s\", cksum=\"%s\"\n", | |
864 | prefix, schema->name, schema->version, schema->cksum); | |
865 | ||
866 | ovsdb_schema_destroy(*schemap); | |
867 | *schemap = schema; | |
868 | } | |
869 | ||
870 | print_change_record(json_array(data)->elems[1], *schemap, names); | |
871 | } | |
872 | ||
873 | static void | |
874 | print_raft_header(const struct raft_header *h, | |
875 | struct ovsdb_schema **schemap, struct smap *names) | |
876 | { | |
877 | printf(" name: \"%s\'\n", h->name); | |
878 | printf(" local address: \"%s\"\n", h->local_address); | |
879 | printf(" server_id: "SID_FMT"\n", SID_ARGS(&h->sid)); | |
880 | if (!uuid_is_zero(&h->cid)) { | |
881 | printf(" cluster_id: "CID_FMT"\n", CID_ARGS(&h->cid)); | |
882 | } | |
883 | if (!sset_is_empty(&h->remote_addresses)) { | |
884 | printf(" remote_addresses:"); | |
885 | ||
886 | const char *s; | |
887 | SSET_FOR_EACH (s, &h->remote_addresses) { | |
888 | printf(" %s", s); | |
889 | } | |
890 | putchar('\n'); | |
891 | } | |
892 | if (h->snap_index) { | |
893 | printf(" prev_index: %"PRIu64"\n", h->snap_index); | |
894 | printf(" prev_term: %"PRIu64"\n", h->snap.term); | |
895 | print_servers("prev_servers", h->snap.servers); | |
896 | if (!uuid_is_zero(&h->snap.eid)) { | |
897 | printf(" prev_eid: %04x\n", uuid_prefix(&h->snap.eid, 4)); | |
898 | } | |
899 | print_data("prev_", h->snap.data, schemap, names); | |
900 | } | |
901 | } | |
902 | ||
903 | static void | |
904 | print_raft_record(const struct raft_record *r, | |
905 | struct ovsdb_schema **schemap, struct smap *names) | |
906 | { | |
907 | if (r->comment) { | |
908 | printf(" comment: \"%s\"\n", r->comment); | |
909 | } | |
910 | if (r->term) { | |
911 | printf(" term: %"PRIu64"\n", r->term); | |
912 | } | |
913 | ||
914 | switch (r->type) { | |
915 | case RAFT_REC_ENTRY: | |
916 | printf(" index: %"PRIu64"\n", r->entry.index); | |
917 | print_servers("servers", r->entry.servers); | |
918 | if (!uuid_is_zero(&r->entry.eid)) { | |
919 | printf(" eid: %04x\n", uuid_prefix(&r->entry.eid, 4)); | |
920 | } | |
921 | print_data("", r->entry.data, schemap, names); | |
922 | break; | |
923 | ||
924 | case RAFT_REC_TERM: | |
925 | break; | |
926 | ||
927 | case RAFT_REC_VOTE: | |
928 | printf(" vote: "SID_FMT"\n", SID_ARGS(&r->sid)); | |
929 | break; | |
930 | ||
931 | case RAFT_REC_NOTE: | |
932 | printf(" note: \"%s\"\n", r->note); | |
933 | break; | |
934 | ||
935 | case RAFT_REC_COMMIT_INDEX: | |
936 | printf(" commit_index: %"PRIu64"\n", r->commit_index); | |
937 | break; | |
938 | ||
939 | case RAFT_REC_LEADER: | |
940 | printf(" leader: "SID_FMT"\n", SID_ARGS(&r->sid)); | |
941 | break; | |
942 | ||
943 | default: | |
944 | OVS_NOT_REACHED(); | |
945 | } | |
946 | } | |
947 | ||
00de46f9 AG |
948 | static void |
949 | raft_header_to_standalone_log(const struct raft_header *h, | |
950 | struct ovsdb_log *db_log_data) | |
951 | { | |
952 | if (h->snap_index) { | |
953 | if (!h->snap.data || json_array(h->snap.data)->n != 2) { | |
954 | ovs_fatal(0, "Incorrect raft header data array length"); | |
955 | } | |
956 | ||
957 | struct json *schema_json = json_array(h->snap.data)->elems[0]; | |
958 | if (schema_json->type != JSON_NULL) { | |
959 | struct ovsdb_schema *schema; | |
960 | check_ovsdb_error(ovsdb_schema_from_json(schema_json, &schema)); | |
961 | ovsdb_schema_destroy(schema); | |
962 | check_ovsdb_error(ovsdb_log_write_and_free(db_log_data, | |
963 | schema_json)); | |
964 | } | |
965 | ||
966 | struct json *data_json = json_array(h->snap.data)->elems[1]; | |
967 | if (!data_json || data_json->type != JSON_OBJECT) { | |
968 | ovs_fatal(0, "Invalid raft header data"); | |
969 | } | |
970 | if (data_json->type != JSON_NULL) { | |
971 | check_ovsdb_error(ovsdb_log_write_and_free(db_log_data, | |
972 | data_json)); | |
973 | } | |
974 | } | |
975 | } | |
976 | ||
977 | static void | |
978 | raft_record_to_standalone_log(const struct raft_record *r, | |
979 | struct ovsdb_log *db_log_data) | |
980 | { | |
981 | if (r->type == RAFT_REC_ENTRY) { | |
982 | if (!r->entry.data) { | |
983 | return; | |
984 | } | |
985 | if (json_array(r->entry.data)->n != 2) { | |
986 | ovs_fatal(0, "Incorrect raft record array length"); | |
987 | } | |
988 | ||
989 | struct json *data_json = json_array(r->entry.data)->elems[1]; | |
990 | if (data_json->type != JSON_NULL) { | |
991 | check_ovsdb_error(ovsdb_log_write_and_free(db_log_data, | |
992 | data_json)); | |
993 | } | |
994 | } | |
995 | } | |
996 | ||
1b1d2e6d BP |
997 | static void |
998 | do_show_log_cluster(struct ovsdb_log *log) | |
999 | { | |
1000 | struct smap names = SMAP_INITIALIZER(&names); | |
1001 | struct ovsdb_schema *schema = NULL; | |
1002 | for (unsigned int i = 0; ; i++) { | |
1003 | struct json *json; | |
1004 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
1005 | if (!json) { | |
1006 | break; | |
1007 | } | |
1008 | ||
1009 | printf("record %u:\n", i); | |
1010 | struct ovsdb_error *error; | |
1011 | if (i == 0) { | |
1012 | struct raft_header h; | |
1013 | error = raft_header_from_json(&h, json); | |
1014 | if (!error) { | |
1015 | print_raft_header(&h, &schema, &names); | |
1016 | raft_header_uninit(&h); | |
1017 | } | |
1018 | } else { | |
1019 | struct raft_record r; | |
1020 | error = raft_record_from_json(&r, json); | |
1021 | if (!error) { | |
1022 | print_raft_record(&r, &schema, &names); | |
1023 | raft_record_uninit(&r); | |
1024 | } | |
1025 | } | |
1026 | if (error) { | |
1027 | char *s = ovsdb_error_to_string_free(error); | |
1028 | puts(s); | |
1029 | free(s); | |
1030 | } | |
1031 | ||
1032 | putchar('\n'); | |
1033 | } | |
1034 | ||
1035 | ovsdb_schema_destroy(schema); | |
1036 | smap_destroy(&names); | |
1037 | } | |
1038 | ||
1039 | static void | |
1040 | do_show_log(struct ovs_cmdl_context *ctx) | |
1041 | { | |
1042 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); | |
1043 | struct ovsdb_log *log; | |
1044 | ||
1045 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC"|"RAFT_MAGIC, | |
1046 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
1047 | if (!strcmp(ovsdb_log_get_magic(log), OVSDB_MAGIC)) { | |
1048 | do_show_log_standalone(log); | |
1049 | } else { | |
1050 | do_show_log_cluster(log); | |
1051 | } | |
1052 | ovsdb_log_close(log); | |
1053 | } | |
1054 | ||
1055 | struct server { | |
6bf2e3f6 | 1056 | struct ovsdb_log *log; |
1b1d2e6d BP |
1057 | const char *filename; |
1058 | const char *nickname; | |
1059 | ||
1060 | struct raft_header header; | |
1061 | ||
1062 | struct raft_record *records; | |
1063 | size_t n_records; | |
1064 | ||
1065 | struct raft_entry *snap; | |
1066 | struct raft_entry *entries; | |
1067 | uint64_t log_start, log_end; | |
1068 | }; | |
1069 | ||
1070 | struct leader { | |
1071 | /* In struct cluster's 'leaders', indexed by 'term'. */ | |
1072 | struct hmap_node hmap_node; | |
1073 | ||
1074 | /* This structure indicates that in 'term', 'server' reported that 'leader' | |
1075 | * was elected leader. When 'log_end' is nonzero, it additionally | |
1076 | * indicates 'leader''s log_end at the time it was elected. */ | |
1077 | uint64_t term; | |
1078 | struct server *server; | |
1079 | struct uuid leader; | |
1080 | uint64_t log_end; | |
1081 | }; | |
1082 | ||
1083 | struct commit { | |
1084 | /* In struct cluster's 'commits', indexed by 'term'. */ | |
1085 | struct hmap_node hmap_node; | |
1086 | ||
1087 | /* This structure indicates that in 'term', 'server' reported the commit | |
1088 | * index as 'index'. */ | |
1089 | uint64_t term; | |
1090 | struct server *server; | |
1091 | uint64_t index; /* Commit index. */ | |
1092 | }; | |
1093 | ||
1094 | struct cluster { | |
1095 | struct server *servers; | |
1096 | size_t n_servers; | |
1097 | ||
1098 | struct hmap leaders; /* Contains 'struct leader's. */ | |
1099 | ||
1100 | struct hmap commits; /* Contains 'struct commit's. */ | |
1101 | }; | |
1102 | ||
1103 | static const char * | |
1104 | get_server_name(const struct cluster *c, const struct uuid *sid, | |
1105 | char buf[SID_LEN + 1], size_t bufsize) | |
1106 | { | |
1107 | for (size_t i = 0; i < c->n_servers; i++) { | |
6233b87d | 1108 | struct server *s = &c->servers[i]; |
1b1d2e6d BP |
1109 | if (uuid_equals(&s->header.sid, sid)) { |
1110 | return s->filename; | |
1111 | } | |
1112 | } | |
1113 | ||
1114 | snprintf(buf, bufsize, SID_FMT, SID_ARGS(sid)); | |
1115 | return buf; | |
1116 | } | |
1117 | ||
1118 | static struct leader * | |
1119 | find_leader(struct cluster *c, uint64_t term) | |
1120 | { | |
1121 | struct leader *leader; | |
1122 | HMAP_FOR_EACH_WITH_HASH (leader, hmap_node, hash_uint64(term), | |
1123 | &c->leaders) { | |
1124 | if (term == leader->term) { | |
1125 | return leader; | |
1126 | } | |
1127 | } | |
1128 | return NULL; | |
1129 | } | |
1130 | ||
1131 | /* Records that 'server' reported that 'leader' was elected leader in 'term'. | |
1132 | * | |
1133 | * Checks the Election Safety Property: at most one leader may be elected in a | |
1134 | * single term (see Figure 3.2). */ | |
1135 | static void | |
1136 | record_leader(struct cluster *c, uint64_t term, struct server *server, | |
1137 | const struct uuid *leader) | |
1138 | { | |
1139 | bool server_is_leader = uuid_equals(&server->header.sid, leader); | |
1140 | struct leader *p = find_leader(c, term); | |
1141 | if (p) { | |
1142 | if (!uuid_equals(&p->leader, leader)) { | |
1143 | char buf1[SID_LEN + 1]; | |
1144 | char buf2[SID_LEN + 1]; | |
1145 | ovs_fatal(0, "term %"PRIu64" has two different leaders: " | |
1146 | "%s says that the leader is %s and " | |
1147 | "%s says that the leader is %s", | |
1148 | term, | |
1149 | p->server->filename, | |
1150 | get_server_name(c, &p->leader, buf1, sizeof buf1), | |
1151 | server->filename, | |
1152 | get_server_name(c, leader, buf2, sizeof buf2)); | |
1153 | } | |
1154 | if (server_is_leader && server->log_end > p->log_end) { | |
1155 | p->log_end = server->log_end; | |
1156 | } | |
1157 | } else { | |
1158 | p = xmalloc(sizeof *p); | |
1159 | hmap_insert(&c->leaders, &p->hmap_node, hash_uint64(term)); | |
1160 | p->term = term; | |
1161 | p->server = server; | |
1162 | p->leader = *leader; | |
1163 | if (server_is_leader) { | |
1164 | p->log_end = server->log_end; | |
1165 | } else { | |
1166 | p->log_end = 0; | |
1167 | } | |
1168 | } | |
1169 | } | |
1170 | ||
1171 | static struct commit * | |
1172 | find_commit(struct cluster *c, uint64_t term) | |
1173 | { | |
1174 | struct commit *commit; | |
1175 | HMAP_FOR_EACH_WITH_HASH (commit, hmap_node, hash_uint64(term), | |
1176 | &c->commits) { | |
1177 | if (term == commit->term) { | |
1178 | return commit; | |
1179 | } | |
1180 | } | |
1181 | return NULL; | |
1182 | } | |
1183 | ||
1184 | static void | |
1185 | record_commit(struct cluster *c, uint64_t term, struct server *server, | |
1186 | uint64_t commit_index) | |
1187 | { | |
1188 | struct commit *commit = find_commit(c, term); | |
1189 | if (commit) { | |
1190 | if (commit_index > commit->index) { | |
1191 | commit->server = server; | |
1192 | commit->index = commit_index; | |
1193 | } | |
1194 | } else { | |
1195 | commit = xmalloc(sizeof *commit); | |
1196 | hmap_insert(&c->commits, &commit->hmap_node, hash_uint64(term)); | |
1197 | commit->term = term; | |
1198 | commit->server = server; | |
1199 | commit->index = commit_index; | |
1200 | } | |
1201 | } | |
1202 | ||
1203 | static void | |
1204 | do_check_cluster(struct ovs_cmdl_context *ctx) | |
1205 | { | |
1206 | struct cluster c = { | |
1207 | .servers = xzalloc((ctx->argc - 1) * sizeof *c.servers), | |
1208 | .n_servers = 0, | |
1209 | .leaders = HMAP_INITIALIZER(&c.leaders), | |
1210 | .commits = HMAP_INITIALIZER(&c.commits), | |
1211 | }; | |
1212 | ||
1213 | uint64_t min_term = UINT64_MAX; | |
1214 | uint64_t max_term = 0; | |
1215 | ||
1216 | for (int i = 1; i < ctx->argc; i++) { | |
1217 | struct server *s = &c.servers[c.n_servers]; | |
1218 | s->filename = ctx->argv[i]; | |
1219 | ||
1b1d2e6d | 1220 | check_ovsdb_error(ovsdb_log_open(s->filename, RAFT_MAGIC, |
6bf2e3f6 | 1221 | OVSDB_LOG_READ_ONLY, -1, &s->log)); |
1b1d2e6d BP |
1222 | |
1223 | struct json *json; | |
6bf2e3f6 | 1224 | check_ovsdb_error(ovsdb_log_read(s->log, &json)); |
1b1d2e6d BP |
1225 | check_ovsdb_error(raft_header_from_json(&s->header, json)); |
1226 | json_destroy(json); | |
1227 | ||
1228 | if (s->header.joining) { | |
1229 | printf("%s has not joined the cluster, omitting\n", s->filename); | |
6bf2e3f6 | 1230 | ovsdb_log_close(s->log); |
1b1d2e6d BP |
1231 | continue; |
1232 | } | |
7073a83f BP |
1233 | for (size_t j = 0; j < c.n_servers; j++) { |
1234 | if (uuid_equals(&s->header.sid, &c.servers[j].header.sid)) { | |
1235 | ovs_fatal(0, "Duplicate server ID "SID_FMT" in %s and %s.", | |
1236 | SID_ARGS(&s->header.sid), | |
1237 | s->filename, c.servers[j].filename); | |
1238 | } | |
1239 | } | |
1b1d2e6d BP |
1240 | if (c.n_servers > 0) { |
1241 | struct server *s0 = &c.servers[0]; | |
1242 | if (!uuid_equals(&s0->header.cid, &s->header.cid)) { | |
1243 | ovs_fatal(0, "%s has cluster ID "CID_FMT" but %s " | |
1244 | "has cluster ID "CID_FMT, | |
1245 | s0->filename, CID_ARGS(&s0->header.cid), | |
1246 | s->filename, CID_ARGS(&s->header.cid)); | |
1247 | } | |
1248 | if (strcmp(s0->header.name, s->header.name)) { | |
1249 | ovs_fatal(0, "%s is named \"%s\" but %s is named \"%s\"", | |
1250 | s0->filename, s0->header.name, | |
1251 | s->filename, s->header.name); | |
1252 | } | |
1253 | } | |
6bf2e3f6 BP |
1254 | c.n_servers++; |
1255 | } | |
1256 | ||
1257 | for (struct server *s = c.servers; s < &c.servers[c.n_servers]; s++) { | |
1b1d2e6d BP |
1258 | s->snap = &s->header.snap; |
1259 | s->log_start = s->log_end = s->header.snap_index + 1; | |
1260 | ||
1261 | size_t allocated_records = 0; | |
1262 | size_t allocated_entries = 0; | |
1263 | ||
1264 | uint64_t term = 0; /* Current term. */ | |
1265 | struct uuid vote = UUID_ZERO; /* Server 's''s vote in 'term'. */ | |
1266 | struct uuid leader = UUID_ZERO; /* Cluster leader in 'term'. */ | |
1267 | uint64_t leader_rec_idx = 0; /* Index of last "leader" record. */ | |
1268 | ||
1269 | uint64_t commit_index = s->header.snap_index; | |
1270 | ||
1271 | for (unsigned long long int rec_idx = 1; ; rec_idx++) { | |
1272 | if (s->n_records >= allocated_records) { | |
1273 | s->records = x2nrealloc(s->records, &allocated_records, | |
1274 | sizeof *s->records); | |
1275 | } | |
6bf2e3f6 BP |
1276 | |
1277 | struct json *json; | |
1278 | check_ovsdb_error(ovsdb_log_read(s->log, &json)); | |
1b1d2e6d BP |
1279 | if (!json) { |
1280 | break; | |
1281 | } | |
1282 | struct raft_record *r = &s->records[s->n_records++]; | |
1283 | check_ovsdb_error(raft_record_from_json(r, json)); | |
1284 | json_destroy(json); | |
1285 | ||
1286 | if (r->term > term) { | |
1287 | term = r->term; | |
1288 | vote = UUID_ZERO; | |
1289 | leader = UUID_ZERO; | |
1290 | leader_rec_idx = 0; | |
1291 | } | |
1292 | if (term < min_term) { | |
1293 | min_term = term; | |
1294 | } | |
1295 | if (term > max_term) { | |
1296 | max_term = term; | |
1297 | } | |
1298 | ||
1b1d2e6d BP |
1299 | switch (r->type) { |
1300 | case RAFT_REC_ENTRY: | |
1301 | if (r->entry.index < commit_index) { | |
1302 | ovs_fatal(0, "%s: record %llu attempts to truncate log " | |
1303 | "from %"PRIu64" to %"PRIu64" entries, but " | |
1304 | "commit index is already %"PRIu64, | |
1305 | s->filename, rec_idx, | |
1306 | s->log_end, r->entry.index, | |
1307 | commit_index); | |
1308 | } else if (r->entry.index > s->log_end) { | |
1309 | ovs_fatal(0, "%s: record %llu with index %"PRIu64" skips " | |
1310 | "past expected index %"PRIu64, s->filename, | |
1311 | rec_idx, r->entry.index, s->log_end); | |
1312 | } | |
1313 | ||
1314 | if (r->entry.index < s->log_end) { | |
1315 | bool is_leader = uuid_equals(&s->header.sid, &leader); | |
1316 | if (is_leader) { | |
1317 | /* Leader Append-Only property (see Figure 3.2). */ | |
1318 | ovs_fatal(0, "%s: record %llu truncates log from " | |
1319 | "%"PRIu64" to %"PRIu64" entries while " | |
1320 | "server is leader", s->filename, rec_idx, | |
1321 | s->log_end, r->entry.index); | |
1322 | } else { | |
1323 | /* This can happen, but it is unusual. */ | |
1324 | printf("%s: record %llu truncates log from %"PRIu64 | |
1325 | " to %"PRIu64" entries\n", s->filename, rec_idx, | |
1326 | s->log_end, r->entry.index); | |
1327 | } | |
1328 | s->log_end = r->entry.index; | |
1329 | } | |
1330 | ||
1331 | uint64_t prev_term = (s->log_end > s->log_start | |
1332 | ? s->entries[s->log_end | |
1333 | - s->log_start - 1].term | |
1334 | : s->snap->term); | |
1335 | if (r->term < prev_term) { | |
1336 | ovs_fatal(0, "%s: record %llu with index %"PRIu64" term " | |
1337 | "%"PRIu64" precedes previous entry's term " | |
1338 | "%"PRIu64, s->filename, rec_idx, | |
1339 | r->entry.index, r->term, prev_term); | |
1340 | } | |
1341 | ||
1342 | uint64_t log_idx = s->log_end++ - s->log_start; | |
1343 | if (log_idx >= allocated_entries) { | |
1344 | s->entries = x2nrealloc(s->entries, &allocated_entries, | |
1345 | sizeof *s->entries); | |
1346 | } | |
1347 | struct raft_entry *e = &s->entries[log_idx]; | |
1348 | e->term = r->term; | |
1349 | e->data = r->entry.data; | |
1350 | e->eid = r->entry.eid; | |
1351 | e->servers = r->entry.servers; | |
1352 | break; | |
1353 | ||
1354 | case RAFT_REC_TERM: | |
1355 | break; | |
1356 | ||
1357 | case RAFT_REC_VOTE: | |
1358 | if (r->term < term) { | |
1359 | ovs_fatal(0, "%s: record %llu votes for term %"PRIu64" " | |
1360 | "but current term is %"PRIu64, s->filename, | |
1361 | rec_idx, r->term, term); | |
1362 | } else if (!uuid_is_zero(&vote) | |
1363 | && !uuid_equals(&vote, &r->sid)) { | |
1364 | char buf1[SID_LEN + 1]; | |
1365 | char buf2[SID_LEN + 1]; | |
1366 | ovs_fatal(0, "%s: record %llu votes for %s in term " | |
1367 | "%"PRIu64" but a previous record for the " | |
1368 | "same term voted for %s", s->filename, | |
1369 | rec_idx, | |
1370 | get_server_name(&c, &vote, buf1, sizeof buf1), | |
1371 | r->term, | |
1372 | get_server_name(&c, &r->sid, buf2, sizeof buf2)); | |
1373 | } else { | |
1374 | vote = r->sid; | |
1375 | } | |
1376 | break; | |
1377 | ||
1378 | case RAFT_REC_NOTE: | |
1379 | if (!strcmp(r->note, "left")) { | |
1380 | printf("%s: record %llu shows that the server left the " | |
1381 | "cluster\n", s->filename, rec_idx); | |
1382 | } | |
1383 | break; | |
1384 | ||
1385 | case RAFT_REC_COMMIT_INDEX: | |
1386 | if (r->commit_index < commit_index) { | |
1387 | ovs_fatal(0, "%s: record %llu regresses commit index " | |
1388 | "from %"PRIu64 " to %"PRIu64, s->filename, | |
1389 | rec_idx, commit_index, r->commit_index); | |
1390 | } else if (r->commit_index >= s->log_end) { | |
1391 | ovs_fatal(0, "%s: record %llu advances commit index to " | |
1392 | "%"PRIu64 " but last log index is %"PRIu64, | |
1393 | s->filename, rec_idx, r->commit_index, | |
1394 | s->log_end - 1); | |
1395 | } else { | |
1396 | commit_index = r->commit_index; | |
1ff1065c PI |
1397 | } |
1398 | ||
1b1d2e6d BP |
1399 | record_commit(&c, term, s, r->commit_index); |
1400 | break; | |
1401 | ||
1402 | case RAFT_REC_LEADER: | |
1403 | if (!uuid_equals(&r->sid, &leader)) { | |
1404 | if (uuid_is_zero(&leader)) { | |
1405 | leader = r->sid; | |
1406 | leader_rec_idx = rec_idx; | |
1407 | } else { | |
1408 | char buf1[SID_LEN + 1]; | |
1409 | char buf2[SID_LEN + 1]; | |
1410 | ovs_fatal(0, "%s: record %llu reports leader %s " | |
1411 | "for term %"PRIu64" but record %"PRIu64" " | |
1412 | "previously reported the leader as %s " | |
1413 | "in that term", | |
1414 | s->filename, rec_idx, | |
1415 | get_server_name(&c, &r->sid, | |
1416 | buf1, sizeof buf1), | |
1417 | term, leader_rec_idx, | |
1418 | get_server_name(&c, &leader, | |
1419 | buf2, sizeof buf2)); | |
1420 | } | |
1421 | } | |
1422 | record_leader(&c, term, s, &r->sid); | |
1423 | break; | |
722f6301 | 1424 | } |
1b1d2e6d BP |
1425 | } |
1426 | ||
6bf2e3f6 BP |
1427 | ovsdb_log_close(s->log); |
1428 | s->log = NULL; | |
1b1d2e6d | 1429 | } |
722f6301 | 1430 | |
1b1d2e6d BP |
1431 | /* Check the Leader Completeness property from Figure 3.2: If a log entry |
1432 | * is committed in a given term, then that entry will be present in the | |
1433 | * logs of the leaders for all higher-numbered terms. */ | |
1434 | if (min_term == UINT64_MAX || max_term == 0) { | |
1435 | ovs_fatal(0, "all logs are empty"); | |
1436 | } | |
1437 | struct commit *commit = NULL; | |
1438 | for (uint64_t term = min_term; term <= max_term; term++) { | |
1439 | struct leader *leader = find_leader(&c, term); | |
c2d71875 BP |
1440 | if (leader && leader->log_end |
1441 | && commit && commit->index >= leader->log_end) { | |
1b1d2e6d BP |
1442 | ovs_fatal(0, "leader %s for term %"PRIu64" has log entries only " |
1443 | "up to index %"PRIu64", but index %"PRIu64" was " | |
1444 | "committed in a previous term (e.g. by %s)", | |
1445 | leader->server->filename, term, leader->log_end - 1, | |
1446 | commit->index, commit->server->filename); | |
1447 | } | |
1448 | ||
1449 | struct commit *next = find_commit(&c, term); | |
1450 | if (next && (!commit || next->index > commit->index)) { | |
1451 | commit = next; | |
1452 | } | |
1453 | } | |
1454 | ||
1455 | /* Section 3.5: Check the Log Matching Property in Figure 3.2: | |
1456 | * | |
1457 | * - If two entries in different logs have the same index and term, then | |
1458 | * they store the same command. | |
1459 | * | |
1460 | * - If two entries in different logs have the same index and term, then | |
1461 | * the logs are identical in all preceding entries. | |
1462 | */ | |
1463 | for (size_t i = 0; i < c.n_servers; i++) { | |
1464 | for (size_t j = 0; j < c.n_servers; j++) { | |
1465 | struct server *a = &c.servers[i]; | |
1466 | struct server *b = &c.servers[j]; | |
1467 | ||
1468 | if (a == b) { | |
1469 | continue; | |
722f6301 | 1470 | } |
c6782bb0 | 1471 | |
1b1d2e6d BP |
1472 | bool must_equal = false; |
1473 | for (uint64_t idx = MIN(a->log_end, b->log_end) - 1; | |
1474 | idx >= MAX(a->log_start, b->log_start); | |
1475 | idx--) { | |
1476 | const struct raft_entry *ae = &a->entries[idx - a->log_start]; | |
1477 | const struct raft_entry *be = &b->entries[idx - b->log_start]; | |
1478 | if (ae->term == be->term) { | |
1479 | must_equal = true; | |
1480 | } | |
1481 | if (!must_equal || raft_entry_equals(ae, be)) { | |
1482 | continue; | |
1483 | } | |
1484 | char *as = json_to_string(raft_entry_to_json(ae), JSSF_SORT); | |
1485 | char *bs = json_to_string(raft_entry_to_json(be), JSSF_SORT); | |
1486 | ovs_fatal(0, "log entries with index %"PRIu64" differ:\n" | |
1487 | "%s has %s\n" | |
1488 | "%s has %s", | |
1489 | idx, a->filename, as, b->filename, bs); | |
c6782bb0 | 1490 | } |
722f6301 | 1491 | } |
722f6301 | 1492 | } |
c6782bb0 | 1493 | |
1b1d2e6d BP |
1494 | /* Clean up. */ |
1495 | ||
1496 | for (size_t i = 0; i < c.n_servers; i++) { | |
1497 | struct server *s = &c.servers[i]; | |
1498 | ||
1499 | raft_header_uninit(&s->header); | |
1500 | for (size_t j = 0; j < s->n_records; j++) { | |
1501 | struct raft_record *r = &s->records[j]; | |
1502 | ||
1503 | raft_record_uninit(r); | |
1504 | } | |
1505 | free(s->records); | |
1506 | free(s->entries); | |
1507 | } | |
1508 | free(c.servers); | |
1509 | ||
1510 | struct commit *next_commit; | |
1511 | HMAP_FOR_EACH_SAFE (commit, next_commit, hmap_node, &c.commits) { | |
1512 | hmap_remove(&c.commits, &commit->hmap_node); | |
1513 | free(commit); | |
1514 | } | |
1515 | hmap_destroy(&c.commits); | |
1516 | ||
1517 | struct leader *leader, *next_leader; | |
1518 | HMAP_FOR_EACH_SAFE (leader, next_leader, hmap_node, &c.leaders) { | |
1519 | hmap_remove(&c.leaders, &leader->hmap_node); | |
1520 | free(leader); | |
1521 | } | |
1522 | hmap_destroy(&c.leaders); | |
722f6301 BP |
1523 | } |
1524 | ||
1b1d2e6d BP |
1525 | static struct ovsdb_version |
1526 | parse_version(const char *s) | |
1527 | { | |
1528 | struct ovsdb_version version; | |
1529 | if (!ovsdb_parse_version(s, &version)) { | |
1530 | ovs_fatal(0, "%s: not an OVSDB version number in format x.y.z", s); | |
1531 | } | |
1532 | return version; | |
1533 | } | |
1534 | ||
1535 | static void | |
1536 | do_compare_versions(struct ovs_cmdl_context *ctx) | |
1537 | { | |
1538 | struct ovsdb_version a = parse_version(ctx->argv[1]); | |
1539 | struct ovsdb_version b = parse_version(ctx->argv[3]); | |
1540 | int cmp = (a.x != b.x ? (a.x > b.x ? 1 : -1) | |
1541 | : a.y != b.y ? (a.y > b.y ? 1 : -1) | |
1542 | : a.z != b.z ? (a.z > b.z ? 1 : -1) | |
1543 | : 0); | |
1544 | ||
1545 | const char *op = ctx->argv[2]; | |
1546 | bool result; | |
1547 | if (!strcmp(op, "<")) { | |
1548 | result = cmp < 0; | |
1549 | } else if (!strcmp(op, "<=")) { | |
1550 | result = cmp <= 0; | |
1551 | } else if (!strcmp(op, "==")) { | |
1552 | result = cmp == 0; | |
1553 | } else if (!strcmp(op, ">=")) { | |
1554 | result = cmp >= 0; | |
1555 | } else if (!strcmp(op, ">")) { | |
1556 | result = cmp > 0; | |
1557 | } else if (!strcmp(op, "!=")) { | |
1558 | result = cmp != 0; | |
1559 | } else { | |
1560 | ovs_fatal(0, "%s: not a relational operator", op); | |
1561 | } | |
1562 | ||
1563 | exit(result ? 0 : 2); | |
1564 | } | |
1565 | ||
00de46f9 AG |
1566 | static void |
1567 | do_convert_to_standalone(struct ovsdb_log *log, struct ovsdb_log *db_log_data) | |
1568 | { | |
1569 | for (unsigned int i = 0; ; i++) { | |
1570 | struct json *json; | |
1571 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
1572 | if (!json) { | |
1573 | break; | |
1574 | } | |
1575 | ||
1576 | if (i == 0) { | |
1577 | struct raft_header h; | |
1578 | check_ovsdb_error(raft_header_from_json(&h, json)); | |
1579 | raft_header_to_standalone_log(&h, db_log_data); | |
1580 | raft_header_uninit(&h); | |
1581 | } else { | |
1582 | struct raft_record r; | |
1583 | check_ovsdb_error(raft_record_from_json(&r, json)); | |
1584 | raft_record_to_standalone_log(&r, db_log_data); | |
1585 | raft_record_uninit(&r); | |
1586 | } | |
1587 | } | |
1588 | } | |
1589 | ||
1590 | static void | |
1591 | do_cluster_standalone(struct ovs_cmdl_context *ctx) | |
1592 | { | |
1593 | const char *db_file_name = ctx->argv[1]; | |
1594 | const char *cluster_db_file_name = ctx->argv[2]; | |
1595 | struct ovsdb_log *log; | |
1596 | struct ovsdb_log *db_log_data; | |
1597 | ||
1598 | check_ovsdb_error(ovsdb_log_open(cluster_db_file_name, | |
1599 | OVSDB_MAGIC"|"RAFT_MAGIC, | |
1600 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
1601 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC, | |
1602 | OVSDB_LOG_CREATE_EXCL, -1, &db_log_data)); | |
1603 | if (strcmp(ovsdb_log_get_magic(log), RAFT_MAGIC) != 0) { | |
1604 | ovs_fatal(0, "Database is not clustered db.\n"); | |
1605 | } | |
1606 | do_convert_to_standalone(log, db_log_data); | |
1607 | check_ovsdb_error(ovsdb_log_commit_block(db_log_data)); | |
1608 | ovsdb_log_close(db_log_data); | |
1609 | ovsdb_log_close(log); | |
1610 | } | |
1b1d2e6d | 1611 | |
f85f8ebb | 1612 | static void |
1636c761 | 1613 | do_help(struct ovs_cmdl_context *ctx OVS_UNUSED) |
f85f8ebb BP |
1614 | { |
1615 | usage(); | |
1616 | } | |
1617 | ||
451de37e | 1618 | static void |
1636c761 | 1619 | do_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED) |
451de37e | 1620 | { |
5f383751 | 1621 | ovs_cmdl_print_commands(get_all_commands()); |
451de37e AW |
1622 | } |
1623 | ||
5f383751 | 1624 | static const struct ovs_cmdl_command all_commands[] = { |
1f4a7252 | 1625 | { "create", "[db [schema]]", 0, 2, do_create, OVS_RW }, |
1b1d2e6d BP |
1626 | { "create-cluster", "db contents local", 3, 3, do_create_cluster, OVS_RW }, |
1627 | { "join-cluster", "db name local remote...", 4, INT_MAX, do_join_cluster, | |
1628 | OVS_RW }, | |
1f4a7252 RM |
1629 | { "compact", "[db [dst]]", 0, 2, do_compact, OVS_RW }, |
1630 | { "convert", "[db [schema [dst]]]", 0, 3, do_convert, OVS_RW }, | |
1631 | { "needs-conversion", NULL, 0, 2, do_needs_conversion, OVS_RO }, | |
439902cb | 1632 | { "db-name", "[db]", 0, 1, do_db_name, OVS_RO }, |
1f4a7252 RM |
1633 | { "db-version", "[db]", 0, 1, do_db_version, OVS_RO }, |
1634 | { "db-cksum", "[db]", 0, 1, do_db_cksum, OVS_RO }, | |
1b1d2e6d BP |
1635 | { "db-cid", "db", 1, 1, do_db_cid, OVS_RO }, |
1636 | { "db-sid", "db", 1, 1, do_db_sid, OVS_RO }, | |
1637 | { "db-local-address", "db", 1, 1, do_db_local_address, OVS_RO }, | |
1638 | { "db-is-clustered", "db", 1, 1, do_db_is_clustered, OVS_RO }, | |
1639 | { "db-is-standalone", "db", 1, 1, do_db_is_standalone, OVS_RO }, | |
439902cb | 1640 | { "schema-name", "[schema]", 0, 1, do_schema_name, OVS_RO }, |
1f4a7252 RM |
1641 | { "schema-version", "[schema]", 0, 1, do_schema_version, OVS_RO }, |
1642 | { "schema-cksum", "[schema]", 0, 1, do_schema_cksum, OVS_RO }, | |
1643 | { "query", "[db] trns", 1, 2, do_query, OVS_RO }, | |
1644 | { "transact", "[db] trns", 1, 2, do_transact, OVS_RO }, | |
1645 | { "show-log", "[db]", 0, 1, do_show_log, OVS_RO }, | |
1b1d2e6d BP |
1646 | { "check-cluster", "db...", 1, INT_MAX, do_check_cluster, OVS_RO }, |
1647 | { "compare-versions", "a op b", 3, 3, do_compare_versions, OVS_RO }, | |
1f4a7252 RM |
1648 | { "help", NULL, 0, INT_MAX, do_help, OVS_RO }, |
1649 | { "list-commands", NULL, 0, INT_MAX, do_list_commands, OVS_RO }, | |
00de46f9 AG |
1650 | { "cluster-to-standalone", "db clusterdb", 2, 2, |
1651 | do_cluster_standalone, OVS_RW }, | |
1652 | { NULL, NULL, 2, 2, NULL, OVS_RO }, | |
f85f8ebb | 1653 | }; |
3815d6c2 | 1654 | |
5f383751 | 1655 | static const struct ovs_cmdl_command *get_all_commands(void) |
3815d6c2 LS |
1656 | { |
1657 | return all_commands; | |
1658 | } |