@@ -27,6 +27,8 @@ Post-v3.2.0
"ovs-appctl dpctl/ct-del-limits default".
* 'dpctl/flush-conntrack' is now capable of flushing connections based
on mark and labels.
+ * Added new option [-f|--format] to choose the output format, e.g. 'json'
+ or 'text' (by default).
- ovs-vsctl:
* New commands 'set-zone-limit', 'del-zone-limit' and 'list-zone-limits'
to manage the maximum number of connections in conntrack zones via
@@ -24,6 +24,7 @@
#include "ovs-thread.h"
#include "util.h"
#include "openvswitch/vlog.h"
+#include "openvswitch/json.h"
VLOG_DEFINE_THIS_MODULE(command_line);
@@ -53,6 +54,41 @@ ovs_cmdl_long_options_to_short_options(const struct option options[])
return xstrdup(short_options);
}
+const char *
+ovs_output_fmt_to_string(enum ovs_output_fmt fmt)
+{
+ switch (fmt) {
+ case OVS_OUTPUT_FMT_TEXT:
+ return "text";
+
+ case OVS_OUTPUT_FMT_JSON:
+ return "json";
+
+ default:
+ return NULL;
+ }
+}
+
+struct json *
+ovs_output_fmt_to_json(enum ovs_output_fmt fmt)
+{
+ const char *string = ovs_output_fmt_to_string(fmt);
+ return string ? json_string_create(string) : NULL;
+}
+
+bool
+ovs_output_fmt_from_string(const char *string, enum ovs_output_fmt *fmt)
+{
+ if (!strcmp(string, "text")) {
+ *fmt = OVS_OUTPUT_FMT_TEXT;
+ } else if (!strcmp(string, "json")) {
+ *fmt = OVS_OUTPUT_FMT_JSON;
+ } else {
+ return false;
+ }
+ return true;
+}
+
static char * OVS_WARN_UNUSED_RESULT
build_short_options(const struct option *long_options)
{
@@ -20,6 +20,7 @@
/* Utilities for command-line parsing. */
#include "compiler.h"
+#include <openvswitch/list.h>
struct option;
@@ -46,6 +47,15 @@ struct ovs_cmdl_command {
char *ovs_cmdl_long_options_to_short_options(const struct option *options);
+enum ovs_output_fmt {
+ OVS_OUTPUT_FMT_TEXT = 1 << 0,
+ OVS_OUTPUT_FMT_JSON = 1 << 1
+};
+
+const char *ovs_output_fmt_to_string(enum ovs_output_fmt);
+struct json *ovs_output_fmt_to_json(enum ovs_output_fmt);
+bool ovs_output_fmt_from_string(const char *, enum ovs_output_fmt *);
+
struct ovs_cmdl_parsed_option {
const struct option *o;
char *arg;
@@ -19,6 +19,7 @@
#include <stdbool.h>
#include "compiler.h"
+#include "command-line.h"
struct dpctl_params {
/* True if it is called by ovs-appctl command. */
@@ -42,6 +43,9 @@ struct dpctl_params {
/* --names: Use port names in output? */
bool names;
+ /* Output format. */
+ enum ovs_output_fmt format;
+
/* Callback for printing. This function is called from dpctl_run_command()
* to output data. The 'aux' parameter is set to the 'aux'
* member. The 'error' parameter is true if 'string' is an error
@@ -21,7 +21,6 @@
#include "coverage.h"
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
#include "jsonrpc.h"
#include "openvswitch/list.h"
#include "openvswitch/poll-loop.h"
@@ -39,6 +38,7 @@ COVERAGE_DEFINE(unixctl_replied);
struct unixctl_command {
const char *usage;
int min_args, max_args;
+ int output_fmts;
unixctl_cb_func *cb;
void *aux;
};
@@ -50,6 +50,8 @@ struct unixctl_conn {
/* Only one request can be in progress at a time. While the request is
* being processed, 'request_id' is populated, otherwise it is null. */
struct json *request_id; /* ID of the currently active request. */
+
+ enum ovs_output_fmt fmt;
};
/* Server for control connection. */
@@ -94,6 +96,39 @@ unixctl_version(struct unixctl_conn *conn, int argc OVS_UNUSED,
unixctl_command_reply(conn, ovs_get_program_version());
}
+static void
+unixctl_set_options(struct unixctl_conn *conn, int argc, const char *argv[],
+ void *aux OVS_UNUSED)
+{
+ char * error = NULL;
+
+ for (size_t i = 1; i < argc; i++) {
+ if ((i + 1) == argc) {
+ error = xasprintf("option requires an argument -- %s", argv[i]);
+ goto error;
+ } else if (!strcmp(argv[i], "--format")) {
+ /* Move index to option argument. */
+ i++;
+
+ /* Parse output format argument. */
+ if (!ovs_output_fmt_from_string(argv[i], &conn->fmt)) {
+ error = xasprintf("option %s has invalid value %s",
+ argv[i - 1], argv[i]);
+ goto error;
+ }
+ } else {
+ error = xasprintf("invalid option -- %s", argv[i]);
+ goto error;
+
+ }
+ }
+ unixctl_command_reply(conn, NULL);
+ return;
+error:
+ unixctl_command_reply_error(conn, error);
+ free(error);
+}
+
/* Registers a unixctl command with the given 'name'. 'usage' describes the
* arguments to the command; it is used only for presentation to the user in
* "list-commands" output. (If 'usage' is NULL, then the command is hidden.)
@@ -109,6 +144,28 @@ void
unixctl_command_register(const char *name, const char *usage,
int min_args, int max_args,
unixctl_cb_func *cb, void *aux)
+{
+ unixctl_command_register_fmt(name, usage, min_args, max_args,
+ OVS_OUTPUT_FMT_TEXT, cb, aux);
+}
+
+/* Registers a unixctl command with the given 'name'. 'usage' describes the
+ * arguments to the command; it is used only for presentation to the user in
+ * "list-commands" output. (If 'usage' is NULL, then the command is hidden.)
+ * 'output_fmts' is a bitmap that defines what output formats a command
+ * supports, e.g. OVS_OUTPUT_FMT_TEXT | OVS_OUTPUT_FMT_JSON.
+ *
+ * 'cb' is called when the command is received. It is passed an array
+ * containing the command name and arguments, plus a copy of 'aux'. Normally
+ * 'cb' should reply by calling unixctl_command_reply() or
+ * unixctl_command_reply_error() before it returns, but if the command cannot
+ * be handled immediately then it can defer the reply until later. A given
+ * connection can only process a single request at a time, so a reply must be
+ * made eventually to avoid blocking that connection. */
+void
+unixctl_command_register_fmt(const char *name, const char *usage,
+ int min_args, int max_args, int output_fmts,
+ unixctl_cb_func *cb, void *aux)
{
struct unixctl_command *command;
struct unixctl_command *lookup = shash_find_data(&commands, name);
@@ -123,41 +180,48 @@ unixctl_command_register(const char *name, const char *usage,
command->usage = usage;
command->min_args = min_args;
command->max_args = max_args;
+ command->output_fmts = output_fmts;
command->cb = cb;
command->aux = aux;
shash_add(&commands, name, command);
}
-static void
-unixctl_command_reply__(struct unixctl_conn *conn,
- bool success, const char *body)
+static struct json *
+json_string_create__(const char *body)
{
- struct json *body_json;
- struct jsonrpc_msg *reply;
-
- COVERAGE_INC(unixctl_replied);
- ovs_assert(conn->request_id);
-
if (!body) {
body = "";
}
if (body[0] && body[strlen(body) - 1] != '\n') {
- body_json = json_string_create_nocopy(xasprintf("%s\n", body));
+ return json_string_create_nocopy(xasprintf("%s\n", body));
} else {
- body_json = json_string_create(body);
+ return json_string_create(body);
}
+}
+
+/* Takes ownership of 'body'. */
+static void
+unixctl_command_reply__(struct unixctl_conn *conn,
+ bool success, struct json *body)
+{
+ struct jsonrpc_msg *reply;
+
+ COVERAGE_INC(unixctl_replied);
+ ovs_assert(conn->request_id);
if (success) {
- reply = jsonrpc_create_reply(body_json, conn->request_id);
+ reply = jsonrpc_create_reply(body, conn->request_id);
} else {
- reply = jsonrpc_create_error(body_json, conn->request_id);
+ reply = jsonrpc_create_error(body, conn->request_id);
}
if (VLOG_IS_DBG_ENABLED()) {
char *id = json_to_string(conn->request_id, 0);
+ char *msg = json_to_string(body, 0);
VLOG_DBG("replying with %s, id=%s: \"%s\"",
- success ? "success" : "error", id, body);
+ success ? "success" : "error", id, msg);
+ free(msg);
free(id);
}
@@ -170,22 +234,34 @@ unixctl_command_reply__(struct unixctl_conn *conn,
/* Replies to the active unixctl connection 'conn'. 'result' is sent to the
* client indicating the command was processed successfully. Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request. */
void
unixctl_command_reply(struct unixctl_conn *conn, const char *result)
{
- unixctl_command_reply__(conn, true, result);
+ unixctl_command_reply__(conn, true, json_string_create__(result));
}
/* Replies to the active unixctl connection 'conn'. 'error' is sent to the
- * client indicating an error occurred processing the command. Only one call to
- * unixctl_command_reply() or unixctl_command_reply_error() may be made per
- * request. */
+ * client indicating an error occurred processing the command. Only one call
+ * to unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request. */
void
unixctl_command_reply_error(struct unixctl_conn *conn, const char *error)
{
- unixctl_command_reply__(conn, false, error);
+ unixctl_command_reply__(conn, false, json_string_create__(error));
+}
+
+/* Replies to the active unixctl connection 'conn'. 'result' is sent to the
+ * client indicating the command was processed successfully. Only one call to
+ * unixctl_command_reply(), unixctl_command_reply_error() or
+ * unixctl_command_reply_json() may be made per request.
+ *
+ * Takes ownership of 'body'. */
+void
+unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body)
+{
+ unixctl_command_reply__(conn, true, body);
}
/* Creates a unixctl server listening on 'path', which for POSIX may be:
@@ -250,6 +326,8 @@ unixctl_server_create(const char *path, struct unixctl_server **serverp)
unixctl_command_register("list-commands", "", 0, 0, unixctl_list_commands,
NULL);
unixctl_command_register("version", "", 0, 0, unixctl_version, NULL);
+ unixctl_command_register("set-options", "[--format text|json]", 2, 2,
+ unixctl_set_options, NULL);
struct unixctl_server *server = xmalloc(sizeof *server);
server->listener = listener;
@@ -291,6 +369,18 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
} else if (params->n > command->max_args) {
error = xasprintf("\"%s\" command takes at most %d arguments",
request->method, command->max_args);
+ } else if ((!command->output_fmts && conn->fmt != OVS_OUTPUT_FMT_TEXT) ||
+ (command->output_fmts && !(conn->fmt & command->output_fmts)))
+ {
+ error = xasprintf("\"%s\" command does not support output format"\
+ " \"%s\" (supported: %d, requested: %d)",
+ request->method, ovs_output_fmt_to_string(conn->fmt),
+ command->output_fmts, conn->fmt);
+ } else if (conn->fmt != OVS_OUTPUT_FMT_TEXT) {
+ /* FIXME: Remove this check once output format will be passed to the
+ * command handler below. */
+ error = xasprintf("output format \"%s\" has not been implemented yet",
+ ovs_output_fmt_to_string(conn->fmt));
} else {
struct svec argv = SVEC_EMPTY_INITIALIZER;
int i;
@@ -307,8 +397,10 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request)
svec_terminate(&argv);
if (!error) {
+ /* FIXME: Output format will be passed as 'fmt' to the command in
+ * later patch. */
command->cb(conn, argv.n, (const char **) argv.names,
- command->aux);
+ /* conn->fmt, */ command->aux);
}
svec_destroy(&argv);
@@ -381,6 +473,7 @@ unixctl_server_run(struct unixctl_server *server)
struct unixctl_conn *conn = xzalloc(sizeof *conn);
ovs_list_push_back(&server->conns, &conn->node);
conn->rpc = jsonrpc_open(stream);
+ conn->fmt = OVS_OUTPUT_FMT_TEXT;
} else if (error == EAGAIN) {
break;
} else {
@@ -518,6 +611,9 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
} else if (reply->result) {
if (reply->result->type == JSON_STRING) {
*result = xstrdup(json_string(reply->result));
+ } else if (reply->result->type == JSON_OBJECT ||
+ reply->result->type == JSON_ARRAY) {
+ *result = json_to_string(reply->result, 0);
} else {
VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s",
jsonrpc_get_name(client),
@@ -17,6 +17,9 @@
#ifndef UNIXCTL_H
#define UNIXCTL_H 1
+#include "openvswitch/json.h"
+#include "command-line.h"
+
#ifdef __cplusplus
extern "C" {
#endif
@@ -40,13 +43,24 @@ int unixctl_client_transact(struct jsonrpc *client,
/* Command registration. */
struct unixctl_conn;
+/* FIXME: Output format will be passed as 'fmt' to the command in later patch.
+ */
typedef void unixctl_cb_func(struct unixctl_conn *,
- int argc, const char *argv[], void *aux);
+ int argc, const char *argv[],
+ /* enum ovs_output_fmt fmt, */ void *aux);
+/* FIXME: unixctl_command_register() will be replaced with
+ * unixctl_command_register_fmt() in a later patch of this series. It
+ * is kept temporarily to reduce the amount of changes in this patch. */
void unixctl_command_register(const char *name, const char *usage,
int min_args, int max_args,
unixctl_cb_func *cb, void *aux);
+void unixctl_command_register_fmt(const char *name, const char *usage,
+ int min_args, int max_args, int output_fmts,
+ unixctl_cb_func *cb, void *aux);
void unixctl_command_reply_error(struct unixctl_conn *, const char *error);
void unixctl_command_reply(struct unixctl_conn *, const char *body);
+void unixctl_command_reply_json(struct unixctl_conn *,
+ struct json *body);
#ifdef __cplusplus
}
@@ -29,12 +29,22 @@
#include "jsonrpc.h"
#include "process.h"
#include "timeval.h"
+#include "svec.h"
#include "unixctl.h"
#include "util.h"
#include "openvswitch/vlog.h"
static void usage(void);
-static const char *parse_command_line(int argc, char *argv[]);
+
+/* Parsed command line args. */
+struct cmdl_args {
+ enum ovs_output_fmt format;
+ char *target;
+};
+
+static struct cmdl_args *cmdl_args_create(void);
+static void cmdl_args_destroy(struct cmdl_args *);
+static struct cmdl_args *parse_command_line(int argc, char *argv[]);
static struct jsonrpc *connect_to_target(const char *target);
int
@@ -43,30 +53,59 @@ main(int argc, char *argv[])
char *cmd_result, *cmd_error;
struct jsonrpc *client;
char *cmd, **cmd_argv;
- const char *target;
+ struct cmdl_args *args;
int cmd_argc;
int error;
+ struct svec opt_argv = SVEC_EMPTY_INITIALIZER;
set_program_name(argv[0]);
/* Parse command line and connect to target. */
- target = parse_command_line(argc, argv);
- client = connect_to_target(target);
+ args = parse_command_line(argc, argv);
+ client = connect_to_target(args->target);
+
+ /* Transact options request (if required) and process reply */
+ if (args->format != OVS_OUTPUT_FMT_TEXT) {
+ svec_add(&opt_argv, "--format");
+ svec_add(&opt_argv, ovs_output_fmt_to_string(args->format));
+ }
+ svec_terminate(&opt_argv);
+
+ if (opt_argv.n > 0) {
+ error = unixctl_client_transact(client, "set-options",
+ opt_argv.n, opt_argv.names,
+ &cmd_result, &cmd_error);
+
+ if (error) {
+ ovs_fatal(error, "%s: transaction error", args->target);
+ }
- /* Transact request and process reply. */
+ if (cmd_error) {
+ jsonrpc_close(client);
+ fputs(cmd_error, stderr);
+ ovs_error(0, "%s: server returned an error", args->target);
+ exit(2);
+ }
+
+ free(cmd_result);
+ free(cmd_error);
+ }
+ svec_destroy(&opt_argv);
+
+ /* Transact command request and process reply. */
cmd = argv[optind++];
cmd_argc = argc - optind;
cmd_argv = cmd_argc ? argv + optind : NULL;
error = unixctl_client_transact(client, cmd, cmd_argc, cmd_argv,
&cmd_result, &cmd_error);
if (error) {
- ovs_fatal(error, "%s: transaction error", target);
+ ovs_fatal(error, "%s: transaction error", args->target);
}
if (cmd_error) {
jsonrpc_close(client);
fputs(cmd_error, stderr);
- ovs_error(0, "%s: server returned an error", target);
+ ovs_error(0, "%s: server returned an error", args->target);
exit(2);
} else if (cmd_result) {
fputs(cmd_result, stdout);
@@ -74,6 +113,7 @@ main(int argc, char *argv[])
OVS_NOT_REACHED();
}
+ cmdl_args_destroy(args);
jsonrpc_close(client);
free(cmd_result);
free(cmd_error);
@@ -101,13 +141,34 @@ Common commands:\n\
vlog/reopen Make the program reopen its log file\n\
Other options:\n\
--timeout=SECS wait at most SECS seconds for a response\n\
+ -f, --format=FMT Output format. One of: 'json', or 'text'\n\
+ ('text', by default)\n\
-h, --help Print this helpful information\n\
-V, --version Display ovs-appctl version information\n",
program_name, program_name);
exit(EXIT_SUCCESS);
}
-static const char *
+static struct cmdl_args *
+cmdl_args_create(void) {
+ struct cmdl_args *args = xmalloc(sizeof *args);
+
+ args->format = OVS_OUTPUT_FMT_TEXT;
+ args->target = NULL;
+
+ return args;
+}
+
+static void
+cmdl_args_destroy(struct cmdl_args *args) {
+ if (args->target) {
+ free(args->target);
+ }
+
+ free(args);
+}
+
+static struct cmdl_args *
parse_command_line(int argc, char *argv[])
{
enum {
@@ -117,6 +178,7 @@ parse_command_line(int argc, char *argv[])
static const struct option long_options[] = {
{"target", required_argument, NULL, 't'},
{"execute", no_argument, NULL, 'e'},
+ {"format", required_argument, NULL, 'f'},
{"help", no_argument, NULL, 'h'},
{"option", no_argument, NULL, 'o'},
{"version", no_argument, NULL, 'V'},
@@ -126,11 +188,11 @@ parse_command_line(int argc, char *argv[])
};
char *short_options_ = ovs_cmdl_long_options_to_short_options(long_options);
char *short_options = xasprintf("+%s", short_options_);
- const char *target;
+
+ struct cmdl_args *args = cmdl_args_create();
int e_options;
unsigned int timeout = 0;
- target = NULL;
e_options = 0;
for (;;) {
int option;
@@ -141,10 +203,10 @@ parse_command_line(int argc, char *argv[])
}
switch (option) {
case 't':
- if (target) {
+ if (args->target) {
ovs_fatal(0, "-t or --target may be specified only once");
}
- target = optarg;
+ args->target = xstrdup(optarg);
break;
case 'e':
@@ -157,6 +219,12 @@ parse_command_line(int argc, char *argv[])
}
break;
+ case 'f':
+ if (!ovs_output_fmt_from_string(optarg, &args->format)) {
+ ovs_fatal(0, "value %s on -f or --format is invalid", optarg);
+ }
+ break;
+
case 'h':
usage();
break;
@@ -194,7 +262,10 @@ parse_command_line(int argc, char *argv[])
"(use --help for help)");
}
- return target ? target : "ovs-vswitchd";
+ if (!args->target) {
+ args->target = xstrdup("ovs-vswitchd");
+ }
+ return args;
}
static struct jsonrpc *
@@ -63,6 +63,7 @@ main(int argc, char *argv[])
fatal_ignore_sigpipe();
dpctl_p.is_appctl = false;
+ dpctl_p.format = OVS_OUTPUT_FMT_TEXT;
dpctl_p.output = dpctl_print;
dpctl_p.usage = usage;