mirror of
https://github.com/openvswitch/ovs
synced 2025-08-22 01:51:26 +00:00
Internal implementation of JSON array will be changed in the future commits. Add access functions that users can rely on instead of accessing the internals of 'struct json' directly and convert all the users. Structure fields are intentionally renamed to make sure that no code is using the old fields directly. json_array() function is removed, as not needed anymore. Added new functions: json_array_size(), json_array_at(), json_array_set() and json_array_pop(). These are enough to cover all the use cases within OVS. The change is fairly large, however, IMO, it's a much overdue cleanup that we need even without changing the underlying implementation. Acked-by: Mike Pattrick <mkp@redhat.com> Signed-off-by: Ilya Maximets <i.maximets@ovn.org>
2547 lines
82 KiB
C
2547 lines
82 KiB
C
/*
|
||
* Copyright (c) 2009-2017 Nicira, Inc.
|
||
*
|
||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||
* you may not use this file except in compliance with the License.
|
||
* You may obtain a copy of the License at:
|
||
*
|
||
* http://www.apache.org/licenses/LICENSE-2.0
|
||
*
|
||
* Unless required by applicable law or agreed to in writing, software
|
||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
* See the License for the specific language governing permissions and
|
||
* limitations under the License.
|
||
*/
|
||
|
||
#include <config.h>
|
||
|
||
#include <ctype.h>
|
||
#include <errno.h>
|
||
#include <fcntl.h>
|
||
#include <getopt.h>
|
||
#include <limits.h>
|
||
#include <signal.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <unistd.h>
|
||
|
||
#include "command-line.h"
|
||
#include "column.h"
|
||
#include "compiler.h"
|
||
#include "daemon.h"
|
||
#include "dirs.h"
|
||
#include "openvswitch/dynamic-string.h"
|
||
#include "fatal-signal.h"
|
||
#include "file.h"
|
||
#include "openvswitch/json.h"
|
||
#include "jsonrpc.h"
|
||
#include "lib/table.h"
|
||
#include "log.h"
|
||
#include "ovs-replay.h"
|
||
#include "ovsdb.h"
|
||
#include "ovsdb-data.h"
|
||
#include "ovsdb-error.h"
|
||
#include "ovsdb-session.h"
|
||
#include "openvswitch/poll-loop.h"
|
||
#include "row.h"
|
||
#include "sort.h"
|
||
#include "svec.h"
|
||
#include "storage.h"
|
||
#include "stream.h"
|
||
#include "stream-ssl.h"
|
||
#include "table.h"
|
||
#include "transaction.h"
|
||
#include "monitor.h"
|
||
#include "condition.h"
|
||
#include "timeval.h"
|
||
#include "unixctl.h"
|
||
#include "util.h"
|
||
#include "openvswitch/vlog.h"
|
||
|
||
VLOG_DEFINE_THIS_MODULE(ovsdb_client);
|
||
|
||
enum args_needed {
|
||
NEED_NONE, /* No JSON-RPC connection or database name needed. */
|
||
NEED_RPC, /* JSON-RPC connection needed. */
|
||
NEED_DATABASE /* JSON-RPC connection and database name needed. */
|
||
};
|
||
|
||
struct ovsdb_client_command {
|
||
const char *name;
|
||
enum args_needed need;
|
||
int min_args;
|
||
int max_args;
|
||
void (*handler)(struct jsonrpc *rpc, const char *database,
|
||
int argc, char *argv[]);
|
||
};
|
||
|
||
/* --timestamp: Print a timestamp before each update on "monitor" command? */
|
||
static bool timestamp;
|
||
|
||
/* --db-change-aware, --no-db-change-aware: Enable db_change_aware feature for
|
||
* "monitor" command?
|
||
*
|
||
* -1 (default): Use db_change_aware if available.
|
||
* 0: Disable db_change_aware.
|
||
* 1: Require db_change_aware.
|
||
*
|
||
* (This option is undocumented because anything other than the default is
|
||
* expected to be useful only for testing that the db_change_aware feature
|
||
* actually works.) */
|
||
static int db_change_aware = -1;
|
||
|
||
/* --force: Ignore schema differences for "restore" command? */
|
||
static bool force;
|
||
|
||
/* --leader-only, --no-leader-only: Only accept the leader in a cluster. */
|
||
static bool leader_only = true;
|
||
|
||
/* Format for table output. */
|
||
static struct table_style table_style = TABLE_STYLE_DEFAULT;
|
||
|
||
static const struct ovsdb_client_command *get_all_commands(void);
|
||
|
||
static struct json *parse_json(const char *);
|
||
|
||
OVS_NO_RETURN static void usage(void);
|
||
static void parse_options(int argc, char *argv[]);
|
||
static struct jsonrpc *open_jsonrpc(const char *server);
|
||
static void fetch_dbs(struct jsonrpc *, struct svec *dbs);
|
||
static bool should_stay_connected(const char *server, const char *database,
|
||
const struct uuid *cid,
|
||
const struct jsonrpc_msg *reply);
|
||
struct jsonrpc_msg *create_database_info_request(const char *database);
|
||
|
||
static char *
|
||
default_remote(void)
|
||
{
|
||
return xasprintf("unix:%s/db.sock", ovs_rundir());
|
||
}
|
||
|
||
static int
|
||
open_rpc(int min_args, enum args_needed need,
|
||
int argc, char *argv[], struct jsonrpc **rpcp, char **databasep)
|
||
{
|
||
struct svec remotes = SVEC_EMPTY_INITIALIZER;
|
||
struct uuid cid = UUID_ZERO;
|
||
|
||
/* First figure out the remote(s). If the first command-line argument has
|
||
* the form of a remote, use it, otherwise use the default. */
|
||
int argidx = 0;
|
||
if (argc > min_args && (isalpha((unsigned char) argv[0][0])
|
||
&& strchr(argv[0], ':'))) {
|
||
ovsdb_session_parse_remote(argv[argidx++], &remotes, &cid);
|
||
} else {
|
||
svec_add_nocopy(&remotes, default_remote());
|
||
}
|
||
|
||
/* Handle the case where there's one remote. In this case, if we need a
|
||
* database name, we try to figure out a default if none was specified
|
||
* explicitly. */
|
||
char *database = *databasep;
|
||
if (remotes.n == 1) {
|
||
struct jsonrpc *rpc = open_jsonrpc(remotes.names[0]);
|
||
svec_destroy(&remotes);
|
||
|
||
if (need == NEED_DATABASE && !database) {
|
||
struct svec dbs;
|
||
|
||
svec_init(&dbs);
|
||
fetch_dbs(rpc, &dbs);
|
||
if (argc - argidx > min_args
|
||
&& svec_contains(&dbs, argv[argidx])) {
|
||
database = xstrdup(argv[argidx++]);
|
||
} else if (svec_contains(&dbs, "Open_vSwitch")) {
|
||
database = xstrdup("Open_vSwitch");
|
||
} else {
|
||
size_t n = 0;
|
||
const char *best = NULL;
|
||
for (size_t i = 0; i < dbs.n; i++) {
|
||
if (dbs.names[i][0] != '_') {
|
||
best = dbs.names[i];
|
||
n++;
|
||
}
|
||
}
|
||
if (n != 1) {
|
||
jsonrpc_close(rpc);
|
||
ovs_fatal(0, "could not find a default database, "
|
||
"please specify a database name");
|
||
}
|
||
database = xstrdup(best);
|
||
}
|
||
svec_destroy(&dbs);
|
||
}
|
||
*rpcp = rpc;
|
||
*databasep = database;
|
||
|
||
return argidx;
|
||
}
|
||
|
||
/* If there's more than one remote, and we need a database name, then it
|
||
* must be specified explicitly. It's too likely to cause surprising
|
||
* behavior if we try to pick a default across several servers. */
|
||
if (!database && need == NEED_DATABASE) {
|
||
if (argc - argidx > min_args) {
|
||
database = xstrdup(argv[argidx++]);
|
||
} else {
|
||
ovs_fatal(0, "database name is required with multiple remotes");
|
||
}
|
||
}
|
||
|
||
/* We have multiple remotes. Connect to them in a random order and choose
|
||
* the first one that is up and hosts the database we want (if any) in an
|
||
* acceptable state. */
|
||
struct jsonrpc_session *js = jsonrpc_session_open_multiple(
|
||
&remotes, false);
|
||
svec_destroy(&remotes);
|
||
|
||
unsigned int seqno = 0;
|
||
struct json *id = NULL;
|
||
for (;;) {
|
||
jsonrpc_session_run(js);
|
||
if (!jsonrpc_session_is_alive(js)) {
|
||
ovs_fatal(0, "no servers were available");
|
||
}
|
||
|
||
if (seqno != jsonrpc_session_get_seqno(js)
|
||
&& jsonrpc_session_is_connected(js)) {
|
||
if (!database) {
|
||
break;
|
||
}
|
||
|
||
seqno = jsonrpc_session_get_seqno(js);
|
||
struct jsonrpc_msg *txn = create_database_info_request(database);
|
||
json_destroy(id);
|
||
id = json_clone(txn->id);
|
||
jsonrpc_session_send(js, txn);
|
||
}
|
||
|
||
struct jsonrpc_msg *reply = jsonrpc_session_recv(js);
|
||
if (reply && id && reply->id && json_equal(id, reply->id)) {
|
||
if (reply->type == JSONRPC_REPLY
|
||
&& should_stay_connected(jsonrpc_session_get_name(js),
|
||
database, &cid, reply)) {
|
||
jsonrpc_msg_destroy(reply);
|
||
break;
|
||
}
|
||
jsonrpc_session_force_reconnect(js);
|
||
}
|
||
jsonrpc_msg_destroy(reply);
|
||
|
||
jsonrpc_session_recv_wait(js);
|
||
jsonrpc_session_wait(js);
|
||
poll_block();
|
||
}
|
||
json_destroy(id);
|
||
|
||
*rpcp = jsonrpc_session_steal(js);
|
||
*databasep = database;
|
||
return argidx;
|
||
}
|
||
|
||
int
|
||
main(int argc, char *argv[])
|
||
{
|
||
const struct ovsdb_client_command *command;
|
||
ovs_cmdl_proctitle_init(argc, argv);
|
||
set_program_name(argv[0]);
|
||
service_start(&argc, &argv);
|
||
parse_options(argc, argv);
|
||
fatal_ignore_sigpipe();
|
||
|
||
daemon_become_new_user(false, false);
|
||
if (optind >= argc) {
|
||
ovs_fatal(0, "missing command name; use --help for help");
|
||
}
|
||
|
||
for (command = get_all_commands(); ; command++) {
|
||
if (!command->name) {
|
||
VLOG_FATAL("unknown command '%s'; use --help for help",
|
||
argv[optind]);
|
||
} else if (!strcmp(command->name, argv[optind])) {
|
||
break;
|
||
}
|
||
}
|
||
optind++;
|
||
|
||
char *database = NULL;
|
||
struct jsonrpc *rpc = NULL;
|
||
if (command->need != NEED_NONE) {
|
||
optind += open_rpc(command->min_args, command->need,
|
||
argc - optind, argv + optind, &rpc, &database);
|
||
}
|
||
|
||
|
||
if (argc - optind < command->min_args ||
|
||
argc - optind > command->max_args) {
|
||
free(database);
|
||
VLOG_FATAL("invalid syntax for '%s' (use --help for help)",
|
||
command->name);
|
||
}
|
||
|
||
command->handler(rpc, database, argc - optind, argv + optind);
|
||
|
||
free(database);
|
||
jsonrpc_close(rpc);
|
||
|
||
if (ferror(stdout)) {
|
||
VLOG_FATAL("write to stdout failed");
|
||
}
|
||
if (ferror(stderr)) {
|
||
VLOG_FATAL("write to stderr failed");
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
parse_options(int argc, char *argv[])
|
||
{
|
||
enum {
|
||
OPT_BOOTSTRAP_CA_CERT = UCHAR_MAX + 1,
|
||
OPT_TIMESTAMP,
|
||
OPT_FORCE,
|
||
OPT_LEADER_ONLY,
|
||
OPT_NO_LEADER_ONLY,
|
||
VLOG_OPTION_ENUMS,
|
||
DAEMON_OPTION_ENUMS,
|
||
TABLE_OPTION_ENUMS,
|
||
SSL_OPTION_ENUMS,
|
||
OVS_REPLAY_OPTION_ENUMS,
|
||
};
|
||
static const struct option long_options[] = {
|
||
{"help", no_argument, NULL, 'h'},
|
||
{"version", no_argument, NULL, 'V'},
|
||
{"timestamp", no_argument, NULL, OPT_TIMESTAMP},
|
||
{"force", no_argument, NULL, OPT_FORCE},
|
||
{"timeout", required_argument, NULL, 't'},
|
||
{"db-change-aware", no_argument, &db_change_aware, 1},
|
||
{"no-db-change-aware", no_argument, &db_change_aware, 0},
|
||
{"leader-only", no_argument, NULL, OPT_LEADER_ONLY},
|
||
{"no-leader-only", no_argument, NULL, OPT_NO_LEADER_ONLY},
|
||
VLOG_LONG_OPTIONS,
|
||
DAEMON_LONG_OPTIONS,
|
||
#ifdef HAVE_OPENSSL
|
||
{"bootstrap-ca-cert", required_argument, NULL, OPT_BOOTSTRAP_CA_CERT},
|
||
STREAM_SSL_LONG_OPTIONS,
|
||
#endif
|
||
TABLE_LONG_OPTIONS,
|
||
OVS_REPLAY_LONG_OPTIONS,
|
||
{NULL, 0, NULL, 0},
|
||
};
|
||
char *short_options = ovs_cmdl_long_options_to_short_options(long_options);
|
||
unsigned int timeout = 0;
|
||
|
||
table_style.format = TF_TABLE;
|
||
|
||
for (;;) {
|
||
int c;
|
||
|
||
c = getopt_long(argc, argv, short_options, long_options, NULL);
|
||
if (c == -1) {
|
||
break;
|
||
}
|
||
|
||
switch (c) {
|
||
case 'h':
|
||
usage();
|
||
|
||
case 'V':
|
||
ovs_print_version(0, 0);
|
||
exit(EXIT_SUCCESS);
|
||
|
||
VLOG_OPTION_HANDLERS
|
||
DAEMON_OPTION_HANDLERS
|
||
TABLE_OPTION_HANDLERS(&table_style)
|
||
STREAM_SSL_OPTION_HANDLERS
|
||
OVS_REPLAY_OPTION_HANDLERS
|
||
|
||
case OPT_BOOTSTRAP_CA_CERT:
|
||
stream_ssl_set_ca_cert_file(optarg, true);
|
||
break;
|
||
|
||
case OPT_TIMESTAMP:
|
||
timestamp = true;
|
||
break;
|
||
|
||
case OPT_FORCE:
|
||
force = true;
|
||
break;
|
||
|
||
case 't':
|
||
if (!str_to_uint(optarg, 10, &timeout) || !timeout) {
|
||
ovs_fatal(0, "value %s on -t or --timeout is invalid", optarg);
|
||
}
|
||
break;
|
||
|
||
case OPT_LEADER_ONLY:
|
||
leader_only = true;
|
||
break;
|
||
|
||
case OPT_NO_LEADER_ONLY:
|
||
leader_only = false;
|
||
break;
|
||
|
||
case '?':
|
||
exit(EXIT_FAILURE);
|
||
|
||
case 0:
|
||
/* getopt_long() already set the value for us. */
|
||
break;
|
||
|
||
default:
|
||
abort();
|
||
}
|
||
}
|
||
free(short_options);
|
||
|
||
ctl_timeout_setup(timeout);
|
||
}
|
||
|
||
static void
|
||
usage(void)
|
||
{
|
||
printf("%s: Open vSwitch database JSON-RPC client\n"
|
||
"usage: %s [OPTIONS] COMMAND [ARG...]\n"
|
||
"\nValid commands are:\n"
|
||
"\n list-dbs [SERVER]\n"
|
||
" list databases available on SERVER\n"
|
||
"\n get-schema [SERVER] [DATABASE]\n"
|
||
" retrieve schema for DATABASE from SERVER\n"
|
||
"\n get-schema-version [SERVER] [DATABASE]\n"
|
||
" retrieve schema for DATABASE from SERVER and report only its\n"
|
||
" version number on stdout\n"
|
||
"\n get-schema-cksum [SERVER] [DATABASE]\n"
|
||
" retrieve schema for DATABASE from SERVER and report only its\n"
|
||
" checksum on stdout\n"
|
||
"\n list-tables [SERVER] [DATABASE]\n"
|
||
" list tables for DATABASE on SERVER\n"
|
||
"\n list-columns [SERVER] [DATABASE] [TABLE]\n"
|
||
" list columns in TABLE (or all tables) in DATABASE on SERVER\n"
|
||
"\n transact [SERVER] TRANSACTION\n"
|
||
" run TRANSACTION (params for \"transact\" request) on SERVER\n"
|
||
" and print the results as JSON on stdout\n"
|
||
"\n query [SERVER] TRANSACTION\n"
|
||
" run TRANSACTION (params for \"transact\" request) on SERVER,\n"
|
||
" as read-only, and print the results as JSON on stdout\n"
|
||
"\n monitor [SERVER] [DATABASE] TABLE [COLUMN,...]...\n"
|
||
" monitor contents of COLUMNs in TABLE in DATABASE on SERVER.\n"
|
||
" COLUMNs may include !initial, !insert, !delete, !modify\n"
|
||
" to avoid seeing the specified kinds of changes.\n"
|
||
"\n monitor-cond [SERVER] [DATABASE] CONDITION TABLE [COLUMN,...]...\n"
|
||
" monitor contents that match CONDITION of COLUMNs in TABLE in\n"
|
||
" DATABASE on SERVER.\n"
|
||
" COLUMNs may include !initial, !insert, !delete, !modify\n"
|
||
" to avoid seeing the specified kinds of changes.\n"
|
||
"\n monitor-cond-since [SERVER] [DATABASE] [LASTID] CONDITION TABLE [COLUMN,...]...\n"
|
||
" monitor contents that match CONDITION of COLUMNs in TABLE in\n"
|
||
" DATABASE on SERVER, since change after LASTID.\n"
|
||
" LASTID specifies transaction ID after which the monitoring\n"
|
||
" starts, which works only for cluster mode. If ignored, it\n"
|
||
" defaults to an all-zero uuid.\n"
|
||
" Other arguments are the same as in monitor-cond.\n"
|
||
"\n convert [SERVER] SCHEMA\n"
|
||
" convert database on SERVER named in SCHEMA to SCHEMA.\n"
|
||
"\n needs-conversion [SERVER] SCHEMA\n"
|
||
" tests whether SCHEMA's db on SERVER needs conversion.\n"
|
||
"\n monitor [SERVER] [DATABASE] ALL\n"
|
||
" monitor all changes to all columns in all tables\n"
|
||
"\n wait [SERVER] DATABASE STATE\n"
|
||
" wait until DATABASE reaches STATE "
|
||
"(\"added\" or \"connected\" or \"removed\")\n"
|
||
" in DATBASE on SERVER.\n"
|
||
"\n dump [SERVER] [DATABASE] [TABLE [COLUMN]...]\n"
|
||
" dump contents of COLUMNs, TABLE (or all tables) in DATABASE\n"
|
||
" on SERVER to stdout\n"
|
||
"\n backup [SERVER] [DATABASE] > SNAPSHOT\n"
|
||
" dump database contents in the form of a database file\n"
|
||
"\n [--force] restore [SERVER] [DATABASE] < SNAPSHOT\n"
|
||
" restore database contents from a database file\n"
|
||
"\n lock [SERVER] LOCK\n"
|
||
" create or wait for LOCK in SERVER\n"
|
||
"\n steal [SERVER] LOCK\n"
|
||
" steal LOCK from SERVER\n"
|
||
"\n unlock [SERVER] LOCK\n"
|
||
" unlock LOCK from SERVER\n"
|
||
"\nThe default SERVER is unix:%s/db.sock.\n"
|
||
"The default DATABASE is Open_vSwitch.\n",
|
||
program_name, program_name, ovs_rundir());
|
||
stream_usage("SERVER", true, true, true);
|
||
table_usage();
|
||
printf(" --timestamp timestamp \"monitor\" output");
|
||
daemon_usage();
|
||
vlog_usage();
|
||
ovs_replay_usage();
|
||
printf("\nOther options:\n"
|
||
" -t, --timeout=SECS limits ovsdb-client runtime to\n"
|
||
" approximately SECS seconds.\n"
|
||
" -h, --help display this help message\n"
|
||
" -V, --version display version information\n");
|
||
exit(EXIT_SUCCESS);
|
||
}
|
||
|
||
static void
|
||
check_txn(int error, struct jsonrpc_msg **reply_)
|
||
{
|
||
struct jsonrpc_msg *reply = *reply_;
|
||
|
||
if (error) {
|
||
ovs_fatal(error, "transaction failed");
|
||
}
|
||
|
||
if (reply->error) {
|
||
ovs_fatal(error, "transaction returned error: %s",
|
||
json_to_string(reply->error, table_style.json_flags));
|
||
}
|
||
}
|
||
|
||
static struct json *
|
||
parse_json(const char *s)
|
||
{
|
||
struct json *json = json_from_string(s);
|
||
if (json->type == JSON_STRING) {
|
||
ovs_fatal(0, "\"%s\": %s", s, json_string(json));
|
||
}
|
||
return json;
|
||
}
|
||
|
||
static struct jsonrpc *
|
||
open_jsonrpc(const char *server)
|
||
{
|
||
struct stream *stream;
|
||
int error;
|
||
|
||
error = stream_open_block(jsonrpc_stream_open(server, &stream,
|
||
DSCP_DEFAULT), -1, &stream);
|
||
if (error == EAFNOSUPPORT) {
|
||
struct pstream *pstream;
|
||
|
||
error = jsonrpc_pstream_open(server, &pstream, DSCP_DEFAULT);
|
||
if (error) {
|
||
ovs_fatal(error, "failed to connect or listen to \"%s\"", server);
|
||
}
|
||
|
||
VLOG_INFO("%s: waiting for connection...", server);
|
||
error = pstream_accept_block(pstream, &stream);
|
||
if (error) {
|
||
ovs_fatal(error, "failed to accept connection on \"%s\"", server);
|
||
}
|
||
|
||
pstream_close(pstream);
|
||
} else if (error) {
|
||
ovs_fatal(error, "failed to connect to \"%s\"", server);
|
||
}
|
||
|
||
return jsonrpc_open(stream);
|
||
}
|
||
|
||
static void
|
||
print_json(struct json *json)
|
||
{
|
||
char *string = json_to_string(json, table_style.json_flags);
|
||
puts(string);
|
||
free(string);
|
||
}
|
||
|
||
static void
|
||
print_and_free_json(struct json *json)
|
||
{
|
||
print_json(json);
|
||
json_destroy(json);
|
||
}
|
||
|
||
static void
|
||
check_ovsdb_error(struct ovsdb_error *error)
|
||
{
|
||
if (error) {
|
||
ovs_fatal(0, "%s", ovsdb_error_to_string_free(error));
|
||
}
|
||
}
|
||
|
||
static struct ovsdb_schema *
|
||
fetch_schema(struct jsonrpc *rpc, const char *database)
|
||
{
|
||
struct jsonrpc_msg *request, *reply;
|
||
struct ovsdb_schema *schema;
|
||
|
||
request = jsonrpc_create_request("get_schema",
|
||
json_array_create_1(
|
||
json_string_create(database)),
|
||
NULL);
|
||
check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);
|
||
check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema));
|
||
jsonrpc_msg_destroy(reply);
|
||
|
||
return schema;
|
||
}
|
||
|
||
static void
|
||
fetch_dbs(struct jsonrpc *rpc, struct svec *dbs)
|
||
{
|
||
struct jsonrpc_msg *request, *reply;
|
||
size_t i, n;
|
||
|
||
request = jsonrpc_create_request("list_dbs", json_array_create_empty(),
|
||
NULL);
|
||
|
||
check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);
|
||
if (reply->result->type != JSON_ARRAY) {
|
||
ovs_fatal(0, "list_dbs response is not array");
|
||
}
|
||
|
||
n = json_array_size(reply->result);
|
||
for (i = 0; i < n; i++) {
|
||
const struct json *name = json_array_at(reply->result, i);
|
||
|
||
if (name->type != JSON_STRING) {
|
||
ovs_fatal(0, "list_dbs response %"PRIuSIZE" is not string", i);
|
||
}
|
||
svec_add(dbs, json_string(name));
|
||
}
|
||
jsonrpc_msg_destroy(reply);
|
||
svec_sort(dbs);
|
||
}
|
||
|
||
static const char *
|
||
parse_string_column(const struct json *row, const char *column_name)
|
||
{
|
||
const struct json *column = shash_find_data(json_object(row), column_name);
|
||
return column && column->type == JSON_STRING ? json_string(column) : "";
|
||
}
|
||
|
||
static int
|
||
parse_boolean_column(const struct json *row, const char *column_name)
|
||
{
|
||
const struct json *column = shash_find_data(json_object(row), column_name);
|
||
return (!column ? -1
|
||
: column->type == JSON_TRUE ? true
|
||
: column->type == JSON_FALSE ? false
|
||
: -1);
|
||
}
|
||
|
||
static struct uuid
|
||
parse_uuid_column(const struct json *row, const char *column_name)
|
||
{
|
||
const struct json *column = shash_find_data(json_object(row), column_name);
|
||
if (!column) {
|
||
return UUID_ZERO;
|
||
}
|
||
|
||
struct ovsdb_type type = { OVSDB_BASE_UUID_INIT, OVSDB_BASE_VOID_INIT,
|
||
0, 1 };
|
||
struct ovsdb_datum datum;
|
||
struct ovsdb_error *error = ovsdb_datum_from_json(&datum, &type, column,
|
||
NULL);
|
||
if (error) {
|
||
ovsdb_error_destroy(error);
|
||
return UUID_ZERO;
|
||
}
|
||
struct uuid uuid = datum.n > 0 ? datum.keys[0].uuid : UUID_ZERO;
|
||
ovsdb_datum_destroy(&datum, &type);
|
||
return uuid;
|
||
}
|
||
|
||
struct jsonrpc_msg *
|
||
create_database_info_request(const char *database)
|
||
{
|
||
struct json *op = json_object_create();
|
||
json_object_put_string(op, "op", "select");
|
||
json_object_put_string(op, "table", "Database");
|
||
struct json *condition = json_array_create_3(
|
||
json_string_create("name"),
|
||
json_string_create("=="),
|
||
json_string_create(database));
|
||
json_object_put(op, "where", json_array_create_1(condition));
|
||
struct json *txn = json_array_create_2(
|
||
json_string_create("_Server"), op);
|
||
return jsonrpc_create_request("transact", txn, NULL);
|
||
}
|
||
|
||
static const struct json *
|
||
parse_database_info_reply(const struct jsonrpc_msg *reply, const char *server,
|
||
const char *database, const struct uuid *cid)
|
||
{
|
||
const struct json *result = reply->result;
|
||
if (result->type != JSON_ARRAY
|
||
|| json_array_size(result) != 1
|
||
|| json_array_at(result, 0)->type != JSON_OBJECT) {
|
||
VLOG_WARN("%s: unexpected reply to _Server request for %s",
|
||
server, database);
|
||
return NULL;
|
||
}
|
||
|
||
const struct json *op_result = json_array_at(result, 0);
|
||
const struct json *rows = shash_find_data(json_object(op_result), "rows");
|
||
if (!rows || rows->type != JSON_ARRAY) {
|
||
VLOG_WARN("%s: missing \"rows\" member in _Server reply for %s",
|
||
server, database);
|
||
return NULL;
|
||
}
|
||
|
||
size_t n = json_array_size(rows);
|
||
for (size_t i = 0; i < n; i++) {
|
||
const struct json *row = json_array_at(rows, i);
|
||
if (row->type != JSON_OBJECT) {
|
||
VLOG_WARN("%s: bad row in _Server reply for %s",
|
||
server, database);
|
||
continue;
|
||
}
|
||
|
||
if (strcmp(parse_string_column(row, "name"), database)) {
|
||
continue;
|
||
}
|
||
|
||
if (cid && !uuid_is_zero(cid)) {
|
||
struct uuid cid2 = parse_uuid_column(row, "cid");
|
||
if (!uuid_equals(cid, &cid2)) {
|
||
continue;
|
||
}
|
||
}
|
||
|
||
return row;
|
||
}
|
||
|
||
/* No such database. */
|
||
return NULL;
|
||
}
|
||
|
||
/* Parses 'reply', a JSON-RPC reply to our request asking for the status of
|
||
* 'database' on 'server'. Determines whether this server is acceptable for
|
||
* the transaction we want to make and returns true if so or false to
|
||
* disconnect and try a different server. */
|
||
static bool
|
||
should_stay_connected(const char *server, const char *database,
|
||
const struct uuid *cid, const struct jsonrpc_msg *reply)
|
||
{
|
||
const struct json *row = parse_database_info_reply(reply, server,
|
||
database, cid);
|
||
if (!row) {
|
||
/* No such database. */
|
||
return false;
|
||
}
|
||
|
||
if (!strcmp(parse_string_column(row, "model"), "standalone")) {
|
||
/* Always accept standalone databases. */
|
||
return true;
|
||
}
|
||
|
||
if (!parse_boolean_column(row, "connected")) {
|
||
/* Reject disconnected servers. */
|
||
return false;
|
||
}
|
||
|
||
if (leader_only && !parse_boolean_column(row, "leader")) {
|
||
/* Reject if not leader.. */
|
||
return false;
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
static void
|
||
do_list_dbs(struct jsonrpc *rpc, const char *database OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
const char *db_name;
|
||
struct svec dbs;
|
||
size_t i;
|
||
|
||
svec_init(&dbs);
|
||
fetch_dbs(rpc, &dbs);
|
||
SVEC_FOR_EACH (i, db_name, &dbs) {
|
||
puts(db_name);
|
||
}
|
||
svec_destroy(&dbs);
|
||
}
|
||
|
||
static void
|
||
do_get_schema(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
struct ovsdb_schema *schema = fetch_schema(rpc, database);
|
||
print_and_free_json(ovsdb_schema_to_json(schema));
|
||
ovsdb_schema_destroy(schema);
|
||
}
|
||
|
||
static void
|
||
do_get_schema_version(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
struct ovsdb_schema *schema = fetch_schema(rpc, database);
|
||
puts(schema->version);
|
||
ovsdb_schema_destroy(schema);
|
||
}
|
||
|
||
static void
|
||
do_get_schema_cksum(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
struct ovsdb_schema *schema = fetch_schema(rpc, database);
|
||
puts(schema->cksum);
|
||
ovsdb_schema_destroy(schema);
|
||
}
|
||
|
||
static void
|
||
do_list_tables(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
struct ovsdb_schema *schema;
|
||
struct shash_node *node;
|
||
struct table t;
|
||
|
||
schema = fetch_schema(rpc, database);
|
||
table_init(&t);
|
||
table_add_column(&t, "Table");
|
||
SHASH_FOR_EACH (node, &schema->tables) {
|
||
struct ovsdb_table_schema *ts = node->data;
|
||
|
||
table_add_row(&t);
|
||
table_add_cell(&t)->text = xstrdup(ts->name);
|
||
}
|
||
ovsdb_schema_destroy(schema);
|
||
table_print(&t, &table_style);
|
||
table_destroy(&t);
|
||
}
|
||
|
||
static void
|
||
do_list_columns(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[])
|
||
{
|
||
const char *table_name = argv[0];
|
||
struct ovsdb_schema *schema;
|
||
struct shash_node *table_node;
|
||
struct table t;
|
||
|
||
schema = fetch_schema(rpc, database);
|
||
table_init(&t);
|
||
if (!table_name) {
|
||
table_add_column(&t, "Table");
|
||
}
|
||
table_add_column(&t, "Column");
|
||
table_add_column(&t, "Type");
|
||
SHASH_FOR_EACH (table_node, &schema->tables) {
|
||
struct ovsdb_table_schema *ts = table_node->data;
|
||
|
||
if (!table_name || !strcmp(table_name, ts->name)) {
|
||
struct shash_node *column_node;
|
||
|
||
SHASH_FOR_EACH (column_node, &ts->columns) {
|
||
const struct ovsdb_column *column = column_node->data;
|
||
|
||
table_add_row(&t);
|
||
if (!table_name) {
|
||
table_add_cell(&t)->text = xstrdup(ts->name);
|
||
}
|
||
table_add_cell(&t)->text = xstrdup(column->name);
|
||
table_add_cell(&t)->json = ovsdb_type_to_json(&column->type);
|
||
}
|
||
}
|
||
}
|
||
ovsdb_schema_destroy(schema);
|
||
table_print(&t, &table_style);
|
||
table_destroy(&t);
|
||
}
|
||
|
||
static void
|
||
send_db_change_aware(struct jsonrpc *rpc)
|
||
{
|
||
if (db_change_aware != 0) {
|
||
struct jsonrpc_msg *request = jsonrpc_create_request(
|
||
"set_db_change_aware",
|
||
json_array_create_1(json_boolean_create(true)),
|
||
NULL);
|
||
struct jsonrpc_msg *reply;
|
||
int error = jsonrpc_transact_block(rpc, request, &reply);
|
||
if (error) {
|
||
ovs_fatal(error, "%s: error setting db_change_aware",
|
||
jsonrpc_get_name(rpc));
|
||
}
|
||
if (reply->type == JSONRPC_ERROR && db_change_aware == 1) {
|
||
ovs_fatal(0, "%s: set_db_change_aware failed (%s)",
|
||
jsonrpc_get_name(rpc), json_to_string(reply->error, 0));
|
||
}
|
||
jsonrpc_msg_destroy(reply);
|
||
}
|
||
}
|
||
|
||
static struct json *
|
||
do_transact__(int argc, char *argv[], struct json *transaction)
|
||
{
|
||
struct jsonrpc_msg *request, *reply;
|
||
if (transaction->type != JSON_ARRAY
|
||
|| !json_array_size(transaction)
|
||
|| json_array_at(transaction, 0)->type != JSON_STRING) {
|
||
ovs_fatal(0, "not a valid OVSDB query");
|
||
}
|
||
const char *db_name = json_string(json_array_at(transaction, 0));
|
||
|
||
struct jsonrpc *rpc;
|
||
char *database = CONST_CAST(char *, db_name);
|
||
open_rpc(1, NEED_DATABASE, argc, argv, &rpc, &database);
|
||
|
||
if (db_change_aware == 1) {
|
||
send_db_change_aware(rpc);
|
||
}
|
||
daemon_save_fd(STDOUT_FILENO);
|
||
daemon_save_fd(STDERR_FILENO);
|
||
daemonize();
|
||
|
||
request = jsonrpc_create_request("transact", transaction, NULL);
|
||
check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);
|
||
struct json *result = json_clone(reply->result);
|
||
jsonrpc_msg_destroy(reply);
|
||
jsonrpc_close(rpc);
|
||
|
||
return result;
|
||
}
|
||
|
||
static void
|
||
do_transact(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,
|
||
int argc, char *argv[])
|
||
{
|
||
print_and_free_json(do_transact__(argc, argv, parse_json(argv[argc - 1])));
|
||
}
|
||
|
||
static void
|
||
do_query(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,
|
||
int argc, char *argv[])
|
||
{
|
||
struct json *transaction = parse_json(argv[argc - 1]);
|
||
|
||
if (transaction->type != JSON_ARRAY) {
|
||
ovs_fatal(0, "not a valid OVSDB query");
|
||
}
|
||
|
||
/* Append an "abort" operation to the query. */
|
||
struct json *abort_op = json_object_create();
|
||
json_object_put_string(abort_op, "op", "abort");
|
||
json_array_add(transaction, abort_op);
|
||
size_t abort_idx = json_array_size(transaction) - 2;
|
||
|
||
/* Run query. */
|
||
struct json *result = do_transact__(argc, argv, transaction);
|
||
|
||
/* If the "abort" operation ended the transaction, remove its result. */
|
||
if (result->type == JSON_ARRAY
|
||
&& json_array_size(result) == abort_idx + 1
|
||
&& json_array_at(result, abort_idx)->type == JSON_OBJECT) {
|
||
const struct json *op_result = json_array_at(result, abort_idx);
|
||
struct json *error = shash_find_data(json_object(op_result), "error");
|
||
if (error
|
||
&& error->type == JSON_STRING
|
||
&& !strcmp(json_string(error), "aborted")) {
|
||
json_destroy(json_array_pop(result));
|
||
}
|
||
}
|
||
|
||
/* Print the result. */
|
||
print_and_free_json(result);
|
||
}
|
||
|
||
/* "monitor" command. */
|
||
|
||
struct monitored_table {
|
||
struct ovsdb_table_schema *table;
|
||
struct ovsdb_column_set columns;
|
||
};
|
||
|
||
static void
|
||
monitor_print_row(const struct json *row, const char *type, const char *uuid,
|
||
const struct ovsdb_column_set *columns, struct table *t)
|
||
{
|
||
size_t i;
|
||
|
||
if (!row) {
|
||
ovs_error(0, "missing %s row", type);
|
||
return;
|
||
} else if (row->type != JSON_OBJECT) {
|
||
ovs_error(0, "<row> is not object");
|
||
return;
|
||
}
|
||
|
||
table_add_row(t);
|
||
table_add_cell(t)->text = xstrdup(uuid);
|
||
table_add_cell(t)->text = xstrdup(type);
|
||
for (i = 0; i < columns->n_columns; i++) {
|
||
const struct ovsdb_column *column = columns->columns[i];
|
||
struct json *value = shash_find_data(json_object(row), column->name);
|
||
struct cell *cell = table_add_cell(t);
|
||
if (value) {
|
||
cell->json = json_clone(value);
|
||
cell->type = &column->type;
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
monitor_print_table(struct json *table_update,
|
||
const struct monitored_table *mt, char *caption,
|
||
bool initial)
|
||
{
|
||
const struct ovsdb_table_schema *table = mt->table;
|
||
const struct ovsdb_column_set *columns = &mt->columns;
|
||
struct shash_node *node;
|
||
struct table t;
|
||
size_t i;
|
||
|
||
if (table_update->type != JSON_OBJECT) {
|
||
ovs_error(0, "<table-update> for table %s is not object", table->name);
|
||
return;
|
||
}
|
||
|
||
table_init(&t);
|
||
table_set_timestamp(&t, timestamp);
|
||
table_set_caption(&t, caption);
|
||
|
||
table_add_column(&t, "row");
|
||
table_add_column(&t, "action");
|
||
for (i = 0; i < columns->n_columns; i++) {
|
||
table_add_column(&t, "%s", columns->columns[i]->name);
|
||
}
|
||
SHASH_FOR_EACH (node, json_object(table_update)) {
|
||
struct json *row_update = node->data;
|
||
struct json *old, *new;
|
||
|
||
if (row_update->type != JSON_OBJECT) {
|
||
ovs_error(0, "<row-update> is not object");
|
||
continue;
|
||
}
|
||
old = shash_find_data(json_object(row_update), "old");
|
||
new = shash_find_data(json_object(row_update), "new");
|
||
if (initial) {
|
||
monitor_print_row(new, "initial", node->name, columns, &t);
|
||
} else if (!old) {
|
||
monitor_print_row(new, "insert", node->name, columns, &t);
|
||
} else if (!new) {
|
||
monitor_print_row(old, "delete", node->name, columns, &t);
|
||
} else {
|
||
monitor_print_row(old, "old", node->name, columns, &t);
|
||
monitor_print_row(new, "new", "", columns, &t);
|
||
}
|
||
}
|
||
table_print(&t, &table_style);
|
||
table_destroy(&t);
|
||
}
|
||
|
||
static void
|
||
monitor_print(const struct json *table_updates,
|
||
const struct monitored_table *mts, size_t n_mts,
|
||
bool initial)
|
||
{
|
||
size_t i;
|
||
|
||
if (table_updates->type != JSON_OBJECT) {
|
||
ovs_error(0, "<table-updates> is not object");
|
||
return;
|
||
}
|
||
|
||
for (i = 0; i < n_mts; i++) {
|
||
const struct monitored_table *mt = &mts[i];
|
||
struct json *table_update = shash_find_data(json_object(table_updates),
|
||
mt->table->name);
|
||
if (table_update) {
|
||
monitor_print_table(table_update, mt,
|
||
n_mts > 1 ? xstrdup(mt->table->name) : NULL,
|
||
initial);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
monitor2_print_row(const struct json *row, const char *type, const char *uuid,
|
||
const struct ovsdb_column_set *columns, struct table *t)
|
||
{
|
||
if (!strcmp(type, "delete")) {
|
||
if (row->type != JSON_NULL) {
|
||
ovs_error(0, "delete method does not expect <row>");
|
||
return;
|
||
}
|
||
|
||
table_add_row(t);
|
||
table_add_cell(t)->text = xstrdup(uuid);
|
||
table_add_cell(t)->text = xstrdup(type);
|
||
} else {
|
||
if (!row || row->type != JSON_OBJECT) {
|
||
ovs_error(0, "<row> is not object");
|
||
return;
|
||
}
|
||
monitor_print_row(row, type, uuid, columns, t);
|
||
}
|
||
}
|
||
|
||
static void
|
||
monitor2_print_table(const struct json *table_update2,
|
||
const struct monitored_table *mt, char *caption)
|
||
{
|
||
const struct ovsdb_table_schema *table = mt->table;
|
||
const struct ovsdb_column_set *columns = &mt->columns;
|
||
struct shash_node *node;
|
||
struct table t;
|
||
|
||
if (table_update2->type != JSON_OBJECT) {
|
||
ovs_error(0, "<table-update> for table %s is not object", table->name);
|
||
return;
|
||
}
|
||
|
||
table_init(&t);
|
||
table_set_timestamp(&t, timestamp);
|
||
table_set_caption(&t, caption);
|
||
|
||
table_add_column(&t, "row");
|
||
table_add_column(&t, "action");
|
||
for (size_t i = 0; i < columns->n_columns; i++) {
|
||
table_add_column(&t, "%s", columns->columns[i]->name);
|
||
}
|
||
SHASH_FOR_EACH (node, json_object(table_update2)) {
|
||
struct json *row_update2 = node->data;
|
||
const char *operation;
|
||
struct json *row;
|
||
const char *ops[] = {"delete", "initial", "modify", "insert"};
|
||
|
||
if (row_update2->type != JSON_OBJECT) {
|
||
ovs_error(0, "<row-update2> is not object");
|
||
continue;
|
||
}
|
||
|
||
/* row_update2 contains one of objects indexed by ops[] */
|
||
for (int i = 0; i < ARRAY_SIZE(ops); i++) {
|
||
operation = ops[i];
|
||
row = shash_find_data(json_object(row_update2), operation);
|
||
|
||
if (row) {
|
||
monitor2_print_row(row, operation, node->name, columns, &t);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
table_print(&t, &table_style);
|
||
table_destroy(&t);
|
||
}
|
||
|
||
static void
|
||
monitor2_print(const struct json *table_updates2,
|
||
const struct monitored_table *mts, size_t n_mts)
|
||
{
|
||
size_t i;
|
||
|
||
if (table_updates2->type != JSON_OBJECT) {
|
||
ovs_error(0, "<table-updates2> is not object");
|
||
return;
|
||
}
|
||
|
||
for (i = 0; i < n_mts; i++) {
|
||
const struct monitored_table *mt = &mts[i];
|
||
struct json *table_update = shash_find_data(
|
||
json_object(table_updates2),
|
||
mt->table->name);
|
||
if (table_update) {
|
||
monitor2_print_table(table_update, mt,
|
||
n_mts > 1 ? xstrdup(mt->table->name) : NULL);
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
monitor3_print(const struct json *result,
|
||
const struct monitored_table *mts, size_t n_mts)
|
||
{
|
||
if (result->type != JSON_ARRAY) {
|
||
ovs_error(0, "<result> is not array");
|
||
}
|
||
|
||
if (json_array_size(result) != 3) {
|
||
ovs_error(0, "<result> should have 3 elements, but has %"PRIuSIZE".",
|
||
json_array_size(result));
|
||
}
|
||
|
||
bool found = json_boolean(json_array_at(result, 0));
|
||
const char *last_id = json_string(json_array_at(result, 1));
|
||
printf("found: %s, last_id: %s\n", found ? "true" : "false", last_id);
|
||
|
||
const struct json *table_updates2 = json_array_at(result, 2);
|
||
monitor2_print(table_updates2, mts, n_mts);
|
||
}
|
||
|
||
static void
|
||
monitor3_notify_print(const char *last_id, const struct json *table_updates2,
|
||
const struct monitored_table *mts, size_t n_mts)
|
||
{
|
||
printf("\nlast_id: %s", last_id);
|
||
monitor2_print(table_updates2, mts, n_mts);
|
||
}
|
||
|
||
static void
|
||
add_column(const char *server, const struct ovsdb_column *column,
|
||
struct ovsdb_column_set *columns, struct json *columns_json)
|
||
{
|
||
if (ovsdb_column_set_contains(columns, column->index)) {
|
||
ovs_fatal(0, "%s: column \"%s\" mentioned multiple times",
|
||
server, column->name);
|
||
}
|
||
ovsdb_column_set_add(columns, column);
|
||
json_array_add(columns_json, json_string_create(column->name));
|
||
}
|
||
|
||
static struct json *
|
||
parse_monitor_columns(char *arg, const char *server, const char *database,
|
||
const struct ovsdb_table_schema *table,
|
||
struct ovsdb_column_set *columns)
|
||
{
|
||
bool initial, insert, delete, modify;
|
||
struct json *mr, *columns_json;
|
||
char *save_ptr = NULL;
|
||
char *token;
|
||
|
||
mr = json_object_create();
|
||
columns_json = json_array_create_empty();
|
||
json_object_put(mr, "columns", columns_json);
|
||
|
||
initial = insert = delete = modify = true;
|
||
for (token = strtok_r(arg, ",", &save_ptr); token != NULL;
|
||
token = strtok_r(NULL, ",", &save_ptr)) {
|
||
if (!strcmp(token, "!initial")) {
|
||
initial = false;
|
||
} else if (!strcmp(token, "!insert")) {
|
||
insert = false;
|
||
} else if (!strcmp(token, "!delete")) {
|
||
delete = false;
|
||
} else if (!strcmp(token, "!modify")) {
|
||
modify = false;
|
||
} else {
|
||
const struct ovsdb_column *column;
|
||
|
||
column = ovsdb_table_schema_get_column(table, token);
|
||
if (!column) {
|
||
ovs_fatal(0, "%s: table \"%s\" in %s does not have a "
|
||
"column named \"%s\"",
|
||
server, table->name, database, token);
|
||
}
|
||
add_column(server, column, columns, columns_json);
|
||
}
|
||
}
|
||
|
||
if (json_array_size(columns_json) == 0) {
|
||
const struct shash_node **nodes;
|
||
size_t i, n;
|
||
|
||
n = shash_count(&table->columns);
|
||
nodes = shash_sort(&table->columns);
|
||
for (i = 0; i < n; i++) {
|
||
const struct ovsdb_column *column = nodes[i]->data;
|
||
if (column->index != OVSDB_COL_UUID
|
||
&& column->index != OVSDB_COL_VERSION) {
|
||
add_column(server, column, columns, columns_json);
|
||
}
|
||
}
|
||
free(nodes);
|
||
|
||
const struct ovsdb_column *version_column =
|
||
ovsdb_table_schema_get_column(table, "_version");
|
||
|
||
ovs_assert(version_column);
|
||
add_column(server, version_column, columns, columns_json);
|
||
}
|
||
|
||
if (!initial || !insert || !delete || !modify) {
|
||
struct json *select = json_object_create();
|
||
json_object_put(select, "initial", json_boolean_create(initial));
|
||
json_object_put(select, "insert", json_boolean_create(insert));
|
||
json_object_put(select, "delete", json_boolean_create(delete));
|
||
json_object_put(select, "modify", json_boolean_create(modify));
|
||
json_object_put(mr, "select", select);
|
||
}
|
||
|
||
return mr;
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_exit(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[] OVS_UNUSED, void *exiting_)
|
||
{
|
||
bool *exiting = exiting_;
|
||
*exiting = true;
|
||
unixctl_command_reply(conn, NULL);
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_block(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[] OVS_UNUSED, void *blocked_)
|
||
{
|
||
bool *blocked = blocked_;
|
||
|
||
if (!*blocked) {
|
||
*blocked = true;
|
||
unixctl_command_reply(conn, NULL);
|
||
} else {
|
||
unixctl_command_reply(conn, "already blocking");
|
||
}
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_unblock(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[] OVS_UNUSED, void *blocked_)
|
||
{
|
||
bool *blocked = blocked_;
|
||
|
||
if (*blocked) {
|
||
*blocked = false;
|
||
unixctl_command_reply(conn, NULL);
|
||
} else {
|
||
unixctl_command_reply(conn, "already unblocked");
|
||
}
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_cond_change(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[], void *rpc_)
|
||
{
|
||
struct jsonrpc *rpc = rpc_;
|
||
struct json *monitor_cond_update_requests = json_object_create();
|
||
struct json *monitor_cond_update_request = json_object_create();
|
||
struct json *params;
|
||
struct jsonrpc_msg *request;
|
||
|
||
json_object_put(monitor_cond_update_request, "where",
|
||
json_from_string(argv[2]));
|
||
json_object_put(monitor_cond_update_requests,
|
||
argv[1],
|
||
json_array_create_1(monitor_cond_update_request));
|
||
|
||
params = json_array_create_3(json_null_create(),json_null_create(),
|
||
monitor_cond_update_requests);
|
||
|
||
request = jsonrpc_create_request("monitor_cond_change", params, NULL);
|
||
jsonrpc_send(rpc, request);
|
||
|
||
VLOG_DBG("cond change %s %s", argv[1], argv[2]);
|
||
unixctl_command_reply(conn, "condition changed");
|
||
}
|
||
|
||
static void
|
||
add_monitored_table(int argc, char *argv[],
|
||
const char *server, const char *database,
|
||
struct json *condition,
|
||
struct ovsdb_table_schema *table,
|
||
struct json *monitor_requests,
|
||
struct monitored_table **mts,
|
||
size_t *n_mts, size_t *allocated_mts)
|
||
{
|
||
struct json *monitor_request_array, *mr;
|
||
struct monitored_table *mt;
|
||
|
||
if (*n_mts >= *allocated_mts) {
|
||
*mts = x2nrealloc(*mts, allocated_mts, sizeof **mts);
|
||
}
|
||
mt = &(*mts)[(*n_mts)++];
|
||
mt->table = table;
|
||
ovsdb_column_set_init(&mt->columns);
|
||
|
||
monitor_request_array = json_array_create_empty();
|
||
if (argc > 1) {
|
||
int i;
|
||
|
||
for (i = 1; i < argc; i++) {
|
||
mr = parse_monitor_columns(argv[i], server, database, table,
|
||
&mt->columns);
|
||
if (i == 1 && condition) {
|
||
json_object_put(mr, "where", condition);
|
||
}
|
||
json_array_add(monitor_request_array, mr);
|
||
}
|
||
} else {
|
||
/* Allocate a writable empty string since parse_monitor_columns()
|
||
* is going to strtok() it and that's risky with literal "". */
|
||
char empty[] = "";
|
||
|
||
mr = parse_monitor_columns(empty, server, database,
|
||
table, &mt->columns);
|
||
if (condition) {
|
||
json_object_put(mr, "where", condition);
|
||
}
|
||
json_array_add(monitor_request_array, mr);
|
||
}
|
||
|
||
json_object_put(monitor_requests, table->name, monitor_request_array);
|
||
}
|
||
|
||
static void
|
||
destroy_monitored_table(struct monitored_table *mts, size_t n)
|
||
{
|
||
int i;
|
||
|
||
for (i = 0; i < n; i++) {
|
||
struct monitored_table *mt = &mts[i];
|
||
ovsdb_column_set_destroy(&mt->columns);
|
||
}
|
||
|
||
free(mts);
|
||
}
|
||
|
||
static void
|
||
do_monitor__(struct jsonrpc *rpc, const char *database,
|
||
enum ovsdb_monitor_version version,
|
||
int argc, char *argv[], struct json *condition,
|
||
const struct uuid *last_id)
|
||
{
|
||
const char *server = jsonrpc_get_name(rpc);
|
||
const char *table_name = argv[0];
|
||
struct unixctl_server *unixctl;
|
||
struct ovsdb_schema *schema;
|
||
struct json *monitor, *monitor_requests, *request_id;
|
||
bool exiting = false;
|
||
bool blocked = false;
|
||
|
||
struct monitored_table *mts;
|
||
size_t n_mts, allocated_mts;
|
||
|
||
ovs_assert(version < OVSDB_MONITOR_VERSION_MAX);
|
||
|
||
daemon_save_fd(STDOUT_FILENO);
|
||
daemon_save_fd(STDERR_FILENO);
|
||
daemonize_start(false, false);
|
||
if (get_detach()) {
|
||
int error;
|
||
|
||
error = unixctl_server_create(NULL, &unixctl);
|
||
if (error) {
|
||
ovs_fatal(error, "failed to create unixctl server");
|
||
}
|
||
|
||
unixctl_command_register("exit", "", 0, 0,
|
||
ovsdb_client_exit, &exiting);
|
||
unixctl_command_register("ovsdb-client/block", "", 0, 0,
|
||
ovsdb_client_block, &blocked);
|
||
unixctl_command_register("ovsdb-client/unblock", "", 0, 0,
|
||
ovsdb_client_unblock, &blocked);
|
||
unixctl_command_register("ovsdb-client/cond_change", "TABLE COND", 2, 2,
|
||
ovsdb_client_cond_change, rpc);
|
||
} else {
|
||
unixctl = NULL;
|
||
}
|
||
|
||
schema = fetch_schema(rpc, database);
|
||
|
||
monitor_requests = json_object_create();
|
||
|
||
mts = NULL;
|
||
n_mts = allocated_mts = 0;
|
||
if (strcmp(table_name, "ALL")) {
|
||
struct ovsdb_table_schema *table;
|
||
|
||
table = shash_find_data(&schema->tables, table_name);
|
||
if (!table) {
|
||
ovs_fatal(0, "%s: %s does not have a table named \"%s\"",
|
||
server, database, table_name);
|
||
}
|
||
|
||
add_monitored_table(argc, argv, server, database, condition, table,
|
||
monitor_requests, &mts, &n_mts, &allocated_mts);
|
||
} else {
|
||
size_t n = shash_count(&schema->tables);
|
||
const struct shash_node **nodes = shash_sort(&schema->tables);
|
||
size_t i;
|
||
|
||
if (condition) {
|
||
ovs_fatal(0, "ALL tables are not allowed with condition");
|
||
}
|
||
|
||
for (i = 0; i < n; i++) {
|
||
struct ovsdb_table_schema *table = nodes[i]->data;
|
||
|
||
add_monitored_table(argc, argv, server, database, NULL, table,
|
||
monitor_requests,
|
||
&mts, &n_mts, &allocated_mts);
|
||
}
|
||
free(nodes);
|
||
}
|
||
|
||
send_db_change_aware(rpc);
|
||
|
||
monitor = json_array_create_3(json_string_create(database),
|
||
json_null_create(), monitor_requests);
|
||
const char *method;
|
||
switch (version) {
|
||
case OVSDB_MONITOR_V1:
|
||
method = "monitor";
|
||
break;
|
||
case OVSDB_MONITOR_V2:
|
||
method = "monitor_cond";
|
||
break;
|
||
case OVSDB_MONITOR_V3:
|
||
method = "monitor_cond_since";
|
||
struct json *json_last_id = json_string_create_uuid(last_id);
|
||
json_array_add(monitor, json_last_id);
|
||
break;
|
||
case OVSDB_MONITOR_VERSION_MAX:
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
|
||
struct jsonrpc_msg *request;
|
||
request = jsonrpc_create_request(method, monitor, NULL);
|
||
request_id = json_clone(request->id);
|
||
jsonrpc_send(rpc, request);
|
||
|
||
for (;;) {
|
||
unixctl_server_run(unixctl);
|
||
while (!blocked) {
|
||
struct jsonrpc_msg *msg;
|
||
int error;
|
||
|
||
error = jsonrpc_recv(rpc, &msg);
|
||
if (error == EAGAIN) {
|
||
break;
|
||
} else if (error) {
|
||
ovs_fatal(error, "%s: receive failed", server);
|
||
}
|
||
|
||
if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
|
||
jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
|
||
msg->id));
|
||
} else if (msg->type == JSONRPC_REPLY
|
||
&& json_equal(msg->id, request_id)) {
|
||
switch(version) {
|
||
case OVSDB_MONITOR_V1:
|
||
monitor_print(msg->result, mts, n_mts, true);
|
||
break;
|
||
case OVSDB_MONITOR_V2:
|
||
monitor2_print(msg->result, mts, n_mts);
|
||
break;
|
||
case OVSDB_MONITOR_V3:
|
||
monitor3_print(msg->result, mts, n_mts);
|
||
break;
|
||
case OVSDB_MONITOR_VERSION_MAX:
|
||
default:
|
||
OVS_NOT_REACHED();
|
||
}
|
||
fflush(stdout);
|
||
daemonize_complete();
|
||
} else if (msg->type == JSONRPC_NOTIFY
|
||
&& !strcmp(msg->method, "update")) {
|
||
struct json *params = msg->params;
|
||
if (params->type == JSON_ARRAY
|
||
&& json_array_size(params) == 2
|
||
&& json_array_at(params, 0)->type == JSON_NULL) {
|
||
monitor_print(json_array_at(params, 1), mts, n_mts, false);
|
||
fflush(stdout);
|
||
}
|
||
} else if (msg->type == JSONRPC_NOTIFY
|
||
&& version == OVSDB_MONITOR_V2
|
||
&& !strcmp(msg->method, "update2")) {
|
||
struct json *params = msg->params;
|
||
if (params->type == JSON_ARRAY
|
||
&& json_array_size(params) == 2
|
||
&& json_array_at(params, 0)->type == JSON_NULL) {
|
||
monitor2_print(json_array_at(params, 1), mts, n_mts);
|
||
fflush(stdout);
|
||
}
|
||
} else if (msg->type == JSONRPC_NOTIFY
|
||
&& version == OVSDB_MONITOR_V3
|
||
&& !strcmp(msg->method, "update3")) {
|
||
struct json *params = msg->params;
|
||
if (params->type == JSON_ARRAY
|
||
&& json_array_size(params) == 3
|
||
&& json_array_at(params, 0)->type == JSON_NULL) {
|
||
monitor3_notify_print(
|
||
json_string(json_array_at(params, 1)),
|
||
json_array_at(params, 2), mts, n_mts);
|
||
fflush(stdout);
|
||
}
|
||
} else if (msg->type == JSONRPC_NOTIFY
|
||
&& !strcmp(msg->method, "monitor_canceled")) {
|
||
ovs_fatal(0, "%s: %s database was removed",
|
||
server, database);
|
||
}
|
||
jsonrpc_msg_destroy(msg);
|
||
}
|
||
|
||
if (exiting) {
|
||
break;
|
||
}
|
||
|
||
jsonrpc_run(rpc);
|
||
jsonrpc_wait(rpc);
|
||
if (!blocked) {
|
||
jsonrpc_recv_wait(rpc);
|
||
}
|
||
unixctl_server_wait(unixctl);
|
||
poll_block();
|
||
}
|
||
|
||
json_destroy(request_id);
|
||
unixctl_server_destroy(unixctl);
|
||
ovsdb_schema_destroy(schema);
|
||
destroy_monitored_table(mts, n_mts);
|
||
}
|
||
|
||
static void
|
||
do_monitor(struct jsonrpc *rpc, const char *database,
|
||
int argc, char *argv[])
|
||
{
|
||
do_monitor__(rpc, database, OVSDB_MONITOR_V1, argc, argv, NULL, NULL);
|
||
}
|
||
|
||
static void
|
||
do_monitor_cond__(struct jsonrpc *rpc, const char *database,
|
||
enum ovsdb_monitor_version version,
|
||
struct uuid *last_id, int argc, char *argv[])
|
||
{
|
||
struct ovsdb_condition cnd;
|
||
struct json *condition = NULL;
|
||
struct ovsdb_schema *schema;
|
||
struct ovsdb_table_schema *table;
|
||
const char *table_name = argv[1];
|
||
|
||
ovs_assert(argc > 1);
|
||
schema = fetch_schema(rpc, database);
|
||
table = shash_find_data(&schema->tables, table_name);
|
||
if (!table) {
|
||
ovs_fatal(0, "%s does not have a table named \"%s\"",
|
||
database, table_name);
|
||
}
|
||
condition = parse_json(argv[0]);
|
||
check_ovsdb_error(ovsdb_condition_from_json(table, condition,
|
||
NULL, &cnd));
|
||
ovsdb_condition_destroy(&cnd);
|
||
do_monitor__(rpc, database, version, --argc, ++argv, condition,
|
||
last_id);
|
||
ovsdb_schema_destroy(schema);
|
||
}
|
||
|
||
static void
|
||
do_monitor_cond(struct jsonrpc *rpc, const char *database,
|
||
int argc, char *argv[])
|
||
{
|
||
do_monitor_cond__(rpc, database, OVSDB_MONITOR_V2, NULL, argc, argv);
|
||
}
|
||
|
||
static void
|
||
do_monitor_cond_since(struct jsonrpc *rpc, const char *database,
|
||
int argc, char *argv[])
|
||
{
|
||
ovs_assert(argc > 1);
|
||
struct uuid last_id;
|
||
if (uuid_from_string(&last_id, argv[0])) {
|
||
argc--;
|
||
argv++;
|
||
}
|
||
do_monitor_cond__(rpc, database, OVSDB_MONITOR_V3, &last_id, argc, argv);
|
||
}
|
||
|
||
static bool
|
||
is_database_clustered(struct jsonrpc *rpc, const char *database)
|
||
{
|
||
struct jsonrpc_msg *reply;
|
||
check_txn(jsonrpc_transact_block(rpc,
|
||
create_database_info_request(database),
|
||
&reply), &reply);
|
||
|
||
const struct json *row = parse_database_info_reply(
|
||
reply, jsonrpc_get_name(rpc), database, NULL);
|
||
bool clustered = !strcmp(parse_string_column(row, "model"), "clustered");
|
||
jsonrpc_msg_destroy(reply);
|
||
return clustered;
|
||
}
|
||
|
||
static void
|
||
do_convert(struct jsonrpc *rpc, const char *database_ OVS_UNUSED,
|
||
int argc, char *argv[])
|
||
{
|
||
const char *schema_file_name = argv[argc - 1];
|
||
struct ovsdb_schema *new_schema;
|
||
check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &new_schema));
|
||
|
||
char *database = new_schema->name;
|
||
open_rpc(1, NEED_DATABASE, argc, argv, &rpc, &database);
|
||
|
||
if (is_database_clustered(rpc, database)) {
|
||
ovsdb_schema_persist_ephemeral_columns(new_schema, schema_file_name);
|
||
}
|
||
|
||
send_db_change_aware(rpc);
|
||
|
||
struct jsonrpc_msg *request, *reply;
|
||
request = jsonrpc_create_request(
|
||
"convert",
|
||
json_array_create_2(json_string_create(new_schema->name),
|
||
ovsdb_schema_to_json(new_schema)), NULL);
|
||
check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);
|
||
jsonrpc_msg_destroy(reply);
|
||
ovsdb_schema_destroy(new_schema);
|
||
jsonrpc_close(rpc);
|
||
}
|
||
|
||
static void
|
||
do_needs_conversion(struct jsonrpc *rpc, const char *database_ OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[])
|
||
{
|
||
const char *schema_file_name = argv[argc - 1];
|
||
struct ovsdb_schema *schema1;
|
||
check_ovsdb_error(ovsdb_schema_from_file(schema_file_name, &schema1));
|
||
|
||
char *database = schema1->name;
|
||
open_rpc(1, NEED_DATABASE, argc, argv, &rpc, &database);
|
||
|
||
if (is_database_clustered(rpc, database)) {
|
||
ovsdb_schema_persist_ephemeral_columns(schema1, schema_file_name);
|
||
}
|
||
|
||
struct ovsdb_schema *schema2 = fetch_schema(rpc, schema1->name);
|
||
puts(ovsdb_schema_equal(schema1, schema2) ? "no" : "yes");
|
||
ovsdb_schema_destroy(schema1);
|
||
ovsdb_schema_destroy(schema2);
|
||
jsonrpc_close(rpc);
|
||
}
|
||
|
||
struct dump_table_aux {
|
||
struct ovsdb_datum **data;
|
||
const struct ovsdb_column **columns;
|
||
size_t n_columns;
|
||
};
|
||
|
||
static int
|
||
compare_data(size_t a_y, size_t b_y, size_t x,
|
||
const struct dump_table_aux *aux)
|
||
{
|
||
return ovsdb_datum_compare_3way(&aux->data[a_y][x],
|
||
&aux->data[b_y][x],
|
||
&aux->columns[x]->type);
|
||
}
|
||
|
||
static int
|
||
compare_rows(size_t a_y, size_t b_y, void *aux_)
|
||
{
|
||
struct dump_table_aux *aux = aux_;
|
||
size_t x;
|
||
|
||
/* Skip UUID columns on the first pass, since their values tend to be
|
||
* random and make our results less reproducible. */
|
||
for (x = 0; x < aux->n_columns; x++) {
|
||
if (aux->columns[x]->type.key.type != OVSDB_TYPE_UUID) {
|
||
int cmp = compare_data(a_y, b_y, x, aux);
|
||
if (cmp) {
|
||
return cmp;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* Use UUID columns as tie-breakers. */
|
||
for (x = 0; x < aux->n_columns; x++) {
|
||
if (aux->columns[x]->type.key.type == OVSDB_TYPE_UUID) {
|
||
int cmp = compare_data(a_y, b_y, x, aux);
|
||
if (cmp) {
|
||
return cmp;
|
||
}
|
||
}
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
static void
|
||
swap_rows(size_t a_y, size_t b_y, void *aux_)
|
||
{
|
||
struct dump_table_aux *aux = aux_;
|
||
struct ovsdb_datum *tmp = aux->data[a_y];
|
||
aux->data[a_y] = aux->data[b_y];
|
||
aux->data[b_y] = tmp;
|
||
}
|
||
|
||
static int
|
||
compare_columns(const void *a_, const void *b_)
|
||
{
|
||
const struct ovsdb_column *const *ap = a_;
|
||
const struct ovsdb_column *const *bp = b_;
|
||
const struct ovsdb_column *a = *ap;
|
||
const struct ovsdb_column *b = *bp;
|
||
|
||
return strcmp(a->name, b->name);
|
||
}
|
||
|
||
static void
|
||
dump_table(const char *table_name, const struct shash *cols,
|
||
const struct json *rows)
|
||
{
|
||
const struct ovsdb_column **columns;
|
||
size_t n_columns;
|
||
|
||
struct ovsdb_datum **data;
|
||
|
||
struct dump_table_aux aux;
|
||
struct shash_node *node;
|
||
struct table t;
|
||
size_t x, y, n;
|
||
|
||
/* Sort columns by name, for reproducibility. */
|
||
columns = xmalloc(shash_count(cols) * sizeof *columns);
|
||
n_columns = 0;
|
||
SHASH_FOR_EACH (node, cols) {
|
||
struct ovsdb_column *column = node->data;
|
||
if (strcmp(column->name, "_version")) {
|
||
columns[n_columns++] = column;
|
||
}
|
||
}
|
||
qsort(columns, n_columns, sizeof *columns, compare_columns);
|
||
|
||
/* Extract data from table. */
|
||
n = json_array_size(rows);
|
||
data = xmalloc(n * sizeof *data);
|
||
for (y = 0; y < n; y++) {
|
||
const struct json *elem = json_array_at(rows, y);
|
||
struct shash *row;
|
||
|
||
if (elem->type != JSON_OBJECT) {
|
||
ovs_fatal(0, "row %"PRIuSIZE" in table %s response is not a JSON object: "
|
||
"%s", y, table_name, json_to_string(elem, 0));
|
||
}
|
||
row = json_object(elem);
|
||
|
||
data[y] = xmalloc(n_columns * sizeof **data);
|
||
for (x = 0; x < n_columns; x++) {
|
||
const struct json *json = shash_find_data(row, columns[x]->name);
|
||
if (!json) {
|
||
ovs_fatal(0, "row %"PRIuSIZE" in table %s response lacks %s column",
|
||
y, table_name, columns[x]->name);
|
||
}
|
||
|
||
check_ovsdb_error(ovsdb_unconstrained_datum_from_json(
|
||
&data[y][x], &columns[x]->type, json));
|
||
}
|
||
}
|
||
|
||
/* Sort rows by column values, for reproducibility. */
|
||
aux.data = data;
|
||
aux.columns = columns;
|
||
aux.n_columns = n_columns;
|
||
sort(n, compare_rows, swap_rows, &aux);
|
||
|
||
/* Add column headings. */
|
||
table_init(&t);
|
||
table_set_caption(&t, xasprintf("%s table", table_name));
|
||
for (x = 0; x < n_columns; x++) {
|
||
table_add_column(&t, "%s", columns[x]->name);
|
||
}
|
||
|
||
/* Print rows. */
|
||
for (y = 0; y < n; y++) {
|
||
table_add_row(&t);
|
||
for (x = 0; x < n_columns; x++) {
|
||
struct cell *cell = table_add_cell(&t);
|
||
cell->json = ovsdb_datum_to_json(&data[y][x], &columns[x]->type);
|
||
cell->type = &columns[x]->type;
|
||
ovsdb_datum_destroy(&data[y][x], &columns[x]->type);
|
||
}
|
||
free(data[y]);
|
||
}
|
||
table_print(&t, &table_style);
|
||
table_destroy(&t);
|
||
|
||
free(data);
|
||
free(columns);
|
||
}
|
||
|
||
static void
|
||
do_dump(struct jsonrpc *rpc, const char *database,
|
||
int argc, char *argv[])
|
||
{
|
||
struct jsonrpc_msg *request, *reply;
|
||
struct ovsdb_schema *schema;
|
||
struct json *transaction;
|
||
|
||
const struct shash_node *node, **tables = NULL;
|
||
size_t n_tables;
|
||
struct ovsdb_table_schema *tschema;
|
||
const struct shash *columns;
|
||
struct shash custom_columns;
|
||
|
||
size_t i;
|
||
|
||
shash_init(&custom_columns);
|
||
schema = fetch_schema(rpc, database);
|
||
if (argc) {
|
||
node = shash_find(&schema->tables, argv[0]);
|
||
if (!node) {
|
||
ovs_fatal(0, "No table \"%s\" found.", argv[0]);
|
||
}
|
||
tables = xmemdup(&node, sizeof node);
|
||
n_tables = 1;
|
||
tschema = tables[0]->data;
|
||
for (i = 1; i < argc; i++) {
|
||
node = shash_find(&tschema->columns, argv[i]);
|
||
if (!node) {
|
||
ovs_fatal(0, "Table \"%s\" has no column %s.", argv[0], argv[i]);
|
||
}
|
||
shash_add(&custom_columns, argv[i], node->data);
|
||
}
|
||
} else {
|
||
n_tables = shash_count(&schema->tables);
|
||
if (n_tables) {
|
||
tables = shash_sort(&schema->tables);
|
||
}
|
||
}
|
||
|
||
/* Construct transaction to retrieve entire database. */
|
||
transaction = json_array_create_1(json_string_create(database));
|
||
for (i = 0; i < n_tables; i++) {
|
||
const struct ovsdb_table_schema *ts = tables[i]->data;
|
||
struct json *op, *jcolumns;
|
||
|
||
if (argc > 1) {
|
||
columns = &custom_columns;
|
||
} else {
|
||
columns = &ts->columns;
|
||
}
|
||
jcolumns = json_array_create_empty();
|
||
SHASH_FOR_EACH (node, columns) {
|
||
const struct ovsdb_column *column = node->data;
|
||
|
||
if (strcmp(column->name, "_version")) {
|
||
json_array_add(jcolumns, json_string_create(column->name));
|
||
}
|
||
}
|
||
|
||
op = json_object_create();
|
||
json_object_put_string(op, "op", "select");
|
||
json_object_put_string(op, "table", tables[i]->name);
|
||
json_object_put(op, "where", json_array_create_empty());
|
||
json_object_put(op, "columns", jcolumns);
|
||
json_array_add(transaction, op);
|
||
}
|
||
|
||
/* Send request, get reply. */
|
||
request = jsonrpc_create_request("transact", transaction, NULL);
|
||
check_txn(jsonrpc_transact_block(rpc, request, &reply), &reply);
|
||
|
||
/* Print database contents. */
|
||
if (reply->result->type != JSON_ARRAY
|
||
|| json_array_size(reply->result) != n_tables) {
|
||
ovs_fatal(0, "reply is not array of %"PRIuSIZE" elements: %s",
|
||
n_tables, json_to_string(reply->result, 0));
|
||
}
|
||
for (i = 0; i < n_tables; i++) {
|
||
const struct ovsdb_table_schema *ts = tables[i]->data;
|
||
const struct json *op_result = json_array_at(reply->result, i);
|
||
struct json *rows;
|
||
|
||
if (op_result->type != JSON_OBJECT
|
||
|| !(rows = shash_find_data(json_object(op_result), "rows"))
|
||
|| rows->type != JSON_ARRAY) {
|
||
ovs_fatal(0, "%s table reply is not an object with a \"rows\" "
|
||
"member array: %s",
|
||
ts->name, json_to_string(op_result, 0));
|
||
}
|
||
|
||
if (argc > 1) {
|
||
dump_table(tables[i]->name, &custom_columns, rows);
|
||
} else {
|
||
dump_table(tables[i]->name, &ts->columns, rows);
|
||
}
|
||
}
|
||
|
||
jsonrpc_msg_destroy(reply);
|
||
shash_destroy(&custom_columns);
|
||
free(tables);
|
||
ovsdb_schema_destroy(schema);
|
||
}
|
||
|
||
static void
|
||
print_and_free_log_record(struct json *record)
|
||
{
|
||
struct ds header = DS_EMPTY_INITIALIZER;
|
||
struct ds data = DS_EMPTY_INITIALIZER;
|
||
ovsdb_log_compose_record(record, OVSDB_MAGIC, &header, &data);
|
||
fwrite(header.string, header.length, 1, stdout);
|
||
fwrite(data.string, data.length, 1, stdout);
|
||
ds_destroy(&data);
|
||
ds_destroy(&header);
|
||
json_destroy(record);
|
||
}
|
||
|
||
static void
|
||
set_binary_mode(FILE *stream OVS_UNUSED)
|
||
{
|
||
#ifdef _WIN32
|
||
fflush(stream);
|
||
/* On Windows set binary mode on the file descriptor to avoid
|
||
* translation (i.e. CRLF line endings). */
|
||
if (_setmode(_fileno(stream), O_BINARY) == -1) {
|
||
ovs_fatal(errno, "could not set binary mode on fd %d",
|
||
_fileno(stream));
|
||
}
|
||
#endif
|
||
}
|
||
|
||
static void
|
||
do_backup(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
if (isatty(STDOUT_FILENO)) {
|
||
ovs_fatal(0, "not writing backup to a terminal; "
|
||
"please redirect stdout to a file");
|
||
}
|
||
set_binary_mode(stdout);
|
||
|
||
/* Get schema. */
|
||
struct ovsdb_schema *schema = fetch_schema(rpc, database);
|
||
|
||
/* Construct transaction to retrieve all tables. */
|
||
struct json *txn = json_array_create_1(json_string_create(database));
|
||
struct shash_node *node;
|
||
SHASH_FOR_EACH (node, &schema->tables) {
|
||
const char *table_name = node->name;
|
||
const struct ovsdb_table_schema *table = node->data;
|
||
|
||
/* Get all the columns except _version and the ephemeral ones.
|
||
*
|
||
* We don't omit tables that only have ephemeral columns because of the
|
||
* possibility that other tables references rows in those tables; that
|
||
* is, even if all the columns are ephemeral, the rows themselves are
|
||
* not. */
|
||
struct json *columns = json_array_create_empty();
|
||
struct shash_node *node2;
|
||
SHASH_FOR_EACH (node2, &table->columns) {
|
||
const struct ovsdb_column *column = node2->data;
|
||
|
||
if (column->persistent) {
|
||
if (!columns) {
|
||
columns = json_array_create_empty();
|
||
}
|
||
json_array_add(columns, json_string_create(column->name));
|
||
}
|
||
}
|
||
|
||
struct json *op = json_object_create();
|
||
json_object_put_string(op, "op", "select");
|
||
json_object_put_string(op, "table", table_name);
|
||
json_object_put(op, "where", json_array_create_empty());
|
||
json_object_put(op, "columns", columns);
|
||
json_array_add(txn, op);
|
||
}
|
||
|
||
/* Send request, get reply. */
|
||
struct jsonrpc_msg *rq = jsonrpc_create_request("transact", txn, NULL);
|
||
struct jsonrpc_msg *reply;
|
||
check_txn(jsonrpc_transact_block(rpc, rq, &reply), &reply);
|
||
|
||
/* Print schema record. */
|
||
print_and_free_log_record(ovsdb_schema_to_json(schema));
|
||
|
||
/* Print database transaction record. */
|
||
if (reply->result->type != JSON_ARRAY
|
||
|| json_array_size(reply->result) != shash_count(&schema->tables)) {
|
||
ovs_fatal(0, "reply is not array of %"PRIuSIZE" elements: %s",
|
||
shash_count(&schema->tables),
|
||
json_to_string(reply->result, 0));
|
||
}
|
||
struct json *output_txn = json_object_create();
|
||
|
||
size_t i = 0;
|
||
SHASH_FOR_EACH (node, &schema->tables) {
|
||
const char *table_name = node->name;
|
||
const struct ovsdb_table_schema *table = node->data;
|
||
const struct json *op_result = json_array_at(reply->result, i++);
|
||
struct json *rows;
|
||
|
||
if (op_result->type != JSON_OBJECT
|
||
|| !(rows = shash_find_data(json_object(op_result), "rows"))
|
||
|| rows->type != JSON_ARRAY) {
|
||
ovs_fatal(0, "%s table reply is not an object with a \"rows\" "
|
||
"member array: %s",
|
||
table->name, json_to_string(op_result, 0));
|
||
}
|
||
|
||
size_t n = json_array_size(rows);
|
||
if (!n) {
|
||
continue;
|
||
}
|
||
|
||
struct json *output_rows = json_object_create();
|
||
for (size_t j = 0; j < n; j++) {
|
||
const struct json *row = json_array_at(rows, j);
|
||
if (row->type != JSON_OBJECT) {
|
||
ovs_fatal(0, "%s table reply row is not an object: %s",
|
||
table_name, json_to_string(row, 0));
|
||
}
|
||
|
||
struct json *uuid_json = shash_find_and_delete(json_object(row),
|
||
"_uuid");
|
||
if (!uuid_json) {
|
||
ovs_fatal(0, "%s table reply row lacks _uuid member: %s",
|
||
table_name, json_to_string(row, 0));
|
||
}
|
||
|
||
const struct ovsdb_base_type uuid_base = OVSDB_BASE_UUID_INIT;
|
||
union ovsdb_atom atom;
|
||
check_ovsdb_error(ovsdb_atom_from_json(&atom, &uuid_base,
|
||
uuid_json, NULL));
|
||
|
||
char uuid_s[UUID_LEN + 1];
|
||
snprintf(uuid_s, sizeof uuid_s, UUID_FMT, UUID_ARGS(&atom.uuid));
|
||
json_object_put(output_rows, uuid_s, json_clone(row));
|
||
json_destroy(uuid_json);
|
||
}
|
||
json_object_put(output_txn, table_name, output_rows);
|
||
}
|
||
output_txn = ovsdb_file_txn_annotate(
|
||
output_txn, "produced by \"ovsdb-client backup\"");
|
||
print_and_free_log_record(output_txn);
|
||
|
||
ovsdb_schema_destroy(schema);
|
||
jsonrpc_msg_destroy(reply);
|
||
}
|
||
|
||
static void
|
||
check_transaction_reply(struct jsonrpc_msg *reply)
|
||
{
|
||
if (reply->result->type != JSON_ARRAY) {
|
||
ovs_fatal(0, "result is not array");
|
||
}
|
||
for (size_t i = 0; i < json_array_size(reply->result); i++) {
|
||
const struct json *json = json_array_at(reply->result, i);
|
||
if (json->type != JSON_OBJECT) {
|
||
ovs_fatal(0, "result array element is not object");
|
||
}
|
||
struct shash *object = json_object(json);
|
||
if (shash_find(object, "error")) {
|
||
ovs_fatal(0, "server returned error reply: %s",
|
||
json_to_string(json, JSSF_SORT));
|
||
}
|
||
}
|
||
}
|
||
|
||
static void
|
||
do_restore(struct jsonrpc *rpc, const char *database,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
if (isatty(STDIN_FILENO)) {
|
||
ovs_fatal(0, "not reading backup from a terminal; "
|
||
"please redirect stdin from a file");
|
||
}
|
||
set_binary_mode(stdin);
|
||
|
||
struct ovsdb *backup = ovsdb_file_read("/dev/stdin", false);
|
||
ovsdb_storage_close(backup->storage);
|
||
backup->storage = NULL;
|
||
|
||
struct ovsdb_schema *online_schema = fetch_schema(rpc, database);
|
||
if (!ovsdb_schema_equal(backup->schema, online_schema)) {
|
||
struct ds s = DS_EMPTY_INITIALIZER;
|
||
if (strcmp(backup->schema->version, online_schema->version)) {
|
||
ds_put_format(&s, "backup schema has version \"%s\" but "
|
||
"database schema has version \"%s\"",
|
||
backup->schema->version, online_schema->version);
|
||
} else {
|
||
ds_put_format(&s, "backup schema and database schema are "
|
||
"both version %s but still differ",
|
||
backup->schema->version);
|
||
}
|
||
if (!force) {
|
||
ovs_fatal(0, "%s (use --force to override differences, or "
|
||
"\"ovsdb-client convert\" to change the schema)",
|
||
ds_cstr(&s));
|
||
}
|
||
VLOG_INFO("%s", ds_cstr(&s));
|
||
ds_destroy(&s);
|
||
}
|
||
ovsdb_schema_destroy(online_schema);
|
||
|
||
struct json *txn = json_array_create_empty();
|
||
json_array_add(txn, json_string_create(backup->schema->name));
|
||
struct shash_node *node;
|
||
SHASH_FOR_EACH (node, &backup->tables) {
|
||
const char *table_name = node->name;
|
||
struct ovsdb_table *table = node->data;
|
||
|
||
struct json *del_op = json_object_create();
|
||
json_object_put_string(del_op, "op", "delete");
|
||
json_object_put_string(del_op, "table", table_name);
|
||
json_object_put(del_op, "where", json_array_create_empty());
|
||
json_array_add(txn, del_op);
|
||
|
||
const struct ovsdb_row *row;
|
||
HMAP_FOR_EACH (row, hmap_node, &table->rows) {
|
||
struct json *ins_op = json_object_create();
|
||
json_object_put_string(ins_op, "op", "insert");
|
||
json_object_put_string(ins_op, "table", table_name);
|
||
json_object_put(ins_op, "uuid-name",
|
||
json_string_create_nocopy(
|
||
ovsdb_data_row_name(ovsdb_row_get_uuid(row))));
|
||
struct json *row_json = json_object_create();
|
||
json_object_put(ins_op, "row", row_json);
|
||
|
||
struct shash_node *node2;
|
||
SHASH_FOR_EACH (node2, &table->schema->columns) {
|
||
const struct ovsdb_column *column = node2->data;
|
||
const struct ovsdb_datum *datum = &row->fields[column->index];
|
||
const struct ovsdb_type *type = &column->type;
|
||
if (column->persistent
|
||
&& column->index >= OVSDB_N_STD_COLUMNS
|
||
&& !ovsdb_datum_is_default(datum, type)) {
|
||
struct json *value = ovsdb_datum_to_json_with_row_names(
|
||
datum, type);
|
||
json_object_put(row_json, column->name, value);
|
||
}
|
||
}
|
||
json_array_add(txn, ins_op);
|
||
}
|
||
}
|
||
ovsdb_destroy(backup);
|
||
struct jsonrpc_msg *rq = jsonrpc_create_request("transact", txn, NULL);
|
||
struct jsonrpc_msg *reply;
|
||
check_txn(jsonrpc_transact_block(rpc, rq, &reply), &reply);
|
||
check_transaction_reply(reply);
|
||
jsonrpc_msg_destroy(reply);
|
||
}
|
||
|
||
|
||
static void
|
||
do_help(struct jsonrpc *rpc OVS_UNUSED, const char *database OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
|
||
{
|
||
usage();
|
||
}
|
||
|
||
|
||
/* "lock" command. */
|
||
|
||
struct ovsdb_client_lock_req {
|
||
const char *method;
|
||
char *lock;
|
||
};
|
||
|
||
static void
|
||
lock_req_init(struct ovsdb_client_lock_req *lock_req,
|
||
const char *method, const char *lock_name)
|
||
{
|
||
if (lock_req->method || lock_req->lock) {
|
||
return;
|
||
}
|
||
lock_req->method = method;
|
||
lock_req->lock = xstrdup(lock_name);
|
||
}
|
||
|
||
static bool
|
||
lock_req_is_set(struct ovsdb_client_lock_req *lock_req)
|
||
{
|
||
return lock_req->method;
|
||
}
|
||
|
||
static void
|
||
lock_req_destroy(struct ovsdb_client_lock_req *lock_req)
|
||
{
|
||
free(lock_req->lock);
|
||
lock_req->method = NULL;
|
||
lock_req->lock = NULL;
|
||
}
|
||
|
||
/* Create a lock class request. Caller is responsible for free
|
||
* the 'request' message. */
|
||
static struct jsonrpc_msg *
|
||
create_lock_request(struct ovsdb_client_lock_req *lock_req)
|
||
{
|
||
struct json *locks, *lock;
|
||
|
||
locks = json_array_create_empty();
|
||
lock = json_string_create(lock_req->lock);
|
||
json_array_add(locks, lock);
|
||
|
||
return jsonrpc_create_request(lock_req->method, locks, NULL);
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_lock(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[], void *lock_req_)
|
||
{
|
||
struct ovsdb_client_lock_req *lock_req = lock_req_;
|
||
lock_req_init(lock_req, "lock", argv[1]);
|
||
unixctl_command_reply(conn, NULL);
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_unlock(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[], void *lock_req_)
|
||
{
|
||
struct ovsdb_client_lock_req *lock_req = lock_req_;
|
||
lock_req_init(lock_req, "unlock", argv[1]);
|
||
unixctl_command_reply(conn, NULL);
|
||
}
|
||
|
||
static void
|
||
ovsdb_client_steal(struct unixctl_conn *conn, int argc OVS_UNUSED,
|
||
const char *argv[], void *lock_req_)
|
||
{
|
||
struct ovsdb_client_lock_req *lock_req = lock_req_;
|
||
lock_req_init(lock_req, "steal", argv[1]);
|
||
unixctl_command_reply(conn, NULL);
|
||
}
|
||
|
||
static void
|
||
do_lock(struct jsonrpc *rpc, const char *method, const char *lock)
|
||
{
|
||
struct ovsdb_client_lock_req lock_req = {NULL, NULL};
|
||
struct unixctl_server *unixctl;
|
||
struct jsonrpc_msg *request;
|
||
struct json *request_id = NULL;
|
||
bool exiting = false;
|
||
bool enable_lock_request = true; /* Don't send another request before
|
||
getting a reply of the previous
|
||
request. */
|
||
daemon_save_fd(STDOUT_FILENO);
|
||
daemonize_start(false, false);
|
||
lock_req_init(&lock_req, method, lock);
|
||
|
||
if (get_detach()) {
|
||
int error;
|
||
|
||
error = unixctl_server_create(NULL, &unixctl);
|
||
if (error) {
|
||
ovs_fatal(error, "failed to create unixctl server");
|
||
}
|
||
|
||
unixctl_command_register("unlock", "LOCK", 1, 1,
|
||
ovsdb_client_unlock, &lock_req);
|
||
unixctl_command_register("steal", "LOCK", 1, 1,
|
||
ovsdb_client_steal, &lock_req);
|
||
unixctl_command_register("lock", "LOCK", 1, 1,
|
||
ovsdb_client_lock, &lock_req);
|
||
unixctl_command_register("exit", "", 0, 0,
|
||
ovsdb_client_exit, &exiting);
|
||
} else {
|
||
unixctl = NULL;
|
||
}
|
||
|
||
for (;;) {
|
||
struct jsonrpc_msg *msg;
|
||
int error;
|
||
|
||
unixctl_server_run(unixctl);
|
||
if (enable_lock_request && lock_req_is_set(&lock_req)) {
|
||
request = create_lock_request(&lock_req);
|
||
request_id = json_clone(request->id);
|
||
jsonrpc_send(rpc, request);
|
||
lock_req_destroy(&lock_req);
|
||
}
|
||
|
||
error = jsonrpc_recv(rpc, &msg);
|
||
if (error == EAGAIN) {
|
||
goto no_msg;
|
||
} else if (error) {
|
||
ovs_fatal(error, "%s: receive failed", jsonrpc_get_name(rpc));
|
||
}
|
||
|
||
if (msg->type == JSONRPC_REQUEST && !strcmp(msg->method, "echo")) {
|
||
jsonrpc_send(rpc, jsonrpc_create_reply(json_clone(msg->params),
|
||
msg->id));
|
||
} else if (msg->type == JSONRPC_REPLY
|
||
&& json_equal(msg->id, request_id)) {
|
||
print_json(msg->result);
|
||
fflush(stdout);
|
||
enable_lock_request = true;
|
||
json_destroy(request_id);
|
||
request_id = NULL;
|
||
daemonize_complete();
|
||
} else if (msg->type == JSONRPC_NOTIFY) {
|
||
puts(msg->method);
|
||
print_json(msg->params);
|
||
fflush(stdout);
|
||
}
|
||
|
||
jsonrpc_msg_destroy(msg);
|
||
|
||
no_msg:
|
||
if (exiting) {
|
||
break;
|
||
}
|
||
|
||
jsonrpc_run(rpc);
|
||
jsonrpc_wait(rpc);
|
||
jsonrpc_recv_wait(rpc);
|
||
|
||
unixctl_server_wait(unixctl);
|
||
poll_block();
|
||
}
|
||
|
||
json_destroy(request_id);
|
||
unixctl_server_destroy(unixctl);
|
||
}
|
||
|
||
static void
|
||
do_lock_create(struct jsonrpc *rpc, const char *database OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[])
|
||
{
|
||
do_lock(rpc, "lock", argv[0]);
|
||
}
|
||
|
||
static void
|
||
do_lock_steal(struct jsonrpc *rpc, const char *database OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[])
|
||
{
|
||
do_lock(rpc, "steal", argv[0]);
|
||
}
|
||
|
||
static void
|
||
do_lock_unlock(struct jsonrpc *rpc, const char *database OVS_UNUSED,
|
||
int argc OVS_UNUSED, char *argv[])
|
||
{
|
||
do_lock(rpc, "unlock", argv[0]);
|
||
}
|
||
|
||
enum ovsdb_client_wait_type {
|
||
WAIT_CONNECTED,
|
||
WAIT_ADDED,
|
||
WAIT_REMOVED
|
||
};
|
||
|
||
static struct jsonrpc_msg *
|
||
compose_wait_transaction(enum ovsdb_client_wait_type type,
|
||
const char *database)
|
||
{
|
||
struct json *txn = json_array_create_empty();
|
||
json_array_add(txn, json_string_create("_Server"));
|
||
|
||
struct json *op = json_object_create();
|
||
json_array_add(txn, op);
|
||
json_object_put_string(op, "op", "wait");
|
||
json_object_put_string(op, "table", "Database");
|
||
json_object_put(op, "where",
|
||
json_array_create_1(
|
||
json_array_create_3(
|
||
json_string_create("name"),
|
||
json_string_create("=="),
|
||
json_string_create(database))));
|
||
|
||
if (type == WAIT_CONNECTED) {
|
||
/* Wait until connected == true. */
|
||
json_object_put(op, "columns",
|
||
json_array_create_1(json_string_create("connected")));
|
||
json_object_put_string(op, "until", "==");
|
||
|
||
struct json *row = json_object_create();
|
||
json_object_put(row, "connected", json_boolean_create(true));
|
||
json_object_put(op, "rows", json_array_create_1(row));
|
||
} else {
|
||
ovs_assert(type == WAIT_ADDED || type == WAIT_REMOVED);
|
||
|
||
/* Wait until such a row exists, or not, respectively. */
|
||
json_object_put(op, "columns", json_array_create_empty());
|
||
json_object_put_string(op, "until", "==");
|
||
json_object_put(op, "rows",
|
||
(type == WAIT_ADDED
|
||
? json_array_create_1(json_object_create())
|
||
: json_array_create_empty()));
|
||
}
|
||
return jsonrpc_create_request("transact", txn, NULL);
|
||
}
|
||
|
||
static void
|
||
do_wait(struct jsonrpc *rpc_unused OVS_UNUSED,
|
||
const char *database_unused OVS_UNUSED,
|
||
int argc, char *argv[])
|
||
{
|
||
const char *database = argv[argc - 2];
|
||
const char *state = argv[argc - 1];
|
||
|
||
enum ovsdb_client_wait_type type;
|
||
if (!strcmp(state, "connected")) {
|
||
type = WAIT_CONNECTED;
|
||
} else if (!strcmp(state, "added")) {
|
||
type = WAIT_ADDED;
|
||
} else if (!strcmp(state, "removed")) {
|
||
type = WAIT_REMOVED;
|
||
} else {
|
||
ovs_fatal(0, "%s: unknown state", state);
|
||
}
|
||
|
||
char *remote = argc > 2 ? xstrdup(argv[0]) : default_remote();
|
||
struct jsonrpc_session *js = jsonrpc_session_open(remote, true);
|
||
free(remote);
|
||
|
||
unsigned int seqno = 0;
|
||
struct json *sdca_id = NULL;
|
||
struct json *txn_id = NULL;
|
||
for (;;) {
|
||
jsonrpc_session_run(js);
|
||
|
||
if (seqno != jsonrpc_session_get_seqno(js)
|
||
&& jsonrpc_session_is_connected(js)) {
|
||
seqno = jsonrpc_session_get_seqno(js);
|
||
|
||
/* Send set_db_change_aware request. */
|
||
struct jsonrpc_msg *rq = jsonrpc_create_request(
|
||
"set_db_change_aware",
|
||
json_array_create_1(json_boolean_create(true)),
|
||
NULL);
|
||
json_destroy(sdca_id);
|
||
sdca_id = json_clone(rq->id);
|
||
jsonrpc_session_send(js, rq);
|
||
|
||
/* Send transaction. */
|
||
rq = compose_wait_transaction(type, database);
|
||
json_destroy(txn_id);
|
||
txn_id = json_clone(rq->id);
|
||
jsonrpc_session_send(js, rq);
|
||
}
|
||
|
||
struct jsonrpc_msg *reply = jsonrpc_session_recv(js);
|
||
if (reply && reply->id) {
|
||
if (sdca_id && json_equal(sdca_id, reply->id)) {
|
||
if (reply->type == JSONRPC_ERROR) {
|
||
ovs_error(0, "%s: set_db_change_aware failed (%s)",
|
||
jsonrpc_session_get_name(js),
|
||
json_to_string(reply->error, 0));
|
||
jsonrpc_msg_destroy(reply);
|
||
exit(EXIT_FAILURE);
|
||
}
|
||
} else if (txn_id && json_equal(txn_id, reply->id)) {
|
||
check_transaction_reply(reply);
|
||
jsonrpc_msg_destroy(reply);
|
||
exit(0);
|
||
}
|
||
}
|
||
jsonrpc_msg_destroy(reply);
|
||
|
||
jsonrpc_session_recv_wait(js);
|
||
jsonrpc_session_wait(js);
|
||
poll_block();
|
||
}
|
||
}
|
||
|
||
/* Command handlers may take an optional server socket name (e.g. "unix:...")
|
||
* and an optional database name (e.g. Open_vSwitch) as their initial
|
||
* arguments. The NEED_* element indicates what a particular command needs.
|
||
* These optional arguments should not be included in min_args or max_args, and
|
||
* they are not included in the argc and argv arguments passed to the handler:
|
||
* the argv[0] passed to the handler is the first argument after the optional
|
||
* server socket name. */
|
||
static const struct ovsdb_client_command all_commands[] = {
|
||
{ "list-dbs", NEED_RPC, 0, 0, do_list_dbs },
|
||
{ "get-schema", NEED_DATABASE, 0, 0, do_get_schema },
|
||
{ "get-schema-version", NEED_DATABASE, 0, 0, do_get_schema_version },
|
||
{ "get-schema-cksum", NEED_DATABASE, 0, 0, do_get_schema_cksum },
|
||
{ "list-tables", NEED_DATABASE, 0, 0, do_list_tables },
|
||
{ "list-columns", NEED_DATABASE, 0, 1, do_list_columns },
|
||
{ "transact", NEED_NONE, 1, 2, do_transact },
|
||
{ "query", NEED_NONE, 1, 2, do_query },
|
||
{ "monitor", NEED_DATABASE, 1, INT_MAX, do_monitor },
|
||
{ "monitor-cond", NEED_DATABASE, 2, 3, do_monitor_cond },
|
||
{ "monitor-cond-since", NEED_DATABASE, 2, 4, do_monitor_cond_since },
|
||
{ "wait", NEED_NONE, 2, 3, do_wait },
|
||
{ "convert", NEED_NONE, 1, 2, do_convert },
|
||
{ "needs-conversion", NEED_NONE, 1, 2, do_needs_conversion },
|
||
{ "dump", NEED_DATABASE, 0, INT_MAX, do_dump },
|
||
{ "backup", NEED_DATABASE, 0, 0, do_backup },
|
||
{ "restore", NEED_DATABASE, 0, 0, do_restore },
|
||
{ "lock", NEED_RPC, 1, 1, do_lock_create },
|
||
{ "steal", NEED_RPC, 1, 1, do_lock_steal },
|
||
{ "unlock", NEED_RPC, 1, 1, do_lock_unlock },
|
||
{ "help", NEED_NONE, 0, INT_MAX, do_help },
|
||
|
||
{ NULL, 0, 0, 0, NULL },
|
||
};
|
||
|
||
static const struct ovsdb_client_command *get_all_commands(void)
|
||
{
|
||
return all_commands;
|
||
}
|