@@ -8,6 +8,7 @@ Synopsis
``ovs-appctl``
[``--target=``<target> | ``-t`` <target>]
[``--timeout=``<secs> | ``-T`` <secs>]
+[``--format=``<format> | ``-f`` <format>]
<command> [<arg>...]
``ovs-appctl --help``
@@ -67,6 +68,17 @@ In normal use only a single option is accepted:
runtime to approximately <secs> seconds. If the timeout expires,
``ovs-appctl`` exits with a ``SIGALRM`` signal.
+* ``-f <format>`` or ``--format=<format>``
+
+ Tells ``ovs-appctl`` which output format to use. By default, or with a
+ <format> of ``text``, ``ovs-appctl`` will print plain-text for humans.
+ When <format> is ``json``, ``ovs-appctl`` will return a JSON document.
+ When ``json`` is requested, but a command has not implemented JSON
+ output, the plain-text output will be wrapped in a provisional JSON
+ document with the following structure::
+
+ {"reply-format":"plain","reply":"$PLAIN_TEXT_HERE"}
+
Common Commands
===============
@@ -2,6 +2,9 @@ Post-v3.3.0
--------------------
- Option '--mlockall' now only locks memory pages on fault, if possible.
This also makes it compatible with vHost Post-copy Live Migration.
+ - ovs-appctl:
+ * Added new option [-f|--format] to choose the output format, e.g. 'json'
+ or 'text' (by default).
- Userspace datapath:
* Conntrack now supports 'random' flag for selecting ports in a range
while natting and 'persistent' flag for selection of the IP address
@@ -17,11 +17,13 @@
#include <config.h>
#include "unixctl.h"
#include <errno.h>
+#include <getopt.h>
#include <unistd.h>
+#include "command-line.h"
#include "coverage.h"
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
-#include "openvswitch/json.h"
+#include "json.h"
#include "jsonrpc.h"
#include "openvswitch/list.h"
#include "openvswitch/poll-loop.h"
@@ -50,6 +52,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 unixctl_output_fmt fmt; /* Output format of current connection. */
};
/* Server for control connection. */
@@ -63,6 +67,30 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5);
static struct shash commands = SHASH_INITIALIZER(&commands);
+const char *
+unixctl_output_fmt_to_string(enum unixctl_output_fmt fmt)
+{
+ switch (fmt) {
+ case UNIXCTL_OUTPUT_FMT_TEXT: return "text";
+ case UNIXCTL_OUTPUT_FMT_JSON: return "json";
+ default: return "<unknown>";
+ }
+}
+
+bool
+unixctl_output_fmt_from_string(const char *string,
+ enum unixctl_output_fmt *fmt)
+{
+ if (!strcasecmp(string, "text")) {
+ *fmt = UNIXCTL_OUTPUT_FMT_TEXT;
+ } else if (!strcasecmp(string, "json")) {
+ *fmt = UNIXCTL_OUTPUT_FMT_JSON;
+ } else {
+ return false;
+ }
+ return true;
+}
+
static void
unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED,
const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED)
@@ -94,6 +122,53 @@ 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)
+{
+ struct ovs_cmdl_parsed_option *parsed_options = NULL;
+ size_t n_parsed_options;
+ char *error = NULL;
+
+ static const struct option options[] = {
+ {"format", required_argument, NULL, 'f'},
+ {NULL, 0, NULL, 0},
+ };
+
+ error = ovs_cmdl_parse_all(argc--, (char **) (argv++), options,
+ &parsed_options, &n_parsed_options);
+ if (error) {
+ goto error;
+ }
+
+ for (size_t i = 0; i < n_parsed_options; i++) {
+ struct ovs_cmdl_parsed_option *parsed_option = &parsed_options[i];
+
+ switch (parsed_option->o->val) {
+ case 'f':
+ /* format */
+ if (!unixctl_output_fmt_from_string(parsed_option->arg,
+ &conn->fmt)) {
+ error = xasprintf("option format has invalid value %s",
+ parsed_option->arg);
+ goto error;
+ }
+ break;
+
+ default:
+ OVS_NOT_REACHED();
+ }
+ }
+
+ unixctl_command_reply(conn, NULL);
+ free(parsed_options);
+ return;
+error:
+ unixctl_command_reply_error(conn, error);
+ free(error);
+ free(parsed_options);
+}
+
/* 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.)
@@ -128,36 +203,35 @@ unixctl_command_register(const char *name, const char *usage,
shash_add(&commands, name, command);
}
+enum unixctl_output_fmt
+unixctl_command_get_output_format(struct unixctl_conn *conn)
+{
+ return conn->fmt;
+}
+
+/* Takes ownership of the 'body'. */
static void
unixctl_command_reply__(struct unixctl_conn *conn,
- bool success, const char *body)
+ bool success, struct json *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));
- } else {
- body_json = json_string_create(body);
- }
-
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, JSSF_SORT);
+
VLOG_DBG("replying with %s, id=%s: \"%s\"",
- success ? "success" : "error", id, body);
+ success ? "success" : "error", id, msg);
+ free(msg);
free(id);
}
@@ -169,23 +243,52 @@ 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. */
+ * client indicating the command was processed successfully. 'result' should
+ * be plain-text; use unixctl_command_reply_json() to return a JSON document
+ * when JSON output has been requested. Only one call to
+ * unixctl_command_reply*() functions may be made per request. */
void
unixctl_command_reply(struct unixctl_conn *conn, const char *result)
{
- unixctl_command_reply__(conn, true, result);
+ struct json *json_result = json_string_create(result ? result : "");
+
+ if (conn->fmt == UNIXCTL_OUTPUT_FMT_JSON) {
+ /* Wrap plain-text reply in provisional JSON document when JSON output
+ * has been requested. */
+ struct json *json_reply = json_object_create();
+
+ json_object_put_string(json_reply, "reply-format", "plain");
+ json_object_put(json_reply, "reply", json_result);
+
+ json_result = json_reply;
+ }
+
+ unixctl_command_reply__(conn, true, json_result);
+}
+
+/* Replies to the active unixctl connection 'conn'. 'body' is sent to the
+ * client indicating the command was processed successfully. Use this function
+ * when JSON output has been requested; otherwise use unixctl_command_reply()
+ * for plain-text output. Only one call to unixctl_command_reply*() functions
+ * may be made per request.
+ *
+ * Takes ownership of the 'body'. */
+void
+unixctl_command_reply_json(struct unixctl_conn *conn, struct json *body)
+{
+ ovs_assert(conn->fmt == UNIXCTL_OUTPUT_FMT_JSON);
+ unixctl_command_reply__(conn, true, body);
}
/* 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. 'error' should
+ * be plain-text. Only one call to unixctl_command_reply*() functions 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 ? error : ""));
}
/* Creates a unixctl server listening on 'path', which for POSIX may be:
@@ -250,6 +353,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]", 1, 2,
+ unixctl_set_options, NULL);
struct unixctl_server *server = xmalloc(sizeof *server);
server->listener = listener;
@@ -381,6 +486,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 = UNIXCTL_OUTPUT_FMT_TEXT;
} else if (error == EAGAIN) {
break;
} else {
@@ -483,7 +589,7 @@ unixctl_client_create(const char *path, struct jsonrpc **client)
* '*err' if not NULL. */
int
unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
- char *argv[], char **result, char **err)
+ char *argv[], struct json **result, struct json **err)
{
struct jsonrpc_msg *request, *reply;
struct json **json_args, *params;
@@ -506,26 +612,17 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc,
return error;
}
- if (reply->error) {
- if (reply->error->type == JSON_STRING) {
- *err = xstrdup(json_string(reply->error));
- } else {
- VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s",
- jsonrpc_get_name(client),
- json_type_to_string(reply->error->type));
- error = EINVAL;
- }
- } else if (reply->result) {
- if (reply->result->type == JSON_STRING) {
- *result = xstrdup(json_string(reply->result));
- } else {
- VLOG_WARN("%s: unexpected result type in JSON rpc reply: %s",
- jsonrpc_get_name(client),
- json_type_to_string(reply->result->type));
- error = EINVAL;
- }
+ if (reply->result && reply->error) {
+ VLOG_WARN("unexpected response when communicating with %s: %s\n %s",
+ jsonrpc_get_name(client),
+ json_to_string(reply->result, JSSF_SORT),
+ json_to_string(reply->error, JSSF_SORT));
+ return EINVAL;
}
+ *result = json_nullable_clone(reply->result);
+ *err = json_nullable_clone(reply->error);
+
jsonrpc_msg_destroy(reply);
return error;
}
@@ -17,10 +17,21 @@
#ifndef UNIXCTL_H
#define UNIXCTL_H 1
+#include <stdbool.h>
+
#ifdef __cplusplus
extern "C" {
#endif
+struct json;
+enum unixctl_output_fmt {
+ UNIXCTL_OUTPUT_FMT_TEXT = 1 << 0,
+ UNIXCTL_OUTPUT_FMT_JSON = 1 << 1,
+};
+
+const char *unixctl_output_fmt_to_string(enum unixctl_output_fmt);
+bool unixctl_output_fmt_from_string(const char *, enum unixctl_output_fmt *);
+
/* Server for Unix domain socket control connection. */
struct unixctl_server;
int unixctl_server_create(const char *path, struct unixctl_server **);
@@ -36,7 +47,7 @@ int unixctl_client_create(const char *path, struct jsonrpc **client);
int unixctl_client_transact(struct jsonrpc *client,
const char *command,
int argc, char *argv[],
- char **result, char **error);
+ struct json **result, struct json **error);
/* Command registration. */
struct unixctl_conn;
@@ -45,8 +56,12 @@ typedef void unixctl_cb_func(struct unixctl_conn *,
void unixctl_command_register(const char *name, const char *usage,
int min_args, int max_args,
unixctl_cb_func *cb, void *aux);
+enum unixctl_output_fmt
+unixctl_command_get_output_format(struct unixctl_conn *);
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
}
@@ -619,11 +619,11 @@ ovs_set_program_name(const char *argv0, const char *version)
free(program_version);
if (!strcmp(version, VERSION)) {
- program_version = xasprintf("%s (Open vSwitch) "VERSION"\n",
+ program_version = xasprintf("%s (Open vSwitch) "VERSION,
program_name);
} else {
program_version = xasprintf("%s %s\n"
- "Open vSwitch Library "VERSION"\n",
+ "Open vSwitch Library "VERSION,
program_name, version);
}
}
@@ -760,7 +760,7 @@ ovs_get_program_name(void)
void
ovs_print_version(uint8_t min_ofp, uint8_t max_ofp)
{
- printf("%s", program_version);
+ printf("%s\n", program_version);
if (min_ofp || max_ofp) {
printf("OpenFlow versions %#x:%#x\n", min_ofp, max_ofp);
}
@@ -87,9 +87,6 @@ class UnixctlConnection(object):
if body is None:
body = ""
- if body and not body.endswith("\n"):
- body += "\n"
-
if success:
reply = Message.create_reply(body, self._request_id)
else:
@@ -63,11 +63,16 @@ def main():
ovs.util.ovs_fatal(err_no, "%s: transaction error" % target)
elif error is not None:
sys.stderr.write(error)
+ if error and not error.endswith("\n"):
+ sys.stderr.write("\n")
+
ovs.util.ovs_error(0, "%s: server returned an error" % target)
sys.exit(2)
else:
assert result is not None
sys.stdout.write(result)
+ if result and not result.endswith("\n"):
+ sys.stdout.write("\n")
if __name__ == '__main__':
@@ -265,3 +265,15 @@ OFPT_FEATURES_REPLY: dpid:$orig_dpid
OVS_VSWITCHD_STOP
AT_CLEANUP
+
+AT_SETUP([ovs-vswitchd version])
+OVS_VSWITCHD_START
+
+AT_CHECK([ovs-appctl version], [0], [ignore])
+ovs_version=$(ovs-appctl version)
+
+AT_CHECK_UNQUOTED([ovs-appctl --format json version], [0], [dnl
+{"reply":"$ovs_version","reply-format":"plain"}
+])
+
+AT_CLEANUP
@@ -26,57 +26,105 @@
#include "daemon.h"
#include "dirs.h"
#include "openvswitch/dynamic-string.h"
+#include "openvswitch/json.h"
#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 unixctl_output_fmt format;
+ char *target;
+};
+
+static struct cmdl_args *cmdl_args_create(void);
+static struct cmdl_args *parse_command_line(int argc, char *argv[]);
static struct jsonrpc *connect_to_target(const char *target);
+static char *reply_to_string(struct json *reply, enum unixctl_output_fmt fmt);
int
main(int argc, char *argv[])
{
- char *cmd_result, *cmd_error;
+ struct svec opt_argv = SVEC_EMPTY_INITIALIZER;
+ struct json *cmd_result, *cmd_error;
struct jsonrpc *client;
+ struct cmdl_args *args;
char *cmd, **cmd_argv;
- const char *target;
+ char *msg = NULL;
int cmd_argc;
int error;
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 request and process reply. */
+ /* Transact options request (if required) and process reply. */
+ if (args->format != UNIXCTL_OUTPUT_FMT_TEXT) {
+ svec_add(&opt_argv, "--format");
+ svec_add(&opt_argv, unixctl_output_fmt_to_string(args->format));
+ }
+ svec_terminate(&opt_argv);
+
+ if (!svec_is_empty(&opt_argv)) {
+ 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);
+ }
+
+ if (cmd_error) {
+ jsonrpc_close(client);
+ msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT);
+ fputs(msg, stderr);
+ free(msg);
+ ovs_error(0, "%s: server returned an error", args->target);
+ exit(2);
+ }
+
+ json_destroy(cmd_result);
+ json_destroy(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);
+ msg = reply_to_string(cmd_error, UNIXCTL_OUTPUT_FMT_TEXT);
+ fputs(msg, stderr);
+ free(msg);
+ ovs_error(0, "%s: server returned an error", args->target);
exit(2);
} else if (cmd_result) {
- fputs(cmd_result, stdout);
+ msg = reply_to_string(cmd_result, args->format);
+ fputs(msg, stdout);
+ free(msg);
} else {
OVS_NOT_REACHED();
}
jsonrpc_close(client);
- free(cmd_result);
- free(cmd_error);
+ json_destroy(cmd_result);
+ json_destroy(cmd_error);
+ free(args);
return 0;
}
@@ -101,13 +149,26 @@ 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\
+ (default: text)\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 = UNIXCTL_OUTPUT_FMT_TEXT;
+ args->target = NULL;
+
+ return 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,10 @@ 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;
- int e_options;
+ struct cmdl_args *args = cmdl_args_create();
unsigned int timeout = 0;
+ int e_options;
- target = NULL;
e_options = 0;
for (;;) {
int option;
@@ -141,10 +202,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 = optarg;
break;
case 'e':
@@ -157,6 +218,12 @@ parse_command_line(int argc, char *argv[])
}
break;
+ case 'f':
+ if (!unixctl_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 +261,10 @@ parse_command_line(int argc, char *argv[])
"(use --help for help)");
}
- return target ? target : "ovs-vswitchd";
+ if (!args->target) {
+ args->target = "ovs-vswitchd";
+ }
+ return args;
}
static struct jsonrpc *
@@ -236,3 +306,30 @@ connect_to_target(const char *target)
return client;
}
+/* The caller is responsible for freeing the returned string, with free(), when
+ * it is no longer needed. */
+static char *
+reply_to_string(struct json *reply, enum unixctl_output_fmt fmt)
+{
+ ovs_assert(reply);
+
+ if (fmt == UNIXCTL_OUTPUT_FMT_TEXT && reply->type != JSON_STRING) {
+ ovs_error(0, "Unexpected reply type in JSON rpc reply: %s",
+ json_type_to_string(reply->type));
+ exit(2);
+ }
+
+ struct ds ds = DS_EMPTY_INITIALIZER;
+
+ if (fmt == UNIXCTL_OUTPUT_FMT_TEXT) {
+ ds_put_cstr(&ds, json_string(reply));
+ } else {
+ json_to_ds(reply, JSSF_SORT, &ds);
+ }
+
+ if (ds_last(&ds) != EOF && ds_last(&ds) != '\n') {
+ ds_put_char(&ds, '\n');
+ }
+
+ return ds_steal_cstr(&ds);
+}