From: Ben Pfaff Date: Fri, 6 Nov 2009 23:35:34 +0000 (-0800) Subject: ovsdb: Add new ovsdb-client program. X-Git-Tag: v1.0.0~259^2~511 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=d0632593feacc7942fa1650baf07e1e0b8dd1676;p=sliver-openvswitch.git ovsdb: Add new ovsdb-client program. --- diff --git a/lib/jsonrpc.c b/lib/jsonrpc.c index cf2a49715..d298f0d59 100644 --- a/lib/jsonrpc.c +++ b/lib/jsonrpc.c @@ -261,6 +261,32 @@ jsonrpc_recv_block(struct jsonrpc *rpc, struct jsonrpc_msg **msgp) } } +int +jsonrpc_transact_block(struct jsonrpc *rpc, struct jsonrpc_msg *request, + struct jsonrpc_msg **replyp) +{ + struct jsonrpc_msg *reply = NULL; + struct json *id; + int error; + + id = json_clone(request->id); + error = jsonrpc_send_block(rpc, request); + if (!error) { + for (;;) { + error = jsonrpc_recv_block(rpc, &reply); + if (error + || (reply->type == JSONRPC_REPLY + && json_equal(id, reply->id))) { + break; + } + jsonrpc_msg_destroy(reply); + } + } + *replyp = error ? NULL : reply; + json_destroy(id); + return error; +} + static void jsonrpc_received(struct jsonrpc *rpc) { diff --git a/lib/jsonrpc.h b/lib/jsonrpc.h index 931983ff6..e6d11ac51 100644 --- a/lib/jsonrpc.h +++ b/lib/jsonrpc.h @@ -46,6 +46,8 @@ void jsonrpc_recv_wait(struct jsonrpc *); int jsonrpc_send_block(struct jsonrpc *, struct jsonrpc_msg *); int jsonrpc_recv_block(struct jsonrpc *, struct jsonrpc_msg **); +int jsonrpc_transact_block(struct jsonrpc *, struct jsonrpc_msg *, + struct jsonrpc_msg **); /* Messages. */ enum jsonrpc_msg_type { diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index d5a59ab0c..924f556b9 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -55,6 +55,7 @@ VLOG_MODULE(ovs_discover) VLOG_MODULE(ofproto) VLOG_MODULE(openflowd) VLOG_MODULE(ovsdb) +VLOG_MODULE(ovsdb_client) VLOG_MODULE(ovsdb_file) VLOG_MODULE(ovsdb_jsonrpc_server) VLOG_MODULE(ovsdb_server) diff --git a/ovsdb/automake.mk b/ovsdb/automake.mk index 5670281bf..04fa52da1 100644 --- a/ovsdb/automake.mk +++ b/ovsdb/automake.mk @@ -33,6 +33,15 @@ man_MANS += ovsdb/ovsdb-tool.1 DISTCLEANFILES += ovsdb/ovsdb-tool.1 EXTRA_DIST += ovsdb/ovsdb-tool.1.in +# ovsdb-client +bin_PROGRAMS += ovsdb/ovsdb-client +ovsdb_ovsdb_client_SOURCES = ovsdb/ovsdb-client.c +ovsdb_ovsdb_client_LDADD = ovsdb/libovsdb.a lib/libopenvswitch.a +# ovsdb-client.1 +man_MANS += ovsdb/ovsdb-client.1 +DISTCLEANFILES += ovsdb/ovsdb-client.1 +EXTRA_DIST += ovsdb/ovsdb-client.1.in + # ovsdb-server sbin_PROGRAMS += ovsdb/ovsdb-server ovsdb_ovsdb_server_SOURCES = ovsdb/ovsdb-server.c diff --git a/ovsdb/ovsdb-client.1.in b/ovsdb/ovsdb-client.1.in new file mode 100644 index 000000000..8bdaae81f --- /dev/null +++ b/ovsdb/ovsdb-client.1.in @@ -0,0 +1,90 @@ +.\" -*- nroff -*- +.de IQ +. br +. ns +. IP "\\$1" +.. +.\" -*- nroff -*- +.TH ovsdb\-client 1 "November 2009" "Open vSwitch" "Open vSwitch Manual" +.ds PN ovsdb\-client +. +.SH NAME +ovsdb\-client \- command-line interface to \fBovsdb-server\fR(1) +. +.SH SYNOPSIS +\fBovsdb\-client \fR[\fIoptions\fR] \fBget-schema\fI server\fR +.br +\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-tables\fI server\fR +.br +\fBovsdb\-client \fR[\fIoptions\fR] \fBlist-columns\fI server \fR[\fItable\fR] +.br +\fBovsdb\-client help\fR +.IP "Output formatting options:" +[\fB--format=\fIformat\fR] +[\fB--wide\fR] +[\fB--no-heading\fR] +.so lib/vlog-syn.man +.so lib/common-syn.man +. +.SH DESCRIPTION +The \fBovsdb\-client\fR program is a command-line client for +interacting with a running \fBovsdb\-server\fR process. For each +command, the \fIserver\fR to connect to must be specified in one of +the following forms: +.IP "\fBtcp:\fIip\fB:\fIport\fR" +Connect to the given TCP \fIport\fR on \fIip\fR. +.IP "\fBunix:\fIfile\fR" +Connect to the Unix domain server socket named \fIfile\fR. +. +.SS "Commands" +The following commands are implemented: +.IP "\fBget-schema\fI server\fR" +Connects to \fIserver\fR, retrieves the database schema, and prints it +in JSON format. +. +.IP "\fBlist-tables\fI server\fR" +Connects to \fIserver\fR, retrieves the database schema, and prints +a table listing the names and comments (if any) on each table within +the database. +. +.IP "\fBlist-columns\fI server \fR[\fItable\fR]" +Connects to \fIserver\fR, retrieves the database schema, and prints +a table listing the names, type, and comment (if any) on each column. If +\fItable\fR is specified, only columns in that table are listed; +otherwise, the tables include columns in all tables. +.SH OPTIONS +.SS "Output Formatting Options" +Much of the output from \fBovsdb\-client\fR is in the form of tables. +The following options controlling output formatting: +. +.IP "\fB-f \fIformat\fR" +.IQ "\fB--format=\fIformat\fR" +Sets the basic type of output formatting. The following types of +\fIformat\fR are available: +.RS +.IP "\fBtable\fR (default)" +Text-based tables with aligned columns. +.IP "\fBhtml\fR" +HTML tables. +.IP "\fBcvs\fR" +Comma-separated values as defined in RFC 4180. +.RE +. +.IP "\fB--wide\fR" +In \fBtable\fR output (the default), when standard output is a +terminal device, by default lines are truncated at a width of 79 +characters. Specifying this option prevents line truncation. +. +.IP "\fB--no-heading\fR" +This option suppresses the heading row that otherwise appears in the +first row of table output. +. +.SS "Logging Options" +.so lib/vlog.man +.SS "Other Options" +.so lib/common.man +.SH "SEE ALSO" +. +\fBovsdb\-server\fR(1), +\fBovsdb\-client\fR(1), +and the OVSDB specification. diff --git a/ovsdb/ovsdb-client.c b/ovsdb/ovsdb-client.c new file mode 100644 index 000000000..10b27ce65 --- /dev/null +++ b/ovsdb/ovsdb-client.c @@ -0,0 +1,576 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * 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 + +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "column.h" +#include "compiler.h" +#include "dynamic-string.h" +#include "json.h" +#include "jsonrpc.h" +#include "ovsdb.h" +#include "ovsdb-error.h" +#include "stream.h" +#include "table.h" +#include "timeval.h" +#include "util.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ovsdb_client + +/* --format: Output formatting. */ +static enum { + FMT_TABLE, /* Textual table. */ + FMT_HTML, /* HTML table. */ + FMT_CSV /* Comma-separated lines. */ +} output_format; + +/* --wide: For --format=table, the maximum output width. */ +static int output_width; + +/* --no-headings: Whether table output should include headings. */ +static int output_headings = true; + +static const struct command all_commands[]; + +static void usage(void) NO_RETURN; +static void parse_options(int argc, char *argv[]); + +int +main(int argc, char *argv[]) +{ + set_program_name(argv[0]); + time_init(); + vlog_init(); + parse_options(argc, argv); + signal(SIGPIPE, SIG_IGN); + run_command(argc - optind, argv + optind, all_commands); + return 0; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"wide", no_argument, &output_width, INT_MAX}, + {"format", required_argument, 0, 'f'}, + {"no-headings", no_argument, &output_headings, 0}, + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + output_width = isatty(fileno(stdout)) ? 79 : INT_MAX; + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case 'f': + if (!strcmp(optarg, "table")) { + output_format = FMT_TABLE; + } else if (!strcmp(optarg, "html")) { + output_format = FMT_HTML; + } else if (!strcmp(optarg, "csv")) { + output_format = FMT_CSV; + } else { + ovs_fatal(0, "unknown output format \"%s\"", optarg); + } + break; + + case 'w': + output_width = INT_MAX; + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(0, 0); + exit(EXIT_SUCCESS); + + case 'v': + vlog_set_verbosity(optarg); + break; + + case '?': + exit(EXIT_FAILURE); + + case 0: + /* getopt_long() already set the value for us. */ + break; + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: Open vSwitch database JSON-RPC client\n" + "usage: %s [OPTIONS] COMMAND [ARG...]\n" + "\nValid commands are:\n" + "\n get-schema SERVER\n" + " retrieve schema from SERVER\n" + "\n list-tables SERVER\n" + " list SERVER's tables\n" + "\n list-columns SERVER [TABLE]\n" + " list columns in TABLE (or all tables) on SERVER\n", + program_name, program_name); + stream_usage("SERVER", true, false); + printf("\nOutput formatting options:\n" + " -f, --format=FORMAT set output formatting to FORMAT\n" + " (\"table\", \"html\", or \"csv\"\n" + " --wide don't limit TTY lines to 79 bytes\n" + " --no-headings omit table heading row\n"); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + exit(EXIT_SUCCESS); +} + +static struct jsonrpc * +open_jsonrpc(const char *server) +{ + struct stream *stream; + int error; + + error = stream_open_block(server, &stream); + 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, JSSF_SORT); + fputs(string, stdout); + 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(error)); + } +} + +static struct ovsdb_schema * +fetch_schema(const char *server) +{ + struct jsonrpc_msg *request, *reply; + struct ovsdb_schema *schema; + struct jsonrpc *rpc; + int error; + + rpc = open_jsonrpc(server); + request = jsonrpc_create_request("get_schema", json_array_create_empty()); + error = jsonrpc_transact_block(rpc, request, &reply); + if (error) { + ovs_fatal(error, "transaction failed"); + } + check_ovsdb_error(ovsdb_schema_from_json(reply->result, &schema)); + jsonrpc_msg_destroy(reply); + jsonrpc_close(rpc); + + return schema; +} + +struct column { + char *heading; + int width; +}; + +struct table { + char **cells; + struct column *columns; + size_t n_columns, allocated_columns; + size_t n_rows, allocated_rows; + size_t current_column; +}; + +static void +table_init(struct table *table) +{ + memset(table, 0, sizeof *table); +} + +static void +table_add_column(struct table *table, const char *heading, ...) + PRINTF_FORMAT(2, 3); + +static void +table_add_column(struct table *table, const char *heading, ...) +{ + struct column *column; + va_list args; + + assert(!table->n_rows); + if (table->n_columns >= table->allocated_columns) { + table->columns = x2nrealloc(table->columns, &table->allocated_columns, + sizeof *table->columns); + } + column = &table->columns[table->n_columns++]; + + va_start(args, heading); + column->heading = xvasprintf(heading, args); + column->width = strlen(column->heading); + va_end(args); +} + +static char ** +table_cell__(const struct table *table, size_t row, size_t column) +{ + return &table->cells[column + row * table->n_columns]; +} + +static void +table_add_row(struct table *table) +{ + size_t x, y; + + if (table->n_rows >= table->allocated_rows) { + table->cells = x2nrealloc(table->cells, &table->allocated_rows, + table->n_columns * sizeof *table->cells); + } + + y = table->n_rows++; + table->current_column = 0; + for (x = 0; x < table->n_columns; x++) { + *table_cell__(table, y, x) = NULL; + } +} + +static void +table_add_cell_nocopy(struct table *table, char *s) +{ + size_t x, y; + int length; + + assert(table->n_rows > 0); + assert(table->current_column < table->n_columns); + + x = table->current_column++; + y = table->n_rows - 1; + *table_cell__(table, y, x) = s; + + length = strlen(s); + if (length > table->columns[x].width) { + table->columns[x].width = length; + } +} + +static void +table_add_cell(struct table *table, const char *format, ...) +{ + va_list args; + + va_start(args, format); + table_add_cell_nocopy(table, xvasprintf(format, args)); + va_end(args); +} + +static void +table_print_table_line__(struct ds *line, size_t max_width) +{ + ds_truncate(line, max_width); + puts(ds_cstr(line)); + ds_clear(line); +} + +static void +table_print_table__(const struct table *table) +{ + struct ds line = DS_EMPTY_INITIALIZER; + size_t x, y; + + if (output_headings) { + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + if (x) { + ds_put_char(&line, ' '); + } + ds_put_format(&line, "%-*s", column->width, column->heading); + } + table_print_table_line__(&line, output_width); + + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + int i; + + if (x) { + ds_put_char(&line, ' '); + } + for (i = 0; i < column->width; i++) { + ds_put_char(&line, '-'); + } + } + table_print_table_line__(&line, output_width); + } + + for (y = 0; y < table->n_rows; y++) { + for (x = 0; x < table->n_columns; x++) { + const char *cell = *table_cell__(table, y, x); + if (x) { + ds_put_char(&line, ' '); + } + ds_put_format(&line, "%-*s", table->columns[x].width, cell); + } + table_print_table_line__(&line, output_width); + } + + ds_destroy(&line); +} + +static void +table_print_html_cell__(const char *element, const char *content) +{ + const char *p; + + printf(" <%s>", element); + for (p = content; *p != '\0'; p++) { + switch (*p) { + case '&': + fputs("&", stdout); + break; + case '<': + fputs("<", stdout); + break; + case '>': + fputs(">", stdout); + break; + default: + putchar(*p); + break; + } + } + printf("\n", element); +} + +static void +table_print_html__(const struct table *table) +{ + size_t x, y; + + fputs("\n", stdout); + + if (output_headings) { + fputs(" \n", stdout); + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + table_print_html_cell__("th", column->heading); + } + fputs(" \n", stdout); + } + + for (y = 0; y < table->n_rows; y++) { + fputs(" \n", stdout); + for (x = 0; x < table->n_columns; x++) { + table_print_html_cell__("td", *table_cell__(table, y, x)); + } + fputs(" \n", stdout); + } + + fputs("
\n", stdout); +} + +static void +table_print_csv_cell__(const char *content) +{ + const char *p; + + if (!strpbrk(content, "\n\",")) { + fputs(content, stdout); + } else { + putchar('"'); + for (p = content; *p != '\0'; p++) { + switch (*p) { + case '"': + fputs("\"\"", stdout); + break; + default: + putchar(*p); + break; + } + } + putchar('"'); + } +} + +static void +table_print_csv__(const struct table *table) +{ + size_t x, y; + + if (output_headings) { + for (x = 0; x < table->n_columns; x++) { + const struct column *column = &table->columns[x]; + if (x) { + putchar(','); + } + table_print_csv_cell__(column->heading); + } + putchar('\n'); + } + + for (y = 0; y < table->n_rows; y++) { + for (x = 0; x < table->n_columns; x++) { + if (x) { + putchar(','); + } + table_print_csv_cell__(*table_cell__(table, y, x)); + } + putchar('\n'); + } +} + +static void +table_print(const struct table *table) +{ + switch (output_format) { + case FMT_TABLE: + table_print_table__(table); + break; + + case FMT_HTML: + table_print_html__(table); + break; + + case FMT_CSV: + table_print_csv__(table); + break; + } +} + +static void +do_get_schema(int argc UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema = fetch_schema(argv[1]); + print_and_free_json(ovsdb_schema_to_json(schema)); + ovsdb_schema_destroy(schema); +} + +static void +do_list_tables(int argc UNUSED, char *argv[]) +{ + struct ovsdb_schema *schema; + struct shash_node *node; + struct table t; + + schema = fetch_schema(argv[1]); + table_init(&t); + table_add_column(&t, "Table"); + table_add_column(&t, "Comment"); + SHASH_FOR_EACH (node, &schema->tables) { + struct ovsdb_table_schema *ts = node->data; + + table_add_row(&t); + table_add_cell(&t, ts->name); + if (ts->comment) { + table_add_cell(&t, ts->comment); + } + } + ovsdb_schema_destroy(schema); + table_print(&t); +} + +static void +do_list_columns(int argc UNUSED, char *argv[]) +{ + const char *table_name = argv[2]; + struct ovsdb_schema *schema; + struct shash_node *table_node; + struct table t; + + schema = fetch_schema(argv[1]); + table_init(&t); + if (!table_name) { + table_add_column(&t, "Table"); + } + table_add_column(&t, "Column"); + table_add_column(&t, "Type"); + table_add_column(&t, "Comment"); + 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) { + struct ovsdb_column *column = column_node->data; + struct json *type = ovsdb_type_to_json(&column->type); + + table_add_row(&t); + if (!table_name) { + table_add_cell(&t, ts->name); + } + table_add_cell(&t, column->name); + table_add_cell_nocopy(&t, json_to_string(type, JSSF_SORT)); + if (column->comment) { + table_add_cell(&t, column->comment); + } + + json_destroy(type); + } + } + } + ovsdb_schema_destroy(schema); + table_print(&t); +} + +static void +do_help(int argc UNUSED, char *argv[] UNUSED) +{ + usage(); +} + +static const struct command all_commands[] = { + { "get-schema", 1, 1, do_get_schema }, + { "list-tables", 1, 1, do_list_tables }, + { "list-columns", 1, 2, do_list_columns }, + { "help", 0, INT_MAX, do_help }, + { NULL, 0, 0, NULL }, +};