]>
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)); | |
36e282f5 DS |
565 | int cmp = strcmp(ovsdb_log_get_magic(log), magic); |
566 | ovsdb_log_close(log); | |
567 | if (cmp) { | |
1b1d2e6d BP |
568 | exit(2); |
569 | } | |
570 | } | |
571 | ||
572 | static void | |
573 | do_db_is_clustered(struct ovs_cmdl_context *ctx) | |
574 | { | |
575 | do_db_has_magic(ctx, RAFT_MAGIC); | |
576 | } | |
577 | ||
578 | static void | |
579 | do_db_is_standalone(struct ovs_cmdl_context *ctx) | |
580 | { | |
581 | do_db_has_magic(ctx, OVSDB_MAGIC); | |
582 | } | |
583 | ||
584 | static void | |
585 | do_schema_name(struct ovs_cmdl_context *ctx) | |
8159b984 | 586 | { |
1636c761 | 587 | const char *schema_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); |
8159b984 BP |
588 | struct ovsdb_schema *schema; |
589 | ||
590 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 591 | puts(schema->name); |
8159b984 BP |
592 | ovsdb_schema_destroy(schema); |
593 | } | |
594 | ||
6aa09313 | 595 | static void |
1b1d2e6d | 596 | do_schema_version(struct ovs_cmdl_context *ctx) |
6aa09313 | 597 | { |
1636c761 | 598 | const char *schema_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); |
6aa09313 BP |
599 | struct ovsdb_schema *schema; |
600 | ||
601 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 602 | puts(schema->version); |
6aa09313 BP |
603 | ovsdb_schema_destroy(schema); |
604 | } | |
605 | ||
439902cb | 606 | static void |
1b1d2e6d | 607 | do_schema_cksum(struct ovs_cmdl_context *ctx) |
439902cb BP |
608 | { |
609 | const char *schema_file_name | |
610 | = ctx->argc >= 2 ? ctx->argv[1] : default_schema(); | |
611 | struct ovsdb_schema *schema; | |
612 | ||
613 | check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema)); | |
1b1d2e6d | 614 | puts(schema->cksum); |
439902cb BP |
615 | ovsdb_schema_destroy(schema); |
616 | } | |
617 | ||
1b1d2e6d | 618 | /* Standalone databases only. */ |
f85f8ebb | 619 | static void |
1b1d2e6d | 620 | transact(struct ovs_cmdl_context *ctx, bool rw) |
f85f8ebb | 621 | { |
1b1d2e6d BP |
622 | const char *db_file_name = ctx->argc >= 3 ? ctx->argv[1] : default_db(); |
623 | const char *transaction = ctx->argv[ctx->argc - 1]; | |
f85f8ebb | 624 | |
1b1d2e6d BP |
625 | struct ovsdb *ovsdb = ovsdb_file_read(db_file_name, rw); |
626 | struct json *request = parse_json(transaction); | |
627 | struct json *result = ovsdb_execute(ovsdb, NULL, request, false, | |
628 | rbac_role, NULL, 0, NULL); | |
f85f8ebb BP |
629 | json_destroy(request); |
630 | ||
631 | print_and_free_json(result); | |
1b1d2e6d | 632 | ovsdb_destroy(ovsdb); |
f85f8ebb BP |
633 | } |
634 | ||
635 | static void | |
1636c761 | 636 | do_query(struct ovs_cmdl_context *ctx) |
f85f8ebb | 637 | { |
1b1d2e6d | 638 | transact(ctx, false); |
f85f8ebb BP |
639 | } |
640 | ||
641 | static void | |
1636c761 | 642 | do_transact(struct ovs_cmdl_context *ctx) |
f85f8ebb | 643 | { |
1b1d2e6d | 644 | transact(ctx, true); |
f85f8ebb BP |
645 | } |
646 | ||
c6782bb0 | 647 | static void |
1b1d2e6d | 648 | print_db_changes(struct shash *tables, struct smap *names, |
4e92542c | 649 | const struct ovsdb_schema *schema) |
c6782bb0 BP |
650 | { |
651 | struct shash_node *n1; | |
652 | ||
1b1d2e6d | 653 | int i = 0; |
c6782bb0 BP |
654 | SHASH_FOR_EACH (n1, tables) { |
655 | const char *table = n1->name; | |
4e92542c | 656 | struct ovsdb_table_schema *table_schema; |
c6782bb0 BP |
657 | struct json *rows = n1->data; |
658 | struct shash_node *n2; | |
659 | ||
660 | if (n1->name[0] == '_' || rows->type != JSON_OBJECT) { | |
661 | continue; | |
662 | } | |
663 | ||
1b1d2e6d BP |
664 | if (i++ == 0) { |
665 | putchar('\n'); | |
666 | } | |
667 | ||
668 | table_schema = schema ? shash_find_data(&schema->tables, table) : NULL; | |
c6782bb0 BP |
669 | SHASH_FOR_EACH (n2, json_object(rows)) { |
670 | const char *row_uuid = n2->name; | |
671 | struct json *columns = n2->data; | |
672 | struct shash_node *n3; | |
c6782bb0 | 673 | |
1b1d2e6d BP |
674 | const char *old_name = smap_get(names, row_uuid); |
675 | char *new_name = NULL; | |
c6782bb0 BP |
676 | if (columns->type == JSON_OBJECT) { |
677 | struct json *new_name_json; | |
678 | ||
679 | new_name_json = shash_find_data(json_object(columns), "name"); | |
680 | if (new_name_json) { | |
681 | new_name = json_to_string(new_name_json, JSSF_SORT); | |
c6782bb0 BP |
682 | } |
683 | } | |
684 | ||
5a0e4aec | 685 | printf(" table %s", table); |
c6782bb0 BP |
686 | |
687 | if (!old_name) { | |
688 | if (new_name) { | |
a0a15a00 | 689 | printf(" insert row %s (%.8s):\n", new_name, row_uuid); |
c6782bb0 BP |
690 | } else { |
691 | printf(" insert row %.8s:\n", row_uuid); | |
692 | } | |
693 | } else { | |
a0a15a00 | 694 | printf(" row %s (%.8s):\n", old_name, row_uuid); |
c6782bb0 BP |
695 | } |
696 | ||
697 | if (columns->type == JSON_OBJECT) { | |
698 | if (show_log_verbosity > 1) { | |
699 | SHASH_FOR_EACH (n3, json_object(columns)) { | |
700 | const char *column = n3->name; | |
4e92542c | 701 | const struct ovsdb_column *column_schema; |
c6782bb0 | 702 | struct json *value = n3->data; |
4e92542c BP |
703 | char *value_string = NULL; |
704 | ||
705 | column_schema = | |
706 | (table_schema | |
707 | ? shash_find_data(&table_schema->columns, column) | |
708 | : NULL); | |
709 | if (column_schema) { | |
4e92542c | 710 | const struct ovsdb_type *type; |
a15fce8e | 711 | struct ovsdb_error *error; |
4e92542c BP |
712 | struct ovsdb_datum datum; |
713 | ||
714 | type = &column_schema->type; | |
715 | error = ovsdb_datum_from_json(&datum, type, | |
716 | value, NULL); | |
717 | if (!error) { | |
718 | struct ds s; | |
719 | ||
720 | ds_init(&s); | |
721 | ovsdb_datum_to_string(&datum, type, &s); | |
722 | value_string = ds_steal_cstr(&s); | |
a15fce8e BP |
723 | } else { |
724 | ovsdb_error_destroy(error); | |
4e92542c BP |
725 | } |
726 | } | |
727 | if (!value_string) { | |
728 | value_string = json_to_string(value, JSSF_SORT); | |
729 | } | |
5a0e4aec | 730 | printf(" %s=%s\n", column, value_string); |
c6782bb0 BP |
731 | free(value_string); |
732 | } | |
733 | } | |
1b1d2e6d BP |
734 | |
735 | if (new_name && (!old_name || strcmp(old_name, new_name))) { | |
736 | smap_replace_nocopy(names, row_uuid, new_name); | |
737 | new_name = NULL; | |
738 | } else if (!old_name) { | |
739 | smap_add_nocopy(names, xstrdup(row_uuid), | |
740 | xmemdup0(row_uuid, 8)); | |
c6782bb0 BP |
741 | } |
742 | } else if (columns->type == JSON_NULL) { | |
5a0e4aec | 743 | printf(" delete row\n"); |
1b1d2e6d | 744 | smap_remove(names, row_uuid); |
c6782bb0 BP |
745 | } |
746 | ||
1b1d2e6d | 747 | free(new_name); |
c6782bb0 BP |
748 | } |
749 | } | |
750 | } | |
751 | ||
722f6301 | 752 | static void |
1b1d2e6d BP |
753 | print_change_record(const struct json *json, const struct ovsdb_schema *schema, |
754 | struct smap *names) | |
722f6301 | 755 | { |
1b1d2e6d BP |
756 | if (!json || json->type != JSON_OBJECT) { |
757 | return; | |
758 | } | |
722f6301 | 759 | |
1b1d2e6d BP |
760 | struct json *date, *comment; |
761 | ||
762 | date = shash_find_data(json_object(json), "_date"); | |
763 | if (date && date->type == JSON_INTEGER) { | |
764 | long long int t = json_integer(date); | |
765 | char *s; | |
766 | ||
767 | if (t < INT32_MAX) { | |
768 | /* Older versions of ovsdb wrote timestamps in seconds. */ | |
769 | t *= 1000; | |
770 | } | |
771 | ||
772 | s = xastrftime_msec(" %Y-%m-%d %H:%M:%S.###", t, true); | |
773 | fputs(s, stdout); | |
774 | free(s); | |
775 | } | |
776 | ||
777 | comment = shash_find_data(json_object(json), "_comment"); | |
778 | if (comment && comment->type == JSON_STRING) { | |
779 | printf(" \"%s\"", json_string(comment)); | |
780 | } | |
781 | ||
782 | if (show_log_verbosity > 0) { | |
783 | print_db_changes(json_object(json), names, schema); | |
784 | } | |
785 | } | |
786 | ||
787 | static void | |
788 | do_show_log_standalone(struct ovsdb_log *log) | |
789 | { | |
790 | struct smap names = SMAP_INITIALIZER(&names); | |
791 | struct ovsdb_schema *schema = NULL; | |
792 | ||
793 | for (unsigned int i = 0; ; i++) { | |
722f6301 BP |
794 | struct json *json; |
795 | ||
796 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
797 | if (!json) { | |
798 | break; | |
799 | } | |
800 | ||
801 | printf("record %u:", i); | |
4e92542c BP |
802 | if (i == 0) { |
803 | check_ovsdb_error(ovsdb_schema_from_json(json, &schema)); | |
804 | printf(" \"%s\" schema, version=\"%s\", cksum=\"%s\"\n", | |
805 | schema->name, schema->version, schema->cksum); | |
1b1d2e6d BP |
806 | } else { |
807 | print_change_record(json, schema, &names); | |
808 | } | |
809 | json_destroy(json); | |
810 | putchar('\n'); | |
811 | } | |
812 | ||
813 | ovsdb_schema_destroy(schema); | |
814 | smap_destroy(&names); | |
815 | } | |
816 | ||
817 | static void | |
818 | print_servers(const char *name, const struct json *servers) | |
819 | { | |
820 | if (!servers) { | |
821 | return; | |
822 | } | |
722f6301 | 823 | |
1b1d2e6d | 824 | printf(" %s: ", name); |
1ff1065c | 825 | |
1b1d2e6d BP |
826 | const struct shash_node **nodes = shash_sort(json_object(servers)); |
827 | size_t n = shash_count(json_object(servers)); | |
828 | for (size_t i = 0; i < n; i++) { | |
829 | if (i > 0) { | |
830 | printf(", "); | |
831 | } | |
832 | ||
833 | const struct shash_node *node = nodes[i]; | |
834 | printf("%.4s(", node->name); | |
835 | ||
836 | const struct json *address = node->data; | |
837 | char *s = json_to_string(address, JSSF_SORT); | |
838 | fputs(s, stdout); | |
839 | free(s); | |
840 | ||
841 | putchar(')'); | |
842 | } | |
843 | free(nodes); | |
844 | putchar('\n'); | |
845 | } | |
846 | ||
847 | static void | |
848 | print_data(const char *prefix, const struct json *data, | |
849 | struct ovsdb_schema **schemap, struct smap *names) | |
850 | { | |
851 | if (!data) { | |
852 | return; | |
853 | } | |
854 | ||
855 | if (json_array(data)->n != 2) { | |
856 | printf(" ***invalid data***\n"); | |
857 | return; | |
858 | } | |
859 | ||
860 | const struct json *schema_json = json_array(data)->elems[0]; | |
861 | if (schema_json->type != JSON_NULL) { | |
862 | struct ovsdb_schema *schema; | |
863 | ||
864 | check_ovsdb_error(ovsdb_schema_from_json(schema_json, &schema)); | |
865 | printf(" %sschema: \"%s\", version=\"%s\", cksum=\"%s\"\n", | |
866 | prefix, schema->name, schema->version, schema->cksum); | |
867 | ||
868 | ovsdb_schema_destroy(*schemap); | |
869 | *schemap = schema; | |
870 | } | |
871 | ||
872 | print_change_record(json_array(data)->elems[1], *schemap, names); | |
873 | } | |
874 | ||
875 | static void | |
876 | print_raft_header(const struct raft_header *h, | |
877 | struct ovsdb_schema **schemap, struct smap *names) | |
878 | { | |
879 | printf(" name: \"%s\'\n", h->name); | |
880 | printf(" local address: \"%s\"\n", h->local_address); | |
881 | printf(" server_id: "SID_FMT"\n", SID_ARGS(&h->sid)); | |
882 | if (!uuid_is_zero(&h->cid)) { | |
883 | printf(" cluster_id: "CID_FMT"\n", CID_ARGS(&h->cid)); | |
884 | } | |
885 | if (!sset_is_empty(&h->remote_addresses)) { | |
886 | printf(" remote_addresses:"); | |
887 | ||
888 | const char *s; | |
889 | SSET_FOR_EACH (s, &h->remote_addresses) { | |
890 | printf(" %s", s); | |
891 | } | |
892 | putchar('\n'); | |
893 | } | |
894 | if (h->snap_index) { | |
895 | printf(" prev_index: %"PRIu64"\n", h->snap_index); | |
896 | printf(" prev_term: %"PRIu64"\n", h->snap.term); | |
897 | print_servers("prev_servers", h->snap.servers); | |
898 | if (!uuid_is_zero(&h->snap.eid)) { | |
899 | printf(" prev_eid: %04x\n", uuid_prefix(&h->snap.eid, 4)); | |
900 | } | |
901 | print_data("prev_", h->snap.data, schemap, names); | |
902 | } | |
903 | } | |
904 | ||
905 | static void | |
906 | print_raft_record(const struct raft_record *r, | |
907 | struct ovsdb_schema **schemap, struct smap *names) | |
908 | { | |
909 | if (r->comment) { | |
910 | printf(" comment: \"%s\"\n", r->comment); | |
911 | } | |
912 | if (r->term) { | |
913 | printf(" term: %"PRIu64"\n", r->term); | |
914 | } | |
915 | ||
916 | switch (r->type) { | |
917 | case RAFT_REC_ENTRY: | |
918 | printf(" index: %"PRIu64"\n", r->entry.index); | |
919 | print_servers("servers", r->entry.servers); | |
920 | if (!uuid_is_zero(&r->entry.eid)) { | |
921 | printf(" eid: %04x\n", uuid_prefix(&r->entry.eid, 4)); | |
922 | } | |
923 | print_data("", r->entry.data, schemap, names); | |
924 | break; | |
925 | ||
926 | case RAFT_REC_TERM: | |
927 | break; | |
928 | ||
929 | case RAFT_REC_VOTE: | |
930 | printf(" vote: "SID_FMT"\n", SID_ARGS(&r->sid)); | |
931 | break; | |
932 | ||
933 | case RAFT_REC_NOTE: | |
934 | printf(" note: \"%s\"\n", r->note); | |
935 | break; | |
936 | ||
937 | case RAFT_REC_COMMIT_INDEX: | |
938 | printf(" commit_index: %"PRIu64"\n", r->commit_index); | |
939 | break; | |
940 | ||
941 | case RAFT_REC_LEADER: | |
942 | printf(" leader: "SID_FMT"\n", SID_ARGS(&r->sid)); | |
943 | break; | |
944 | ||
945 | default: | |
946 | OVS_NOT_REACHED(); | |
947 | } | |
948 | } | |
949 | ||
00de46f9 AG |
950 | static void |
951 | raft_header_to_standalone_log(const struct raft_header *h, | |
952 | struct ovsdb_log *db_log_data) | |
953 | { | |
954 | if (h->snap_index) { | |
955 | if (!h->snap.data || json_array(h->snap.data)->n != 2) { | |
4cdc7b4d | 956 | ovs_fatal(0, "Incorrect raft header data array length"); |
00de46f9 AG |
957 | } |
958 | ||
4cdc7b4d DS |
959 | struct json_array *pa = json_array(h->snap.data); |
960 | struct json *schema_json = pa->elems[0]; | |
961 | struct ovsdb_error *error = NULL; | |
962 | ||
00de46f9 AG |
963 | if (schema_json->type != JSON_NULL) { |
964 | struct ovsdb_schema *schema; | |
965 | check_ovsdb_error(ovsdb_schema_from_json(schema_json, &schema)); | |
966 | ovsdb_schema_destroy(schema); | |
4cdc7b4d | 967 | error = ovsdb_log_write(db_log_data, schema_json); |
00de46f9 AG |
968 | } |
969 | ||
4cdc7b4d DS |
970 | if (!error) { |
971 | struct json *data_json = pa->elems[1]; | |
972 | if (!data_json || data_json->type != JSON_OBJECT) { | |
973 | ovs_fatal(0, "Invalid raft header data"); | |
974 | } | |
975 | if (data_json->type != JSON_NULL) { | |
976 | error = ovsdb_log_write(db_log_data, data_json); | |
977 | } | |
00de46f9 | 978 | } |
4cdc7b4d | 979 | check_ovsdb_error(error); |
00de46f9 AG |
980 | } |
981 | } | |
982 | ||
983 | static void | |
984 | raft_record_to_standalone_log(const struct raft_record *r, | |
985 | struct ovsdb_log *db_log_data) | |
986 | { | |
987 | if (r->type == RAFT_REC_ENTRY) { | |
988 | if (!r->entry.data) { | |
989 | return; | |
990 | } | |
4cdc7b4d DS |
991 | struct json_array *pa = json_array(r->entry.data); |
992 | ||
993 | if (pa->n != 2) { | |
00de46f9 AG |
994 | ovs_fatal(0, "Incorrect raft record array length"); |
995 | } | |
4cdc7b4d | 996 | struct json *data_json = pa->elems[1]; |
00de46f9 | 997 | if (data_json->type != JSON_NULL) { |
4cdc7b4d | 998 | check_ovsdb_error(ovsdb_log_write(db_log_data, data_json)); |
00de46f9 AG |
999 | } |
1000 | } | |
1001 | } | |
1002 | ||
1b1d2e6d BP |
1003 | static void |
1004 | do_show_log_cluster(struct ovsdb_log *log) | |
1005 | { | |
1006 | struct smap names = SMAP_INITIALIZER(&names); | |
1007 | struct ovsdb_schema *schema = NULL; | |
1008 | for (unsigned int i = 0; ; i++) { | |
1009 | struct json *json; | |
1010 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
1011 | if (!json) { | |
1012 | break; | |
1013 | } | |
1014 | ||
1015 | printf("record %u:\n", i); | |
1016 | struct ovsdb_error *error; | |
1017 | if (i == 0) { | |
1018 | struct raft_header h; | |
1019 | error = raft_header_from_json(&h, json); | |
1020 | if (!error) { | |
1021 | print_raft_header(&h, &schema, &names); | |
1022 | raft_header_uninit(&h); | |
1023 | } | |
1024 | } else { | |
1025 | struct raft_record r; | |
1026 | error = raft_record_from_json(&r, json); | |
1027 | if (!error) { | |
1028 | print_raft_record(&r, &schema, &names); | |
1029 | raft_record_uninit(&r); | |
1030 | } | |
1031 | } | |
1032 | if (error) { | |
1033 | char *s = ovsdb_error_to_string_free(error); | |
1034 | puts(s); | |
1035 | free(s); | |
1036 | } | |
1037 | ||
1038 | putchar('\n'); | |
1039 | } | |
1040 | ||
1041 | ovsdb_schema_destroy(schema); | |
1042 | smap_destroy(&names); | |
1043 | } | |
1044 | ||
1045 | static void | |
1046 | do_show_log(struct ovs_cmdl_context *ctx) | |
1047 | { | |
1048 | const char *db_file_name = ctx->argc >= 2 ? ctx->argv[1] : default_db(); | |
1049 | struct ovsdb_log *log; | |
1050 | ||
1051 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC"|"RAFT_MAGIC, | |
1052 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
1053 | if (!strcmp(ovsdb_log_get_magic(log), OVSDB_MAGIC)) { | |
1054 | do_show_log_standalone(log); | |
1055 | } else { | |
1056 | do_show_log_cluster(log); | |
1057 | } | |
1058 | ovsdb_log_close(log); | |
1059 | } | |
1060 | ||
1061 | struct server { | |
6bf2e3f6 | 1062 | struct ovsdb_log *log; |
1b1d2e6d BP |
1063 | const char *filename; |
1064 | const char *nickname; | |
1065 | ||
1066 | struct raft_header header; | |
1067 | ||
1068 | struct raft_record *records; | |
1069 | size_t n_records; | |
1070 | ||
1071 | struct raft_entry *snap; | |
1072 | struct raft_entry *entries; | |
1073 | uint64_t log_start, log_end; | |
1074 | }; | |
1075 | ||
1076 | struct leader { | |
1077 | /* In struct cluster's 'leaders', indexed by 'term'. */ | |
1078 | struct hmap_node hmap_node; | |
1079 | ||
1080 | /* This structure indicates that in 'term', 'server' reported that 'leader' | |
1081 | * was elected leader. When 'log_end' is nonzero, it additionally | |
1082 | * indicates 'leader''s log_end at the time it was elected. */ | |
1083 | uint64_t term; | |
1084 | struct server *server; | |
1085 | struct uuid leader; | |
1086 | uint64_t log_end; | |
1087 | }; | |
1088 | ||
1089 | struct commit { | |
1090 | /* In struct cluster's 'commits', indexed by 'term'. */ | |
1091 | struct hmap_node hmap_node; | |
1092 | ||
1093 | /* This structure indicates that in 'term', 'server' reported the commit | |
1094 | * index as 'index'. */ | |
1095 | uint64_t term; | |
1096 | struct server *server; | |
1097 | uint64_t index; /* Commit index. */ | |
1098 | }; | |
1099 | ||
1100 | struct cluster { | |
1101 | struct server *servers; | |
1102 | size_t n_servers; | |
1103 | ||
1104 | struct hmap leaders; /* Contains 'struct leader's. */ | |
1105 | ||
1106 | struct hmap commits; /* Contains 'struct commit's. */ | |
1107 | }; | |
1108 | ||
1109 | static const char * | |
1110 | get_server_name(const struct cluster *c, const struct uuid *sid, | |
1111 | char buf[SID_LEN + 1], size_t bufsize) | |
1112 | { | |
1113 | for (size_t i = 0; i < c->n_servers; i++) { | |
6233b87d | 1114 | struct server *s = &c->servers[i]; |
1b1d2e6d BP |
1115 | if (uuid_equals(&s->header.sid, sid)) { |
1116 | return s->filename; | |
1117 | } | |
1118 | } | |
1119 | ||
1120 | snprintf(buf, bufsize, SID_FMT, SID_ARGS(sid)); | |
1121 | return buf; | |
1122 | } | |
1123 | ||
1124 | static struct leader * | |
1125 | find_leader(struct cluster *c, uint64_t term) | |
1126 | { | |
1127 | struct leader *leader; | |
1128 | HMAP_FOR_EACH_WITH_HASH (leader, hmap_node, hash_uint64(term), | |
1129 | &c->leaders) { | |
1130 | if (term == leader->term) { | |
1131 | return leader; | |
1132 | } | |
1133 | } | |
1134 | return NULL; | |
1135 | } | |
1136 | ||
1137 | /* Records that 'server' reported that 'leader' was elected leader in 'term'. | |
1138 | * | |
1139 | * Checks the Election Safety Property: at most one leader may be elected in a | |
1140 | * single term (see Figure 3.2). */ | |
1141 | static void | |
1142 | record_leader(struct cluster *c, uint64_t term, struct server *server, | |
1143 | const struct uuid *leader) | |
1144 | { | |
1145 | bool server_is_leader = uuid_equals(&server->header.sid, leader); | |
1146 | struct leader *p = find_leader(c, term); | |
1147 | if (p) { | |
1148 | if (!uuid_equals(&p->leader, leader)) { | |
1149 | char buf1[SID_LEN + 1]; | |
1150 | char buf2[SID_LEN + 1]; | |
1151 | ovs_fatal(0, "term %"PRIu64" has two different leaders: " | |
1152 | "%s says that the leader is %s and " | |
1153 | "%s says that the leader is %s", | |
1154 | term, | |
1155 | p->server->filename, | |
1156 | get_server_name(c, &p->leader, buf1, sizeof buf1), | |
1157 | server->filename, | |
1158 | get_server_name(c, leader, buf2, sizeof buf2)); | |
1159 | } | |
1160 | if (server_is_leader && server->log_end > p->log_end) { | |
1161 | p->log_end = server->log_end; | |
1162 | } | |
1163 | } else { | |
1164 | p = xmalloc(sizeof *p); | |
1165 | hmap_insert(&c->leaders, &p->hmap_node, hash_uint64(term)); | |
1166 | p->term = term; | |
1167 | p->server = server; | |
1168 | p->leader = *leader; | |
1169 | if (server_is_leader) { | |
1170 | p->log_end = server->log_end; | |
1171 | } else { | |
1172 | p->log_end = 0; | |
1173 | } | |
1174 | } | |
1175 | } | |
1176 | ||
1177 | static struct commit * | |
1178 | find_commit(struct cluster *c, uint64_t term) | |
1179 | { | |
1180 | struct commit *commit; | |
1181 | HMAP_FOR_EACH_WITH_HASH (commit, hmap_node, hash_uint64(term), | |
1182 | &c->commits) { | |
1183 | if (term == commit->term) { | |
1184 | return commit; | |
1185 | } | |
1186 | } | |
1187 | return NULL; | |
1188 | } | |
1189 | ||
1190 | static void | |
1191 | record_commit(struct cluster *c, uint64_t term, struct server *server, | |
1192 | uint64_t commit_index) | |
1193 | { | |
1194 | struct commit *commit = find_commit(c, term); | |
1195 | if (commit) { | |
1196 | if (commit_index > commit->index) { | |
1197 | commit->server = server; | |
1198 | commit->index = commit_index; | |
1199 | } | |
1200 | } else { | |
1201 | commit = xmalloc(sizeof *commit); | |
1202 | hmap_insert(&c->commits, &commit->hmap_node, hash_uint64(term)); | |
1203 | commit->term = term; | |
1204 | commit->server = server; | |
1205 | commit->index = commit_index; | |
1206 | } | |
1207 | } | |
1208 | ||
1209 | static void | |
1210 | do_check_cluster(struct ovs_cmdl_context *ctx) | |
1211 | { | |
1212 | struct cluster c = { | |
1213 | .servers = xzalloc((ctx->argc - 1) * sizeof *c.servers), | |
1214 | .n_servers = 0, | |
1215 | .leaders = HMAP_INITIALIZER(&c.leaders), | |
1216 | .commits = HMAP_INITIALIZER(&c.commits), | |
1217 | }; | |
1218 | ||
1219 | uint64_t min_term = UINT64_MAX; | |
1220 | uint64_t max_term = 0; | |
1221 | ||
1222 | for (int i = 1; i < ctx->argc; i++) { | |
1223 | struct server *s = &c.servers[c.n_servers]; | |
1224 | s->filename = ctx->argv[i]; | |
1225 | ||
1b1d2e6d | 1226 | check_ovsdb_error(ovsdb_log_open(s->filename, RAFT_MAGIC, |
6bf2e3f6 | 1227 | OVSDB_LOG_READ_ONLY, -1, &s->log)); |
1b1d2e6d BP |
1228 | |
1229 | struct json *json; | |
6bf2e3f6 | 1230 | check_ovsdb_error(ovsdb_log_read(s->log, &json)); |
1b1d2e6d BP |
1231 | check_ovsdb_error(raft_header_from_json(&s->header, json)); |
1232 | json_destroy(json); | |
1233 | ||
1234 | if (s->header.joining) { | |
1235 | printf("%s has not joined the cluster, omitting\n", s->filename); | |
6bf2e3f6 | 1236 | ovsdb_log_close(s->log); |
1b1d2e6d BP |
1237 | continue; |
1238 | } | |
7073a83f BP |
1239 | for (size_t j = 0; j < c.n_servers; j++) { |
1240 | if (uuid_equals(&s->header.sid, &c.servers[j].header.sid)) { | |
1241 | ovs_fatal(0, "Duplicate server ID "SID_FMT" in %s and %s.", | |
1242 | SID_ARGS(&s->header.sid), | |
1243 | s->filename, c.servers[j].filename); | |
1244 | } | |
1245 | } | |
1b1d2e6d BP |
1246 | if (c.n_servers > 0) { |
1247 | struct server *s0 = &c.servers[0]; | |
1248 | if (!uuid_equals(&s0->header.cid, &s->header.cid)) { | |
1249 | ovs_fatal(0, "%s has cluster ID "CID_FMT" but %s " | |
1250 | "has cluster ID "CID_FMT, | |
1251 | s0->filename, CID_ARGS(&s0->header.cid), | |
1252 | s->filename, CID_ARGS(&s->header.cid)); | |
1253 | } | |
1254 | if (strcmp(s0->header.name, s->header.name)) { | |
1255 | ovs_fatal(0, "%s is named \"%s\" but %s is named \"%s\"", | |
1256 | s0->filename, s0->header.name, | |
1257 | s->filename, s->header.name); | |
1258 | } | |
1259 | } | |
6bf2e3f6 BP |
1260 | c.n_servers++; |
1261 | } | |
1262 | ||
1263 | for (struct server *s = c.servers; s < &c.servers[c.n_servers]; s++) { | |
1b1d2e6d BP |
1264 | s->snap = &s->header.snap; |
1265 | s->log_start = s->log_end = s->header.snap_index + 1; | |
1266 | ||
1267 | size_t allocated_records = 0; | |
1268 | size_t allocated_entries = 0; | |
1269 | ||
1270 | uint64_t term = 0; /* Current term. */ | |
1271 | struct uuid vote = UUID_ZERO; /* Server 's''s vote in 'term'. */ | |
1272 | struct uuid leader = UUID_ZERO; /* Cluster leader in 'term'. */ | |
1273 | uint64_t leader_rec_idx = 0; /* Index of last "leader" record. */ | |
1274 | ||
1275 | uint64_t commit_index = s->header.snap_index; | |
1276 | ||
1277 | for (unsigned long long int rec_idx = 1; ; rec_idx++) { | |
1278 | if (s->n_records >= allocated_records) { | |
1279 | s->records = x2nrealloc(s->records, &allocated_records, | |
1280 | sizeof *s->records); | |
1281 | } | |
6bf2e3f6 BP |
1282 | |
1283 | struct json *json; | |
1284 | check_ovsdb_error(ovsdb_log_read(s->log, &json)); | |
1b1d2e6d BP |
1285 | if (!json) { |
1286 | break; | |
1287 | } | |
1288 | struct raft_record *r = &s->records[s->n_records++]; | |
1289 | check_ovsdb_error(raft_record_from_json(r, json)); | |
1290 | json_destroy(json); | |
1291 | ||
1292 | if (r->term > term) { | |
1293 | term = r->term; | |
1294 | vote = UUID_ZERO; | |
1295 | leader = UUID_ZERO; | |
1296 | leader_rec_idx = 0; | |
1297 | } | |
1298 | if (term < min_term) { | |
1299 | min_term = term; | |
1300 | } | |
1301 | if (term > max_term) { | |
1302 | max_term = term; | |
1303 | } | |
1304 | ||
1b1d2e6d BP |
1305 | switch (r->type) { |
1306 | case RAFT_REC_ENTRY: | |
1307 | if (r->entry.index < commit_index) { | |
1308 | ovs_fatal(0, "%s: record %llu attempts to truncate log " | |
1309 | "from %"PRIu64" to %"PRIu64" entries, but " | |
1310 | "commit index is already %"PRIu64, | |
1311 | s->filename, rec_idx, | |
1312 | s->log_end, r->entry.index, | |
1313 | commit_index); | |
1314 | } else if (r->entry.index > s->log_end) { | |
1315 | ovs_fatal(0, "%s: record %llu with index %"PRIu64" skips " | |
1316 | "past expected index %"PRIu64, s->filename, | |
1317 | rec_idx, r->entry.index, s->log_end); | |
1318 | } | |
1319 | ||
1320 | if (r->entry.index < s->log_end) { | |
1321 | bool is_leader = uuid_equals(&s->header.sid, &leader); | |
1322 | if (is_leader) { | |
1323 | /* Leader Append-Only property (see Figure 3.2). */ | |
1324 | ovs_fatal(0, "%s: record %llu truncates log from " | |
1325 | "%"PRIu64" to %"PRIu64" entries while " | |
1326 | "server is leader", s->filename, rec_idx, | |
1327 | s->log_end, r->entry.index); | |
1328 | } else { | |
1329 | /* This can happen, but it is unusual. */ | |
1330 | printf("%s: record %llu truncates log from %"PRIu64 | |
1331 | " to %"PRIu64" entries\n", s->filename, rec_idx, | |
1332 | s->log_end, r->entry.index); | |
1333 | } | |
1334 | s->log_end = r->entry.index; | |
1335 | } | |
1336 | ||
1337 | uint64_t prev_term = (s->log_end > s->log_start | |
1338 | ? s->entries[s->log_end | |
1339 | - s->log_start - 1].term | |
1340 | : s->snap->term); | |
1341 | if (r->term < prev_term) { | |
1342 | ovs_fatal(0, "%s: record %llu with index %"PRIu64" term " | |
1343 | "%"PRIu64" precedes previous entry's term " | |
1344 | "%"PRIu64, s->filename, rec_idx, | |
1345 | r->entry.index, r->term, prev_term); | |
1346 | } | |
1347 | ||
1348 | uint64_t log_idx = s->log_end++ - s->log_start; | |
1349 | if (log_idx >= allocated_entries) { | |
1350 | s->entries = x2nrealloc(s->entries, &allocated_entries, | |
1351 | sizeof *s->entries); | |
1352 | } | |
1353 | struct raft_entry *e = &s->entries[log_idx]; | |
1354 | e->term = r->term; | |
1355 | e->data = r->entry.data; | |
1356 | e->eid = r->entry.eid; | |
1357 | e->servers = r->entry.servers; | |
1358 | break; | |
1359 | ||
1360 | case RAFT_REC_TERM: | |
1361 | break; | |
1362 | ||
1363 | case RAFT_REC_VOTE: | |
1364 | if (r->term < term) { | |
1365 | ovs_fatal(0, "%s: record %llu votes for term %"PRIu64" " | |
1366 | "but current term is %"PRIu64, s->filename, | |
1367 | rec_idx, r->term, term); | |
1368 | } else if (!uuid_is_zero(&vote) | |
1369 | && !uuid_equals(&vote, &r->sid)) { | |
1370 | char buf1[SID_LEN + 1]; | |
1371 | char buf2[SID_LEN + 1]; | |
1372 | ovs_fatal(0, "%s: record %llu votes for %s in term " | |
1373 | "%"PRIu64" but a previous record for the " | |
1374 | "same term voted for %s", s->filename, | |
1375 | rec_idx, | |
1376 | get_server_name(&c, &vote, buf1, sizeof buf1), | |
1377 | r->term, | |
1378 | get_server_name(&c, &r->sid, buf2, sizeof buf2)); | |
1379 | } else { | |
1380 | vote = r->sid; | |
1381 | } | |
1382 | break; | |
1383 | ||
1384 | case RAFT_REC_NOTE: | |
1385 | if (!strcmp(r->note, "left")) { | |
1386 | printf("%s: record %llu shows that the server left the " | |
1387 | "cluster\n", s->filename, rec_idx); | |
1388 | } | |
1389 | break; | |
1390 | ||
1391 | case RAFT_REC_COMMIT_INDEX: | |
1392 | if (r->commit_index < commit_index) { | |
1393 | ovs_fatal(0, "%s: record %llu regresses commit index " | |
1394 | "from %"PRIu64 " to %"PRIu64, s->filename, | |
1395 | rec_idx, commit_index, r->commit_index); | |
1396 | } else if (r->commit_index >= s->log_end) { | |
1397 | ovs_fatal(0, "%s: record %llu advances commit index to " | |
1398 | "%"PRIu64 " but last log index is %"PRIu64, | |
1399 | s->filename, rec_idx, r->commit_index, | |
1400 | s->log_end - 1); | |
1401 | } else { | |
1402 | commit_index = r->commit_index; | |
1ff1065c PI |
1403 | } |
1404 | ||
1b1d2e6d BP |
1405 | record_commit(&c, term, s, r->commit_index); |
1406 | break; | |
1407 | ||
1408 | case RAFT_REC_LEADER: | |
1409 | if (!uuid_equals(&r->sid, &leader)) { | |
1410 | if (uuid_is_zero(&leader)) { | |
1411 | leader = r->sid; | |
1412 | leader_rec_idx = rec_idx; | |
1413 | } else { | |
1414 | char buf1[SID_LEN + 1]; | |
1415 | char buf2[SID_LEN + 1]; | |
1416 | ovs_fatal(0, "%s: record %llu reports leader %s " | |
1417 | "for term %"PRIu64" but record %"PRIu64" " | |
1418 | "previously reported the leader as %s " | |
1419 | "in that term", | |
1420 | s->filename, rec_idx, | |
1421 | get_server_name(&c, &r->sid, | |
1422 | buf1, sizeof buf1), | |
1423 | term, leader_rec_idx, | |
1424 | get_server_name(&c, &leader, | |
1425 | buf2, sizeof buf2)); | |
1426 | } | |
1427 | } | |
1428 | record_leader(&c, term, s, &r->sid); | |
1429 | break; | |
722f6301 | 1430 | } |
1b1d2e6d BP |
1431 | } |
1432 | ||
6bf2e3f6 BP |
1433 | ovsdb_log_close(s->log); |
1434 | s->log = NULL; | |
1b1d2e6d | 1435 | } |
722f6301 | 1436 | |
1b1d2e6d BP |
1437 | /* Check the Leader Completeness property from Figure 3.2: If a log entry |
1438 | * is committed in a given term, then that entry will be present in the | |
1439 | * logs of the leaders for all higher-numbered terms. */ | |
1440 | if (min_term == UINT64_MAX || max_term == 0) { | |
1441 | ovs_fatal(0, "all logs are empty"); | |
1442 | } | |
1443 | struct commit *commit = NULL; | |
1444 | for (uint64_t term = min_term; term <= max_term; term++) { | |
1445 | struct leader *leader = find_leader(&c, term); | |
c2d71875 BP |
1446 | if (leader && leader->log_end |
1447 | && commit && commit->index >= leader->log_end) { | |
1b1d2e6d BP |
1448 | ovs_fatal(0, "leader %s for term %"PRIu64" has log entries only " |
1449 | "up to index %"PRIu64", but index %"PRIu64" was " | |
1450 | "committed in a previous term (e.g. by %s)", | |
1451 | leader->server->filename, term, leader->log_end - 1, | |
1452 | commit->index, commit->server->filename); | |
1453 | } | |
1454 | ||
1455 | struct commit *next = find_commit(&c, term); | |
1456 | if (next && (!commit || next->index > commit->index)) { | |
1457 | commit = next; | |
1458 | } | |
1459 | } | |
1460 | ||
1461 | /* Section 3.5: Check the Log Matching Property in Figure 3.2: | |
1462 | * | |
1463 | * - If two entries in different logs have the same index and term, then | |
1464 | * they store the same command. | |
1465 | * | |
1466 | * - If two entries in different logs have the same index and term, then | |
1467 | * the logs are identical in all preceding entries. | |
1468 | */ | |
1469 | for (size_t i = 0; i < c.n_servers; i++) { | |
1470 | for (size_t j = 0; j < c.n_servers; j++) { | |
1471 | struct server *a = &c.servers[i]; | |
1472 | struct server *b = &c.servers[j]; | |
1473 | ||
1474 | if (a == b) { | |
1475 | continue; | |
722f6301 | 1476 | } |
c6782bb0 | 1477 | |
1b1d2e6d BP |
1478 | bool must_equal = false; |
1479 | for (uint64_t idx = MIN(a->log_end, b->log_end) - 1; | |
1480 | idx >= MAX(a->log_start, b->log_start); | |
1481 | idx--) { | |
1482 | const struct raft_entry *ae = &a->entries[idx - a->log_start]; | |
1483 | const struct raft_entry *be = &b->entries[idx - b->log_start]; | |
1484 | if (ae->term == be->term) { | |
1485 | must_equal = true; | |
1486 | } | |
1487 | if (!must_equal || raft_entry_equals(ae, be)) { | |
1488 | continue; | |
1489 | } | |
1490 | char *as = json_to_string(raft_entry_to_json(ae), JSSF_SORT); | |
1491 | char *bs = json_to_string(raft_entry_to_json(be), JSSF_SORT); | |
1492 | ovs_fatal(0, "log entries with index %"PRIu64" differ:\n" | |
1493 | "%s has %s\n" | |
1494 | "%s has %s", | |
1495 | idx, a->filename, as, b->filename, bs); | |
c6782bb0 | 1496 | } |
722f6301 | 1497 | } |
722f6301 | 1498 | } |
c6782bb0 | 1499 | |
27dc7adf FP |
1500 | /* Check for db consistency: |
1501 | * The serverid must be in the servers list. | |
1502 | */ | |
1503 | ||
1504 | for (struct server *s = c.servers; s < &c.servers[c.n_servers]; s++) { | |
1505 | struct shash *servers_obj = json_object(s->snap->servers); | |
1506 | char *server_id = xasprintf(SID_FMT, SID_ARGS(&s->header.sid)); | |
1507 | bool found = false; | |
1508 | const struct shash_node *node; | |
1509 | ||
1510 | SHASH_FOR_EACH (node, servers_obj) { | |
1511 | if (!strncmp(server_id, node->name, SID_LEN)) { | |
1512 | found = true; | |
1513 | } | |
1514 | } | |
1515 | ||
1516 | if (!found) { | |
1517 | for (struct raft_entry *e = s->entries; | |
1518 | e < &s->entries[s->log_end - s->log_start]; e++) { | |
1519 | if (e->servers == NULL) { | |
1520 | continue; | |
1521 | } | |
1522 | struct shash *log_servers_obj = json_object(e->servers); | |
1523 | SHASH_FOR_EACH (node, log_servers_obj) { | |
1524 | if (!strncmp(server_id, node->name, SID_LEN)) { | |
1525 | found = true; | |
1526 | } | |
1527 | } | |
1528 | } | |
1529 | } | |
1530 | ||
1531 | if (!found) { | |
1532 | ovs_fatal(0, "%s: server %s not found in server list", | |
1533 | s->filename, server_id); | |
1534 | } | |
1535 | free(server_id); | |
1536 | } | |
1537 | ||
1b1d2e6d BP |
1538 | /* Clean up. */ |
1539 | ||
1540 | for (size_t i = 0; i < c.n_servers; i++) { | |
1541 | struct server *s = &c.servers[i]; | |
1542 | ||
1543 | raft_header_uninit(&s->header); | |
1544 | for (size_t j = 0; j < s->n_records; j++) { | |
1545 | struct raft_record *r = &s->records[j]; | |
1546 | ||
1547 | raft_record_uninit(r); | |
1548 | } | |
1549 | free(s->records); | |
1550 | free(s->entries); | |
1551 | } | |
1552 | free(c.servers); | |
1553 | ||
1554 | struct commit *next_commit; | |
1555 | HMAP_FOR_EACH_SAFE (commit, next_commit, hmap_node, &c.commits) { | |
1556 | hmap_remove(&c.commits, &commit->hmap_node); | |
1557 | free(commit); | |
1558 | } | |
1559 | hmap_destroy(&c.commits); | |
1560 | ||
1561 | struct leader *leader, *next_leader; | |
1562 | HMAP_FOR_EACH_SAFE (leader, next_leader, hmap_node, &c.leaders) { | |
1563 | hmap_remove(&c.leaders, &leader->hmap_node); | |
1564 | free(leader); | |
1565 | } | |
1566 | hmap_destroy(&c.leaders); | |
722f6301 BP |
1567 | } |
1568 | ||
1b1d2e6d BP |
1569 | static struct ovsdb_version |
1570 | parse_version(const char *s) | |
1571 | { | |
1572 | struct ovsdb_version version; | |
1573 | if (!ovsdb_parse_version(s, &version)) { | |
1574 | ovs_fatal(0, "%s: not an OVSDB version number in format x.y.z", s); | |
1575 | } | |
1576 | return version; | |
1577 | } | |
1578 | ||
1579 | static void | |
1580 | do_compare_versions(struct ovs_cmdl_context *ctx) | |
1581 | { | |
1582 | struct ovsdb_version a = parse_version(ctx->argv[1]); | |
1583 | struct ovsdb_version b = parse_version(ctx->argv[3]); | |
1584 | int cmp = (a.x != b.x ? (a.x > b.x ? 1 : -1) | |
1585 | : a.y != b.y ? (a.y > b.y ? 1 : -1) | |
1586 | : a.z != b.z ? (a.z > b.z ? 1 : -1) | |
1587 | : 0); | |
1588 | ||
1589 | const char *op = ctx->argv[2]; | |
1590 | bool result; | |
1591 | if (!strcmp(op, "<")) { | |
1592 | result = cmp < 0; | |
1593 | } else if (!strcmp(op, "<=")) { | |
1594 | result = cmp <= 0; | |
1595 | } else if (!strcmp(op, "==")) { | |
1596 | result = cmp == 0; | |
1597 | } else if (!strcmp(op, ">=")) { | |
1598 | result = cmp >= 0; | |
1599 | } else if (!strcmp(op, ">")) { | |
1600 | result = cmp > 0; | |
1601 | } else if (!strcmp(op, "!=")) { | |
1602 | result = cmp != 0; | |
1603 | } else { | |
1604 | ovs_fatal(0, "%s: not a relational operator", op); | |
1605 | } | |
1606 | ||
1607 | exit(result ? 0 : 2); | |
1608 | } | |
1609 | ||
00de46f9 AG |
1610 | static void |
1611 | do_convert_to_standalone(struct ovsdb_log *log, struct ovsdb_log *db_log_data) | |
1612 | { | |
1613 | for (unsigned int i = 0; ; i++) { | |
1614 | struct json *json; | |
1615 | check_ovsdb_error(ovsdb_log_read(log, &json)); | |
1616 | if (!json) { | |
1617 | break; | |
1618 | } | |
1619 | ||
1620 | if (i == 0) { | |
1621 | struct raft_header h; | |
1622 | check_ovsdb_error(raft_header_from_json(&h, json)); | |
1623 | raft_header_to_standalone_log(&h, db_log_data); | |
1624 | raft_header_uninit(&h); | |
1625 | } else { | |
1626 | struct raft_record r; | |
1627 | check_ovsdb_error(raft_record_from_json(&r, json)); | |
1628 | raft_record_to_standalone_log(&r, db_log_data); | |
1629 | raft_record_uninit(&r); | |
1630 | } | |
4cdc7b4d | 1631 | json_destroy(json); |
00de46f9 AG |
1632 | } |
1633 | } | |
1634 | ||
1635 | static void | |
1636 | do_cluster_standalone(struct ovs_cmdl_context *ctx) | |
1637 | { | |
1638 | const char *db_file_name = ctx->argv[1]; | |
1639 | const char *cluster_db_file_name = ctx->argv[2]; | |
1640 | struct ovsdb_log *log; | |
1641 | struct ovsdb_log *db_log_data; | |
1642 | ||
1643 | check_ovsdb_error(ovsdb_log_open(cluster_db_file_name, | |
1644 | OVSDB_MAGIC"|"RAFT_MAGIC, | |
1645 | OVSDB_LOG_READ_ONLY, -1, &log)); | |
1646 | check_ovsdb_error(ovsdb_log_open(db_file_name, OVSDB_MAGIC, | |
1647 | OVSDB_LOG_CREATE_EXCL, -1, &db_log_data)); | |
1648 | if (strcmp(ovsdb_log_get_magic(log), RAFT_MAGIC) != 0) { | |
1649 | ovs_fatal(0, "Database is not clustered db.\n"); | |
1650 | } | |
1651 | do_convert_to_standalone(log, db_log_data); | |
1652 | check_ovsdb_error(ovsdb_log_commit_block(db_log_data)); | |
1653 | ovsdb_log_close(db_log_data); | |
1654 | ovsdb_log_close(log); | |
1655 | } | |
1b1d2e6d | 1656 | |
f85f8ebb | 1657 | static void |
1636c761 | 1658 | do_help(struct ovs_cmdl_context *ctx OVS_UNUSED) |
f85f8ebb BP |
1659 | { |
1660 | usage(); | |
1661 | } | |
1662 | ||
451de37e | 1663 | static void |
1636c761 | 1664 | do_list_commands(struct ovs_cmdl_context *ctx OVS_UNUSED) |
451de37e | 1665 | { |
5f383751 | 1666 | ovs_cmdl_print_commands(get_all_commands()); |
451de37e AW |
1667 | } |
1668 | ||
5f383751 | 1669 | static const struct ovs_cmdl_command all_commands[] = { |
1f4a7252 | 1670 | { "create", "[db [schema]]", 0, 2, do_create, OVS_RW }, |
1b1d2e6d BP |
1671 | { "create-cluster", "db contents local", 3, 3, do_create_cluster, OVS_RW }, |
1672 | { "join-cluster", "db name local remote...", 4, INT_MAX, do_join_cluster, | |
1673 | OVS_RW }, | |
1f4a7252 RM |
1674 | { "compact", "[db [dst]]", 0, 2, do_compact, OVS_RW }, |
1675 | { "convert", "[db [schema [dst]]]", 0, 3, do_convert, OVS_RW }, | |
1676 | { "needs-conversion", NULL, 0, 2, do_needs_conversion, OVS_RO }, | |
439902cb | 1677 | { "db-name", "[db]", 0, 1, do_db_name, OVS_RO }, |
1f4a7252 RM |
1678 | { "db-version", "[db]", 0, 1, do_db_version, OVS_RO }, |
1679 | { "db-cksum", "[db]", 0, 1, do_db_cksum, OVS_RO }, | |
1b1d2e6d BP |
1680 | { "db-cid", "db", 1, 1, do_db_cid, OVS_RO }, |
1681 | { "db-sid", "db", 1, 1, do_db_sid, OVS_RO }, | |
1682 | { "db-local-address", "db", 1, 1, do_db_local_address, OVS_RO }, | |
1683 | { "db-is-clustered", "db", 1, 1, do_db_is_clustered, OVS_RO }, | |
1684 | { "db-is-standalone", "db", 1, 1, do_db_is_standalone, OVS_RO }, | |
439902cb | 1685 | { "schema-name", "[schema]", 0, 1, do_schema_name, OVS_RO }, |
1f4a7252 RM |
1686 | { "schema-version", "[schema]", 0, 1, do_schema_version, OVS_RO }, |
1687 | { "schema-cksum", "[schema]", 0, 1, do_schema_cksum, OVS_RO }, | |
1688 | { "query", "[db] trns", 1, 2, do_query, OVS_RO }, | |
1689 | { "transact", "[db] trns", 1, 2, do_transact, OVS_RO }, | |
1690 | { "show-log", "[db]", 0, 1, do_show_log, OVS_RO }, | |
1b1d2e6d BP |
1691 | { "check-cluster", "db...", 1, INT_MAX, do_check_cluster, OVS_RO }, |
1692 | { "compare-versions", "a op b", 3, 3, do_compare_versions, OVS_RO }, | |
1f4a7252 RM |
1693 | { "help", NULL, 0, INT_MAX, do_help, OVS_RO }, |
1694 | { "list-commands", NULL, 0, INT_MAX, do_list_commands, OVS_RO }, | |
00de46f9 AG |
1695 | { "cluster-to-standalone", "db clusterdb", 2, 2, |
1696 | do_cluster_standalone, OVS_RW }, | |
1697 | { NULL, NULL, 2, 2, NULL, OVS_RO }, | |
f85f8ebb | 1698 | }; |
3815d6c2 | 1699 | |
5f383751 | 1700 | static const struct ovs_cmdl_command *get_all_commands(void) |
3815d6c2 LS |
1701 | { |
1702 | return all_commands; | |
1703 | } |