Commit c10f8e16 authored by Liu Xiaodong's avatar Liu Xiaodong Committed by Jim Harris
Browse files

jsonrpc-client: add new C client library



It's a C libary for client to call rpc method.

Change-Id: I5378747bd9dab83a41801225ba794b3910d1f5a5
Signed-off-by: default avatarLiu Xiaodong <xiaodong.liu@intel.com>
Reviewed-on: https://review.gerrithub.io/424061


Tested-by: default avatarSPDK CI Jenkins <sys_sgci@intel.com>
Chandler-Test-Pool: SPDK Automated Test System <sys_sgsw@intel.com>
Reviewed-by: default avatarPawel Wodkowski <pawelx.wodkowski@intel.com>
Reviewed-by: default avatarBen Walker <benjamin.walker@intel.com>
Reviewed-by: default avatarJim Harris <james.r.harris@intel.com>
parent 8337cc2d
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -124,6 +124,7 @@ if [ $SPDK_TEST_NVME -eq 1 ]; then
fi

run_test suite test/env/env.sh
run_test suite test/rpc_client/rpc_client.sh

if [ $SPDK_TEST_IOAT -eq 1 ]; then
	run_test suite test/ioat/ioat.sh
+99 −0
Original line number Diff line number Diff line
@@ -64,6 +64,9 @@ extern "C" {
struct spdk_jsonrpc_server;
struct spdk_jsonrpc_request;

struct spdk_jsonrpc_client;
struct spdk_jsonrpc_client_request;

/**
 * User callback to handle a single JSON-RPC request.
 *
@@ -79,6 +82,19 @@ typedef void (*spdk_jsonrpc_handle_request_fn)(
	const struct spdk_json_val *method,
	const struct spdk_json_val *params);

/**
 * Function for specific RPC method response parsing handlers.
 *
 * \param parser_ctx context where analysis are put.
 * \param result json values responsed to this method.
 *
 * \return 0 on success.
 *         SPDK_JSON_PARSE_INVALID on failure.
 */
typedef int (*spdk_jsonrpc_client_response_parser)(
	void *parser_ctx,
	const struct spdk_json_val *result);

/**
 * Create a JSON-RPC server listening on the required address.
 *
@@ -159,6 +175,89 @@ void spdk_jsonrpc_send_error_response(struct spdk_jsonrpc_request *request,
void spdk_jsonrpc_send_error_response_fmt(struct spdk_jsonrpc_request *request,
		int error_code, const char *fmt, ...) __attribute__((format(printf, 3, 4)));

/**
 * Begin building a JSON-RPC request.
 *
 * If this function returns non-NULL, the user must call spdk_jsonrpc_end_request()
 * on the request after writing the desired request object to the spdk_json_write_ctx.
 *
 * \param request JSON-RPC request.
 * \param method Name of the RPC method.
 * \param id ID index for the request.
 *
 * \return JSON write context to write the parameter object to, or NULL if no
 * parameter is necessary.
 */
struct spdk_json_write_ctx *spdk_jsonrpc_begin_request(
	struct spdk_jsonrpc_client_request *request,
	const char *method,
	int32_t id);

/**
 * Complete a JSON-RPC request.
 *
 * \param request JSON-RPC request.
 * \param w JSON write context returned from spdk_jsonrpc_begin_request().
 */
void spdk_jsonrpc_end_request(struct spdk_jsonrpc_client_request *request,
			      struct spdk_json_write_ctx *w);

/**
 * Connect to the specified RPC server.
 *
 * \param rpc_sock_addr RPC socket address.
 * \param addr_family Protocol families of address.
 *
 * \return JSON-RPC client on success, NULL on failure.
 */
struct spdk_jsonrpc_client *spdk_jsonrpc_client_connect(const char *rpc_sock_addr,
		int addr_family);

/**
 * Close the JSON-RPC client.
 *
 * \param client JSON-RPC client.
 */
void spdk_jsonrpc_client_close(struct spdk_jsonrpc_client *client);

/**
 * Create one JSON-RPC request
 *
 * \return Created JSON-RPC request.
 */
struct spdk_jsonrpc_client_request *spdk_jsonrpc_client_create_request(void);

/**
 * free one JSON-RPC request
 *
 * \param req Created JSON-RPC request.
 */
void spdk_jsonrpc_client_free_request(
	struct spdk_jsonrpc_client_request *req);

/**
 * Send the JSON-RPC request in JSON-RPC client.
 *
 * \param client JSON-RPC client.
 * \param req JSON-RPC request.
 *
 * \return 0 on success.
 */
int spdk_jsonrpc_client_send_request(struct spdk_jsonrpc_client *client,
				     struct spdk_jsonrpc_client_request *req);

/**
 * Receive the JSON-RPC response in JSON-RPC client.
 *
 * \param client JSON-RPC client.
 * \param parser_fn Specific function used to parse the result inside response.
 * \param parser_ctx Parameter for parser_fn.
 *
 * \return 0 on success.
 */
int spdk_jsonrpc_client_recv_response(struct spdk_jsonrpc_client *client,
				      spdk_jsonrpc_client_response_parser parser_fn,
				      void *parser_ctx);

#ifdef __cplusplus
}
+1 −0
Original line number Diff line number Diff line
@@ -36,5 +36,6 @@ include $(SPDK_ROOT_DIR)/mk/spdk.common.mk

LIBNAME = jsonrpc
C_SRCS = jsonrpc_server.c jsonrpc_server_tcp.c
C_SRCS += jsonrpc_client.c jsonrpc_client_tcp.c

include $(SPDK_ROOT_DIR)/mk/spdk.lib.mk
+198 −0
Original line number Diff line number Diff line
/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "spdk/util.h"
#include "jsonrpc_internal.h"

struct jsonrpc_response {
	char *version;
	const struct spdk_json_val *id;
	const struct spdk_json_val *result;
};

static int
capture_val(const struct spdk_json_val *val, void *out)
{
	const struct spdk_json_val **vptr = out;

	*vptr = val;
	return 0;
}

static const struct spdk_json_object_decoder jsonrpc_response_decoders[] = {
	{"jsonrpc", offsetof(struct jsonrpc_response, version), spdk_json_decode_string},
	{"id", offsetof(struct jsonrpc_response, id), capture_val},
	{"result", offsetof(struct jsonrpc_response, result), capture_val},
};

static int
parse_single_response(struct spdk_json_val *values,
		      spdk_jsonrpc_client_response_parser parser_fn,
		      void *parser_ctx)
{
	struct jsonrpc_response resp = {};
	int rc = 0;

	if (spdk_json_decode_object(values, jsonrpc_response_decoders,
				    SPDK_COUNTOF(jsonrpc_response_decoders),
				    &resp)) {
		rc = SPDK_JSON_PARSE_INVALID;
		goto done;
	}

	if (strcmp(resp.version, "2.0")) {
		rc = SPDK_JSON_PARSE_INVALID;
		goto done;
	}

	if (resp.id->type != SPDK_JSON_VAL_STRING && resp.id->type != SPDK_JSON_VAL_NUMBER) {
		rc = SPDK_JSON_PARSE_INVALID;
	}

done:
	free(resp.version);
	if (rc) {
		return SPDK_JSON_PARSE_INVALID;
	}

	return parser_fn(parser_ctx, resp.result);
}

int
spdk_jsonrpc_parse_response(struct spdk_jsonrpc_client *client, void *json, size_t size)
{
	ssize_t rc;
	void *end = NULL;

	/* Check to see if we have received a full JSON value. */
	rc = spdk_json_parse(json, size, NULL, 0, &end, 0);
	if (rc == SPDK_JSON_PARSE_INCOMPLETE) {
		return rc;
	}

	SPDK_DEBUGLOG(SPDK_LOG_RPC_CLIENT, "Json string is :\n%s\n", (char *)json);
	if (rc < 0 || rc > SPDK_JSONRPC_MAX_VALUES) {
		SPDK_ERRLOG("JSON parse error\n");
		/*
		 * Can't recover from parse error (no guaranteed resync point in streaming JSON).
		 * Return an error to indicate that the connection should be closed.
		 */
		return SPDK_JSON_PARSE_INVALID;
	}

	/* Decode a second time now that there is a full JSON value available. */
	rc = spdk_json_parse(json, size, client->values, SPDK_JSONRPC_MAX_VALUES, &end,
			     SPDK_JSON_PARSE_FLAG_DECODE_IN_PLACE);
	if (rc < 0 || rc > SPDK_JSONRPC_MAX_VALUES) {
		SPDK_ERRLOG("JSON parse error on second pass\n");
		return SPDK_JSON_PARSE_INVALID;
	}

	assert(end != NULL);

	if (client->values[0].type != SPDK_JSON_VAL_OBJECT_BEGIN) {
		SPDK_ERRLOG("top-level JSON value was not object\n");
		return SPDK_JSON_PARSE_INVALID;
	}

	rc = parse_single_response(client->values, client->parser_fn, client->parser_ctx);

	return rc;
}

static int
jsonrpc_client_write_cb(void *cb_ctx, const void *data, size_t size)
{
	struct spdk_jsonrpc_client_request *request = cb_ctx;
	size_t new_size = request->send_buf_size;

	while (new_size - request->send_len < size) {
		if (new_size >= SPDK_JSONRPC_SEND_BUF_SIZE_MAX) {
			SPDK_ERRLOG("Send buf exceeded maximum size (%zu)\n",
				    (size_t)SPDK_JSONRPC_SEND_BUF_SIZE_MAX);
			return -ENOSPC;
		}

		new_size *= 2;
	}

	if (new_size != request->send_buf_size) {
		uint8_t *new_buf;

		new_buf = realloc(request->send_buf, new_size);
		if (new_buf == NULL) {
			SPDK_ERRLOG("Resizing send_buf failed (current size %zu, new size %zu)\n",
				    request->send_buf_size, new_size);
			return -ENOMEM;
		}

		request->send_buf = new_buf;
		request->send_buf_size = new_size;
	}

	memcpy(request->send_buf + request->send_len, data, size);
	request->send_len += size;

	return 0;
}

struct spdk_json_write_ctx *
spdk_jsonrpc_begin_request(struct spdk_jsonrpc_client_request *request, const char *method,
			   int32_t id)
{
	struct spdk_json_write_ctx *w;

	w = spdk_json_write_begin(jsonrpc_client_write_cb, request, 0);
	if (w == NULL) {
		return NULL;
	}

	spdk_json_write_object_begin(w);
	spdk_json_write_named_string(w, "jsonrpc", "2.0");
	spdk_json_write_named_int32(w, "id", id);
	spdk_json_write_named_string(w, "method", method);

	return w;
}

void
spdk_jsonrpc_end_request(struct spdk_jsonrpc_client_request *request, struct spdk_json_write_ctx *w)
{
	assert(w != NULL);

	spdk_json_write_object_end(w);
	spdk_json_write_end(w);
	jsonrpc_client_write_cb(request, "\n", 1);
}

SPDK_LOG_REGISTER_COMPONENT("rpc_client", SPDK_LOG_RPC_CLIENT)
+284 −0
Original line number Diff line number Diff line
/*-
 *   BSD LICENSE
 *
 *   Copyright (c) Intel Corporation.
 *   All rights reserved.
 *
 *   Redistribution and use in source and binary forms, with or without
 *   modification, are permitted provided that the following conditions
 *   are met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in
 *       the documentation and/or other materials provided with the
 *       distribution.
 *     * Neither the name of Intel Corporation nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 *   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 *   "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 *   LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 *   A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 *   OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 *   SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 *   LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 *   DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 *   THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 *   (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 *   OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
#include "spdk/string.h"
#include "jsonrpc_internal.h"

#define RPC_DEFAULT_PORT	"5260"

static struct spdk_jsonrpc_client *
_spdk_jsonrpc_client_connect(int domain, int protocol,
			     struct sockaddr *server_addr, socklen_t addrlen)
{
	struct spdk_jsonrpc_client *client;
	int rc;

	client = calloc(1, sizeof(struct spdk_jsonrpc_client));
	if (client == NULL) {
		return NULL;
	}

	client->sockfd = socket(domain, SOCK_STREAM, protocol);
	if (client->sockfd < 0) {
		SPDK_ERRLOG("socket() failed\n");
		free(client);
		return NULL;
	}

	rc = connect(client->sockfd, server_addr, addrlen);
	if (rc != 0) {
		SPDK_ERRLOG("could not connet JSON-RPC server: %s\n", spdk_strerror(errno));
		close(client->sockfd);
		free(client);
		return NULL;
	}

	/* memory malloc for recv-buf */
	client->recv_buf = malloc(SPDK_JSONRPC_SEND_BUF_SIZE_INIT);
	if (!client->recv_buf) {
		SPDK_ERRLOG("memory malloc for recv-buf failed\n");
		close(client->sockfd);
		free(client);
		return NULL;
	}
	client->recv_buf_size = SPDK_JSONRPC_SEND_BUF_SIZE_INIT;

	return client;
}

struct spdk_jsonrpc_client *
spdk_jsonrpc_client_connect(const char *rpc_sock_addr, int addr_family)
{
	struct spdk_jsonrpc_client *client;

	if (addr_family == AF_UNIX) {
		/* Unix Domain Socket */
		struct sockaddr_un rpc_sock_addr_unix = {};
		int rc;

		rpc_sock_addr_unix.sun_family = AF_UNIX;
		rc = snprintf(rpc_sock_addr_unix.sun_path,
			      sizeof(rpc_sock_addr_unix.sun_path),
			      "%s", rpc_sock_addr);
		if (rc < 0 || (size_t)rc >= sizeof(rpc_sock_addr_unix.sun_path)) {
			SPDK_ERRLOG("RPC Listen address Unix socket path too long\n");
			return NULL;
		}

		client = _spdk_jsonrpc_client_connect(AF_UNIX, 0,
						      (struct sockaddr *)&rpc_sock_addr_unix,
						      sizeof(rpc_sock_addr_unix));
	} else {
		/* TCP/IP socket */
		struct addrinfo		hints;
		struct addrinfo		*res;
		char *tmp;
		char *host, *port;

		tmp = strdup(rpc_sock_addr);
		if (!tmp) {
			SPDK_ERRLOG("Out of memory\n");
			return NULL;
		}

		if (spdk_parse_ip_addr(tmp, &host, &port) < 0) {
			free(tmp);
			SPDK_ERRLOG("Invalid listen address '%s'\n", rpc_sock_addr);
			return NULL;
		}

		if (port == NULL) {
			port = RPC_DEFAULT_PORT;
		}

		memset(&hints, 0, sizeof(hints));
		hints.ai_family = AF_UNSPEC;
		hints.ai_socktype = SOCK_STREAM;
		hints.ai_protocol = IPPROTO_TCP;

		if (getaddrinfo(host, port, &hints, &res) != 0) {
			free(tmp);
			SPDK_ERRLOG("Unable to look up RPC connnect address '%s'\n", rpc_sock_addr);
			return NULL;
		}

		client = _spdk_jsonrpc_client_connect(res->ai_family, res->ai_protocol,
						      res->ai_addr, res->ai_addrlen);

		freeaddrinfo(res);
		free(tmp);
	}

	return client;
}

void
spdk_jsonrpc_client_close(struct spdk_jsonrpc_client *client)
{
	if (client->sockfd >= 0) {
		close(client->sockfd);
		free(client->recv_buf);
		client->sockfd = -1;
	}

	free(client);
}

struct spdk_jsonrpc_client_request *
spdk_jsonrpc_client_create_request(void)
{
	struct spdk_jsonrpc_client_request *request;

	request = calloc(1, sizeof(*request));
	if (request == NULL) {
		return NULL;
	}

	/* memory malloc for send-buf */
	request->send_buf = malloc(SPDK_JSONRPC_SEND_BUF_SIZE_INIT);
	if (!request->send_buf) {
		SPDK_ERRLOG("memory malloc for send-buf failed\n");
		free(request);
		return NULL;
	}
	request->send_buf_size = SPDK_JSONRPC_SEND_BUF_SIZE_INIT;

	return request;
}

void
spdk_jsonrpc_client_free_request(struct spdk_jsonrpc_client_request *req)
{
	free(req->send_buf);
	free(req);
}

int
spdk_jsonrpc_client_send_request(struct spdk_jsonrpc_client *client,
				 struct spdk_jsonrpc_client_request *request)
{
	ssize_t rc;

	/* Reset offset in request */
	request->send_offset = 0;

	while (request->send_len > 0) {
		rc = send(client->sockfd, request->send_buf + request->send_offset,
			  request->send_len, 0);
		if (rc <= 0) {
			if (rc < 0 && errno == EINTR) {
				rc = 0;
			} else {
				return rc;
			}
		}

		request->send_offset += rc;
		request->send_len -= rc;
	}

	return 0;
}

static int
recv_buf_expand(struct spdk_jsonrpc_client *client)
{
	uint8_t *new_buf;

	if (client->recv_buf_size * 2 > SPDK_JSONRPC_SEND_BUF_SIZE_MAX) {
		return -ENOSPC;
	}

	new_buf = realloc(client->recv_buf, client->recv_buf_size * 2);
	if (new_buf == NULL) {
		SPDK_ERRLOG("Resizing recv_buf failed (current size %zu, new size %zu)\n",
			    client->recv_buf_size, client->recv_buf_size * 2);
		return -ENOMEM;
	}

	client->recv_buf = new_buf;
	client->recv_buf_size *= 2;

	return 0;
}

int
spdk_jsonrpc_client_recv_response(struct spdk_jsonrpc_client *client,
				  spdk_jsonrpc_client_response_parser parser_fn,
				  void *parser_ctx)
{
	ssize_t rc = 0;
	size_t recv_avail;
	size_t recv_offset = 0;

	client->parser_fn = parser_fn;
	client->parser_ctx = parser_ctx;

	recv_avail = client->recv_buf_size;

	while (recv_avail > 0) {
		rc = recv(client->sockfd, client->recv_buf + recv_offset, recv_avail, 0);
		if (rc < 0) {
			if (errno == EINTR) {
				continue;
			} else {
				return errno;
			}
		} else if (rc == 0) {
			return -EIO;
		}

		recv_offset += rc;
		recv_avail -= rc;

		/* Check to see if we have received a full JSON value. */
		rc = spdk_jsonrpc_parse_response(client, client->recv_buf, recv_offset);
		if (rc == 0) {
			/* Successfully parsed response */
			return 0;
		} else if (rc != SPDK_JSON_PARSE_INCOMPLETE) {
			SPDK_ERRLOG("jsonrpc parse request failed\n");
			return -EINVAL;
		}

		/* Expand receive buffer if larger one is needed */
		if (recv_avail == 0) {
			rc = recv_buf_expand(client);
			if (rc != 0) {
				return rc;
			}
			recv_avail = client->recv_buf_size - recv_offset;
		}
	}

	return 0;
}
Loading