From patchwork Thu Nov 16 10:41:15 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Jakob Meng X-Patchwork-Id: 1864680 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=fail reason="signature verification failed" (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NaTiJWne; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=openvswitch.org (client-ip=2605:bc80:3010::138; helo=smtp1.osuosl.org; envelope-from=ovs-dev-bounces@openvswitch.org; receiver=patchwork.ozlabs.org) Received: from smtp1.osuosl.org (smtp1.osuosl.org [IPv6:2605:bc80:3010::138]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4SWGmz1YXsz1yRR for ; Thu, 16 Nov 2023 21:41:55 +1100 (AEDT) Received: from localhost (localhost [127.0.0.1]) by smtp1.osuosl.org (Postfix) with ESMTP id 5A8E883224; Thu, 16 Nov 2023 10:41:53 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 5A8E883224 Authentication-Results: smtp1.osuosl.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NaTiJWne X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp1.osuosl.org ([127.0.0.1]) by localhost (smtp1.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id NlE3cnopbJxf; Thu, 16 Nov 2023 10:41:51 +0000 (UTC) Received: from lists.linuxfoundation.org (lf-lists.osuosl.org [IPv6:2605:bc80:3010:104::8cd3:938]) by smtp1.osuosl.org (Postfix) with ESMTPS id 0C49B82E95; Thu, 16 Nov 2023 10:41:50 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp1.osuosl.org 0C49B82E95 Received: from lf-lists.osuosl.org (localhost [127.0.0.1]) by lists.linuxfoundation.org (Postfix) with ESMTP id DACECC0DD5; Thu, 16 Nov 2023 10:41:48 +0000 (UTC) X-Original-To: dev@openvswitch.org Delivered-To: ovs-dev@lists.linuxfoundation.org Received: from smtp4.osuosl.org (smtp4.osuosl.org [140.211.166.137]) by lists.linuxfoundation.org (Postfix) with ESMTP id 13FCAC0DD5 for ; Thu, 16 Nov 2023 10:41:48 +0000 (UTC) Received: from localhost (localhost [127.0.0.1]) by smtp4.osuosl.org (Postfix) with ESMTP id E2D2141BDD for ; Thu, 16 Nov 2023 10:41:47 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org E2D2141BDD Authentication-Results: smtp4.osuosl.org; dkim=pass (1024-bit key) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=NaTiJWne X-Virus-Scanned: amavisd-new at osuosl.org Received: from smtp4.osuosl.org ([127.0.0.1]) by localhost (smtp4.osuosl.org [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id Fzxr2JM3bWsR for ; Thu, 16 Nov 2023 10:41:45 +0000 (UTC) Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.129.124]) by smtp4.osuosl.org (Postfix) with ESMTPS id 9857141BA2 for ; Thu, 16 Nov 2023 10:41:45 +0000 (UTC) DKIM-Filter: OpenDKIM Filter v2.11.0 smtp4.osuosl.org 9857141BA2 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1700131304; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding: in-reply-to:in-reply-to:references:references; bh=vxvhGKQEnEoM6qNU31SuqSJoVH5s7Cra8dyucZmsWj0=; b=NaTiJWneWexZmfLeYEgeK2dnCzT1iApNvclUEXIT9nSkYEvygK/45gAazBT4+RP1RXBtZM qtFSuWsbiJ+P1LosiG1ltsi3rnyg8Od8K8vDd1+7hAiNRzwn858PEqwWIqL2UTcXe3jaId giHbKj5cD1ESSIt7rXKn/vakFbwt7HI= Received: from mail-lf1-f71.google.com (mail-lf1-f71.google.com [209.85.167.71]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-641-VyADvHrKN3Kz_-lxjn1Ybw-1; Thu, 16 Nov 2023 05:41:42 -0500 X-MC-Unique: VyADvHrKN3Kz_-lxjn1Ybw-1 Received: by mail-lf1-f71.google.com with SMTP id 2adb3069b0e04-507a3ae32beso631146e87.2 for ; Thu, 16 Nov 2023 02:41:42 -0800 (PST) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1700131301; x=1700736101; h=content-transfer-encoding:mime-version:references:in-reply-to :message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc :subject:date:message-id:reply-to; bh=vxvhGKQEnEoM6qNU31SuqSJoVH5s7Cra8dyucZmsWj0=; b=awQTM4PKEF71SSx32iZY/qb5ibcskAhRDhxDVHXxjODu2Wip03KAlvYaX6hR/eIGLd s5rKgQHHUpeinl7tJZLUydtZZCWEN2JWrcsS4KqARF7QEUJYEc+N+309j50x38Qydf2G 8f8hP5vXz47IXPcAp3HY/ovcKg7ggUIQwfEjxaHRpa8crXEm4DMHVox7YhKw9QVASJrk ItmzQ+AlNPTAVA6OcF0lY/Qt1ysT/xZNhgG//6/Iq7R5Ot6H+mu9KZbDegt+DO/T6Yrz uvRaywfdzSayeDWzkQ779oAhdrH+HRXHx8PP7Hn+ZSG6aPxoll99jA+rarj44frk5Uyl dlOw== X-Gm-Message-State: AOJu0Yz59t/Ed9e6dv700Dt7b73yf2vjqYD2EO5hB75FAiG5jOSVOEg3 qhaWfhq6oPQ1QbUNZIKYZcK8yXjzsULR+0gSTIVS+ifi7DMSoTLyKvsPMCds5usTZkBSSoUEHrd DH1uywYnFs5d1f71VUXYqk7lfnrDTMCNnllJ7KCuBv366Dm1FDdHKeREVJR8gKfP/Jks= X-Received: by 2002:a2e:83c8:0:b0:2c6:f625:cc61 with SMTP id s8-20020a2e83c8000000b002c6f625cc61mr5772014ljh.31.1700131301202; Thu, 16 Nov 2023 02:41:41 -0800 (PST) X-Google-Smtp-Source: AGHT+IE8SCbrBjg6FaIX1jmxCUarAPLVwb8YYMSoS/SE+jg1JRh+75QeMgGDPcvCPBb+5I0RNkuo8Q== X-Received: by 2002:a2e:83c8:0:b0:2c6:f625:cc61 with SMTP id s8-20020a2e83c8000000b002c6f625cc61mr5771986ljh.31.1700131300584; Thu, 16 Nov 2023 02:41:40 -0800 (PST) Received: from positronik4lide.redhat.com ([87.122.56.153]) by smtp.gmail.com with ESMTPSA id n10-20020a05600c304a00b004080f0376a0sm2937210wmh.42.2023.11.16.02.41.39 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 16 Nov 2023 02:41:40 -0800 (PST) From: jmeng@redhat.com To: dev@openvswitch.org, i.maximets@ovn.org, echaudro@redhat.com, ktraynor@redhat.com, aconole@redhat.com, rjarry@redhat.com Date: Thu, 16 Nov 2023 11:41:15 +0100 Message-Id: <20231116104120.25676-2-jmeng@redhat.com> X-Mailer: git-send-email 2.39.2 In-Reply-To: <20231116104120.25676-1-jmeng@redhat.com> References: <20231116104120.25676-1-jmeng@redhat.com> MIME-Version: 1.0 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com Subject: [ovs-dev] [PATCH v4 1/6] Add global option for JSON output to ovs-appctl. X-BeenThere: ovs-dev@openvswitch.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: ovs-dev-bounces@openvswitch.org Sender: "dev" From: Jakob Meng For monitoring systems such as Prometheus it would be beneficial if OVS would expose statistics in a machine-readable format. This patch introduces support for different output formats to ovs-xxx tools. They gain a global option '-f,--format' which allows users to request JSON instead of plain-text for humans. An example call implemented in a later patch is 'ovs-appctl --format json dpif/show'. For that it is necessary to change the JSON-RPC API lib/unixctl.* which ovs-xxx tools use to communicate with ovs-vswitchd across unix sockets. It now allows to transport the requested output format besides the command and its args. This change has been implemented in a backward compatible way. One can use an updated client (ovs-appctl) with an old server (ovs-vswitchd) and vice versa. Of course, JSON output only works when both sides have been updated. Previously, the command was copied to the 'method' parameter in JSON-RPC while the args were copied to the 'params' parameter. Without any other means to transport parameters via JSON-RPC left, the meaning of 'method' and 'params' had to be changed: 'method' will now be 'execute/v1' when an output format other than 'text' is requested. In that case, the first parameter of the JSON array 'params' will now be the designated command, the second one the output format and the rest will be command args. unixctl_command_register() in lib/unixctl.* has been cloned as unixctl_command_register_fmt() in order to demonstrate the new API. The latter will be replaced with the former in a later patch. The new function gained an int argument called 'output_fmts' with which commands have to announce their support for output formats. Function type unixctl_cb_func in lib/unixctl.h has gained a new arg 'enum ovs_output_fmt fmt'. For now, it has been added as a comment only for the huge changes reason mentioned earlier. The output format which is passed via unix socket to ovs-vswitchd will be converted into a value of type 'enum ovs_output_fmt' in lib/unixctl.c and then passed to said 'fmt' arg of the choosen command handler (in a future patch). When a requested output format is not supported by a command, then process_command() in lib/unixctl.c will return an error. This patch does not yet pass the choosen output format to commands. Doing so requires changes to all unixctl_command_register() calls and all command callbacks. To improve readibility those changes have been split out into a follow up patch. Respectively, whenever an output format other than 'text' is choosen for ovs-xxx tools, they will fail. By default, the output format is plain-text as before. In popular tools like kubectl the option for output control is usually called '-o|--output' instead of '-f,--format'. But ovs-appctl already has an short option '-o' which prints the available ovs-appctl options ('--option'). The now choosen name also better alines with ovsdb-client where '-f,--format' controls output formatting. Reported-at: https://bugzilla.redhat.com/1824861 Signed-off-by: Jakob Meng --- NEWS | 2 + lib/command-line.c | 36 ++++++ lib/command-line.h | 10 ++ lib/dpctl.h | 4 + lib/unixctl.c | 249 +++++++++++++++++++++++++++++++---------- lib/unixctl.h | 17 ++- tests/pmd.at | 5 + utilities/ovs-appctl.c | 67 ++++++++--- utilities/ovs-dpctl.c | 1 + 9 files changed, 319 insertions(+), 72 deletions(-) diff --git a/NEWS b/NEWS index 43aea97b5..12a895629 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,8 @@ Post-v3.2.0 a.k.a. 'configured' values, can be found in the 'status' column of the Interface table, i.e. with 'ovs-vsctl get interface <..> status'. Reported names adjusted accordingly. + * Added new option [-f|--format] to choose the output format, e.g. 'json' + or 'text' (by default). v3.2.0 - 17 Aug 2023 diff --git a/lib/command-line.c b/lib/command-line.c index 967f4f5d5..775e0430a 100644 --- a/lib/command-line.c +++ b/lib/command-line.c @@ -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) { diff --git a/lib/command-line.h b/lib/command-line.h index fc2a2690f..494274cec 100644 --- a/lib/command-line.h +++ b/lib/command-line.h @@ -20,6 +20,7 @@ /* Utilities for command-line parsing. */ #include "compiler.h" +#include 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; diff --git a/lib/dpctl.h b/lib/dpctl.h index 9d0052152..1c9b2409f 100644 --- a/lib/dpctl.h +++ b/lib/dpctl.h @@ -19,6 +19,7 @@ #include #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 diff --git a/lib/unixctl.c b/lib/unixctl.c index 103357ee9..5de59f339 100644 --- a/lib/unixctl.c +++ b/lib/unixctl.c @@ -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; }; @@ -63,6 +63,8 @@ static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(5, 5); static struct shash commands = SHASH_INITIALIZER(&commands); +static const char *rpc_marker = "execute/v1"; + static void unixctl_list_commands(struct unixctl_conn *conn, int argc OVS_UNUSED, const char *argv[] OVS_UNUSED, void *aux OVS_UNUSED) @@ -109,6 +111,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 +147,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 +201,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: @@ -266,6 +309,11 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request) struct unixctl_command *command; struct json_array *params; + const char *method; + enum ovs_output_fmt fmt; + struct svec argv = SVEC_EMPTY_INITIALIZER; + int args_offset; + bool plain_rpc; COVERAGE_INC(unixctl_received); conn->request_id = json_clone(request->id); @@ -279,45 +327,95 @@ process_command(struct unixctl_conn *conn, struct jsonrpc_msg *request) free(id_s); } + /* The JSON-RPC API requires an indirection in order to allow transporting + * additional data like the output format besides command and args. For + * backward compatibility with older clients the plain RPC is still + * supported. */ + plain_rpc = strcmp(request->method, rpc_marker); + args_offset = plain_rpc ? 0 : 2; + params = json_array(request->params); - command = shash_find_data(&commands, request->method); + if (!plain_rpc && (params->n < 2)) { + error = xasprintf("JSON-RPC API mismatch: Unexpected # of params:"\ + " %"PRIuSIZE, params->n); + goto error; + } + + for (size_t i = 0; i < params->n; i++) { + if (params->elems[i]->type != JSON_STRING) { + error = xasprintf("command has non-string argument: %s", + json_to_string(params->elems[i], 0)); + goto error; + } + } + + /* Extract method name. */ + method = plain_rpc ? request->method : json_string(params->elems[0]); + + /* Extract output format. */ + if (plain_rpc) { + fmt = OVS_OUTPUT_FMT_TEXT; + } else { + if (!ovs_output_fmt_from_string(json_string(params->elems[1]), &fmt)) { + error = xasprintf("invalid output format: %s", + json_string(params->elems[1])); + goto error; + } + } + + /* Find command with method name. */ + command = shash_find_data(&commands, method); + + /* Verify that method call complies with command requirements. */ if (!command) { error = xasprintf("\"%s\" is not a valid command (use " "\"list-commands\" to see a list of valid commands)", - request->method); - } else if (params->n < command->min_args) { + method); + goto error; + } else if ((params->n - args_offset) < command->min_args) { error = xasprintf("\"%s\" command requires at least %d arguments", - request->method, command->min_args); - } else if (params->n > command->max_args) { + method, command->min_args); + goto error; + } else if ((params->n - args_offset) > command->max_args) { error = xasprintf("\"%s\" command takes at most %d arguments", - request->method, command->max_args); - } else { - struct svec argv = SVEC_EMPTY_INITIALIZER; - int i; - - svec_add(&argv, request->method); - for (i = 0; i < params->n; i++) { - if (params->elems[i]->type != JSON_STRING) { - error = xasprintf("\"%s\" command has non-string argument", - request->method); - break; - } - svec_add(&argv, json_string(params->elems[i])); - } - svec_terminate(&argv); - - if (!error) { - command->cb(conn, argv.n, (const char **) argv.names, - command->aux); - } + method, command->max_args); + goto error; + } else if ((!command->output_fmts && fmt != OVS_OUTPUT_FMT_TEXT) || + (command->output_fmts && !(fmt & command->output_fmts))) + { + error = xasprintf("\"%s\" command does not support output format"\ + " \"%s\" %d %d", method, + ovs_output_fmt_to_string(fmt), command->output_fmts, + fmt); + goto error; + } - svec_destroy(&argv); + /* FIXME: Remove this check once output format will be passed to the + * command handler below. */ + if (fmt != OVS_OUTPUT_FMT_TEXT) { + error = xasprintf("output format \"%s\" has not been implemented yet", + ovs_output_fmt_to_string(fmt)); + goto error; } - if (error) { - unixctl_command_reply_error(conn, error); - free(error); + /* Extract command args. */ + svec_add(&argv, method); + for (size_t i = args_offset; i < params->n; i++) { + svec_add(&argv, json_string(params->elems[i])); } + svec_terminate(&argv); + + /* FIXME: Output format will be passed as 'fmt' to the command in later + * patch. */ + command->cb(conn, argv.n, (const char **) argv.names, /* fmt, */ + command->aux); + + svec_destroy(&argv); + + return; +error: + unixctl_command_reply_error(conn, error); + free(error); } static int @@ -483,21 +581,43 @@ 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[], enum ovs_output_fmt fmt, + char **result, char **err) { struct jsonrpc_msg *request, *reply; struct json **json_args, *params; int error, i; + /* The JSON-RPC API requires an indirection in order to allow transporting + * additional data like the output format besides command and args. For + * backward compatibility with older servers the plain RPC is still + * supported. */ + bool plain_rpc = (fmt == OVS_OUTPUT_FMT_TEXT); *result = NULL; *err = NULL; - json_args = xmalloc(argc * sizeof *json_args); - for (i = 0; i < argc; i++) { - json_args[i] = json_string_create(argv[i]); + if (plain_rpc) { + json_args = xmalloc(argc * sizeof *json_args); + for (i = 0; i < argc; i++) { + json_args[i] = json_string_create(argv[i]); + } + + params = json_array_create(json_args, argc); + request = jsonrpc_create_request(command, params, NULL); + } else { + json_args = xmalloc((argc + 2) * sizeof *json_args); + json_args[0] = json_string_create(command); + json_args[1] = ovs_output_fmt_to_json(fmt); + for (i = 0; i < argc; i++) { + json_args[i + 2] = json_string_create(argv[i]); + } + + params = json_array_create(json_args, argc + 2); + + /* Use a versioned command to ensure that both server and client + * use the same JSON-RPC API. */ + request = jsonrpc_create_request(rpc_marker, params, NULL); } - params = json_array_create(json_args, argc); - request = jsonrpc_create_request(command, params, NULL); error = jsonrpc_transact_block(client, request, &reply); if (error) { @@ -508,7 +628,17 @@ unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, if (reply->error) { if (reply->error->type == JSON_STRING) { - *err = xstrdup(json_string(reply->error)); + /* Catch incompatible server and return helpful error msg. */ + char *plain_rpc_error = xasprintf("\"%s\" is not a valid command", + rpc_marker); + if (!strncmp(plain_rpc_error, json_string(reply->error), + strlen(plain_rpc_error))) { + *err = xstrdup("JSON RPC reply indicates incompatible server. " + "Please upgrade server side to newer version."); + } else { + *err = xstrdup(json_string(reply->error)); + } + free(plain_rpc_error); } else { VLOG_WARN("%s: unexpected error type in JSON RPC reply: %s", jsonrpc_get_name(client), @@ -518,6 +648,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), diff --git a/lib/unixctl.h b/lib/unixctl.h index 4562dbc49..896ecb062 100644 --- a/lib/unixctl.h +++ b/lib/unixctl.h @@ -17,6 +17,9 @@ #ifndef UNIXCTL_H #define UNIXCTL_H 1 +#include "openvswitch/json.h" +#include "command-line.h" + #ifdef __cplusplus extern "C" { #endif @@ -36,17 +39,29 @@ int unixctl_client_create(const char *path, struct jsonrpc **client); int unixctl_client_transact(struct jsonrpc *client, const char *command, int argc, char *argv[], + enum ovs_output_fmt fmt, char **result, char **error); /* 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 } diff --git a/tests/pmd.at b/tests/pmd.at index 06cc90477..3fcda13fd 100644 --- a/tests/pmd.at +++ b/tests/pmd.at @@ -93,6 +93,11 @@ pmd thread numa_id core_id : overhead: NOT AVAIL ]) +AT_CHECK([ovs-appctl --format json dpif-netdev/pmd-rxq-show], [2], [], [dnl +"dpif-netdev/pmd-rxq-show" command does not support output format "json" 1 2 +ovs-appctl: ovs-vswitchd: server returned an error +]) + AT_CHECK([ovs-appctl dpif/show], [0], [dnl dummy@ovs-dummy: hit:0 missed:0 br0: diff --git a/utilities/ovs-appctl.c b/utilities/ovs-appctl.c index ba0c172e6..feff1be0b 100644 --- a/utilities/ovs-appctl.c +++ b/utilities/ovs-appctl.c @@ -34,7 +34,16 @@ #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 +52,30 @@ 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; 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. */ 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); + args->format, &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 +83,7 @@ main(int argc, char *argv[]) OVS_NOT_REACHED(); } + cmdl_args_destroy(args); jsonrpc_close(client); free(cmd_result); free(cmd_error); @@ -101,13 +111,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 +148,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 +158,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 +173,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 +189,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 +232,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 * diff --git a/utilities/ovs-dpctl.c b/utilities/ovs-dpctl.c index 56d7a942b..30104ddb2 100644 --- a/utilities/ovs-dpctl.c +++ b/utilities/ovs-dpctl.c @@ -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;