[03/11] telemetry: add client feature and sockets

Message ID 1535026093-101872-4-git-send-email-ciara.power@intel.com (mailing list archive)
State Superseded, archived
Delegated to: Thomas Monjalon
Headers
Series introduce telemetry library |

Checks

Context Check Description
ci/checkpatch warning coding style issues
ci/Intel-compilation fail Compilation issues

Commit Message

Power, Ciara Aug. 23, 2018, 12:08 p.m. UTC
  This patch introduces clients to the telemetry API.

When a client makes a connection through the initial telemetry
socket, they can send a message through the socket to be
parsed. Register messages are expected through this socket, to
enable clients to register and have a client socket setup for
future communications.

A TAILQ is used to store all clients information. Using this, the
client sockets are polled for messages, which will later be parsed
and dealt with accordingly.

Functionality that make use of the client sockets were introduced
in this patch also, such as writing to client sockets, and sending
error responses.

Signed-off-by: Ciara Power <ciara.power@intel.com>
Signed-off-by: Brian Archbold <brian.archbold@intel.com>
---
 lib/librte_telemetry/meson.build              |   2 +
 lib/librte_telemetry/rte_telemetry.c          | 358 ++++++++++++++++++++++++++
 lib/librte_telemetry/rte_telemetry_internal.h |  25 ++
 mk/rte.app.mk                                 |   2 +-
 4 files changed, 386 insertions(+), 1 deletion(-)
  

Comments

Stephen Hemminger Aug. 23, 2018, 11:27 p.m. UTC | #1
On Thu, 23 Aug 2018 13:08:05 +0100
Ciara Power <ciara.power@intel.com> wrote:

> This patch introduces clients to the telemetry API.
> 
> When a client makes a connection through the initial telemetry
> socket, they can send a message through the socket to be
> parsed. Register messages are expected through this socket, to
> enable clients to register and have a client socket setup for
> future communications.
> 
> A TAILQ is used to store all clients information. Using this, the
> client sockets are polled for messages, which will later be parsed
> and dealt with accordingly.
> 
> Functionality that make use of the client sockets were introduced
> in this patch also, such as writing to client sockets, and sending
> error responses.
> 
> Signed-off-by: Ciara Power <ciara.power@intel.com>
> Signed-off-by: Brian Archbold <brian.archbold@intel.com>

Rather than using the rather heavyweight jansson library and creating
an additional dependency on an external library; may I recommend reusing
the json_writer library (I wrote) that is part of iproute2 and much
simpler.

   https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/json_writer.c
  
Hunt, David Aug. 28, 2018, 3:26 p.m. UTC | #2
On 24/8/2018 12:27 AM, Stephen Hemminger wrote:
> On Thu, 23 Aug 2018 13:08:05 +0100
> Ciara Power <ciara.power@intel.com> wrote:
>
>> This patch introduces clients to the telemetry API.
>>
>> When a client makes a connection through the initial telemetry
>> socket, they can send a message through the socket to be
>> parsed. Register messages are expected through this socket, to
>> enable clients to register and have a client socket setup for
>> future communications.
>>
>> A TAILQ is used to store all clients information. Using this, the
>> client sockets are polled for messages, which will later be parsed
>> and dealt with accordingly.
>>
>> Functionality that make use of the client sockets were introduced
>> in this patch also, such as writing to client sockets, and sending
>> error responses.
>>
>> Signed-off-by: Ciara Power <ciara.power@intel.com>
>> Signed-off-by: Brian Archbold <brian.archbold@intel.com>
> Rather than using the rather heavyweight jansson library and creating
> an additional dependency on an external library; may I recommend reusing
> the json_writer library (I wrote) that is part of iproute2 and much
> simpler.
>
>     https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/json_writer.c

Hi Stephen, Ciara,

I'm about to push another patchset to the mailing list in the next few 
days which
also makes use of Jansson. I'm parsing an incoming JSON string 
containing power
management info. The Jansson package comes pre-installed in many 
operating systems,
although you do indeed need to install libjansson-dev to build against it.
I would certainly like to see the community accept its use.

Regards,
Dave.
  
Van Haaren, Harry Aug. 28, 2018, 5:09 p.m. UTC | #3
> -----Original Message-----
> From: Hunt, David
> Sent: Tuesday, August 28, 2018 4:27 PM
> To: Stephen Hemminger <stephen@networkplumber.org>; Power, Ciara
> <ciara.power@intel.com>
> Cc: Van Haaren, Harry <harry.van.haaren@intel.com>; Archbold, Brian
> <brian.archbold@intel.com>; Kenny, Emma <emma.kenny@intel.com>; dev@dpdk.org
> Subject: Re: [dpdk-dev] [PATCH 03/11] telemetry: add client feature and
> sockets
> 
> 
> On 24/8/2018 12:27 AM, Stephen Hemminger wrote:
> > On Thu, 23 Aug 2018 13:08:05 +0100
> > Ciara Power <ciara.power@intel.com> wrote:
> >
> >> This patch introduces clients to the telemetry API.
> >>
> >> When a client makes a connection through the initial telemetry
> >> socket, they can send a message through the socket to be
> >> parsed. Register messages are expected through this socket, to
> >> enable clients to register and have a client socket setup for
> >> future communications.
> >>
> >> A TAILQ is used to store all clients information. Using this, the
> >> client sockets are polled for messages, which will later be parsed
> >> and dealt with accordingly.
> >>
> >> Functionality that make use of the client sockets were introduced
> >> in this patch also, such as writing to client sockets, and sending
> >> error responses.
> >>
> >> Signed-off-by: Ciara Power <ciara.power@intel.com>
> >> Signed-off-by: Brian Archbold <brian.archbold@intel.com>
> > Rather than using the rather heavyweight jansson library and creating
> > an additional dependency on an external library; may I recommend reusing
> > the json_writer library (I wrote) that is part of iproute2 and much
> > simpler.
> >
> >
> https://git.kernel.org/pub/scm/network/iproute2/iproute2.git/tree/lib/json_w
> riter.c

Although I tend to prefer lightweight or dependency-free software, in this
case I think the requirements (parsing and writing JSON) is more than we
should implement our own version of in DPDK.

Jansson provides all we need, and as Dave notes below it is a common
library with good packaging support already.

With the Meson build system, I'd like to see it being picked up dynamically
and compiling in automatically when available.

For the existing Make system, I'm OK with Telemetry being off by default
and users switching it on if they wish to accept Jansson dep and gain the
telemetry functionality.


> I'm about to push another patchset to the mailing list in the next few
> days which
> also makes use of Jansson. I'm parsing an incoming JSON string
> containing power
> management info. The Jansson package comes pre-installed in many
> operating systems,
> although you do indeed need to install libjansson-dev to build against it.
> I would certainly like to see the community accept its use.
  

Patch

diff --git a/lib/librte_telemetry/meson.build b/lib/librte_telemetry/meson.build
index 7716076..0ccfa36 100644
--- a/lib/librte_telemetry/meson.build
+++ b/lib/librte_telemetry/meson.build
@@ -5,3 +5,5 @@  sources = files('rte_telemetry.c')
 headers = files('rte_telemetry.h', 'rte_telemetry_internal.h')
 deps += ['metrics', 'ethdev']
 cflags += '-DALLOW_EXPERIMENTAL_API'
+jansson = cc.find_library('jansson', required: true)
+ext_deps += jansson
diff --git a/lib/librte_telemetry/rte_telemetry.c b/lib/librte_telemetry/rte_telemetry.c
index f984929..e9dd022 100644
--- a/lib/librte_telemetry/rte_telemetry.c
+++ b/lib/librte_telemetry/rte_telemetry.c
@@ -6,6 +6,7 @@ 
 #include <fcntl.h>
 #include <sys/socket.h>
 #include <sys/un.h>
+#include <jansson.h>
 
 #include <rte_eal.h>
 #include <rte_ethdev.h>
@@ -15,6 +16,8 @@ 
 #include "rte_telemetry.h"
 #include "rte_telemetry_internal.h"
 
+#define BUF_SIZE 1024
+#define ACTION_POST 1
 #define SLEEP_TIME 10
 
 #define DEFAULT_DPDK_PATH "/var/run/.rte_telemetry"
@@ -36,6 +39,88 @@  rte_telemetry_check_port_activity(int port_id)
 	return 0;
 }
 
+int32_t
+rte_telemetry_write_to_socket(struct telemetry_impl *telemetry,
+	const char *json_string)
+{
+	int ret;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Error, could not initialise "
+			"TELEMETRY_API\n");
+		return -1;
+	}
+	if (!telemetry->request_client) {
+		TELEMETRY_LOG_ERR("Error - No client has been chosen to"
+			" write to\n");
+		return -1;
+	}
+	if (!json_string) {
+		TELEMETRY_LOG_ERR("Error - Invalid JSON string!\n");
+		return -1;
+	}
+	ret = send(telemetry->request_client->fd,
+			 json_string, strlen(json_string), 0);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Failed to write to socket for "
+			"client: %s\n", telemetry->request_client->file_path);
+		return -1;
+	}
+	return 0;
+}
+
+int32_t
+rte_telemetry_send_error_response(struct telemetry_impl *telemetry,
+	int error_type)
+{
+	int ret;
+	const char *status_code, *json_buffer;
+	json_t *root;
+
+	if (error_type == -EPERM)
+		status_code = "Status Error: Unknown";
+	else if (error_type == -EINVAL)
+		status_code = "Status Error: Invalid Argument 404";
+	else if (error_type == -ENOMEM)
+		status_code = "Status Error: Memory Allocation Error";
+	else {
+		TELEMETRY_LOG_ERR("Error - invalid error type\n");
+		return -EINVAL;
+	}
+
+	root = json_object();
+
+	if (!root) {
+		TELEMETRY_LOG_ERR("Error - Could not create root JSON "
+			"object\n");
+		return -EPERM;
+	}
+
+	ret = json_object_set_new(root, "status_code",
+		json_string(status_code));
+
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Status code field cannot be set\n");
+		return -EPERM;
+	}
+
+	ret = json_object_set_new(root, "data", json_null());
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Data field cannot be set\n");
+		return -EPERM;
+	}
+
+	json_buffer = json_dumps(root, JSON_INDENT(2));
+	json_decref(root);
+
+	ret = rte_telemetry_write_to_socket(telemetry, json_buffer);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Could not write to socket\n");
+		return -EPERM;
+	}
+	return 0;
+}
+
 static int32_t
 rte_telemetry_reg_ethdev_to_metrics(uint16_t port_id)
 {
@@ -127,6 +212,31 @@  rte_telemetry_initial_accept(struct telemetry_impl *telemetry)
 }
 
 static int32_t
+rte_telemetry_read_client(struct telemetry_impl *telemetry)
+{
+	char buf[BUF_SIZE];
+	int buffer_read = 0;
+	errno = 0;
+
+	buffer_read = read(telemetry->accept_fd, buf, BUF_SIZE-1);
+	buf[buffer_read] = '\0';
+	if (buffer_read == -1) {
+		TELEMETRY_LOG_ERR("Error - Read error\n");
+		return -1;
+	} else if (buffer_read == 0) {
+		close(telemetry->accept_fd);
+		telemetry->accept_fd = 0;
+	} else {
+		int ret = rte_telemetry_parse_client_message(telemetry, buf);
+		if (ret < 0)
+			TELEMETRY_LOG_WARN("Warning - parse message failed\n");
+		close(telemetry->accept_fd);
+		telemetry->accept_fd = 0;
+	}
+	return 0;
+}
+
+static int32_t
 rte_telemetry_accept_new_client(struct telemetry_impl *telemetry)
 {
 	int ret;
@@ -148,6 +258,28 @@  rte_telemetry_accept_new_client(struct telemetry_impl *telemetry)
 				return -1;
 			}
 		}
+	} else {
+		ret = rte_telemetry_read_client(telemetry);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Error - failed to read socket "
+				"buffer\n");
+			return -1;
+		}
+	}
+	return 0;
+}
+
+static int32_t
+rte_telemetry_read_client_sockets(struct telemetry_impl *telemetry)
+{
+	telemetry_client *client;
+	TAILQ_FOREACH(client, &telemetry->client_list_head, client_list) {
+		char client_buf[BUF_SIZE];
+		int bytes = read(client->fd, client_buf, BUF_SIZE-1);
+		client_buf[bytes] = '\0';
+		if (bytes > 0) {
+			telemetry->request_client = client;
+		}
 	}
 	return 0;
 }
@@ -169,6 +301,12 @@  rte_telemetry_run(void *userdata)
 			"failed\n");
 		return -1;
 	}
+
+	ret = rte_telemetry_read_client_sockets(telemetry);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - client socket read failed\n");
+		return -1;
+	}
 	return 0;
 }
 
@@ -267,6 +405,7 @@  rte_telemetry_init(uint32_t socket_id)
 			TELEMETRY_LOG_ERR("Error - TELEMETRY cleanup failed\n");
 		return -EPERM;
 	}
+	TAILQ_INIT(&static_telemetry->client_list_head);
 
 	pthread_attr_init(&attr);
 	ret = rte_ctrl_thread_create(&static_telemetry->thread_id,
@@ -282,12 +421,38 @@  rte_telemetry_init(uint32_t socket_id)
 	return 0;
 }
 
+static int32_t
+rte_telemetry_client_cleanup(struct telemetry_client *client)
+{
+	int ret;
+
+	ret = close(client->fd);
+	free(client->file_path);
+	free(client);
+
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Close client socket failed\n");
+		return -EPERM;
+	}
+	return 0;
+}
+
 int32_t
 rte_telemetry_cleanup(void)
 {
 	int ret;
 	struct telemetry_impl *telemetry = static_telemetry;
 
+	telemetry_client *client, *temp_client;
+	TAILQ_FOREACH_SAFE(client, &telemetry->client_list_head, client_list,
+		temp_client) {
+		TAILQ_REMOVE(&telemetry->client_list_head, client, client_list);
+		ret = rte_telemetry_client_cleanup(client);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Error - Client cleanup failed\n");
+			return -EPERM;
+		}
+	}
 	ret = close(telemetry->server_fd);
 	if (ret < 0) {
 		TELEMETRY_LOG_ERR("Error - Close TELEMETRY socket failed\n");
@@ -301,6 +466,199 @@  rte_telemetry_cleanup(void)
 	return 0;
 }
 
+int32_t
+rte_telemetry_unregister_client(struct telemetry_impl *telemetry,
+	const char *client_path)
+{
+	int ret;
+
+	if (!telemetry) {
+		TELEMETRY_LOG_WARN("Warning - TELEMETRY is not initialised\n");
+		return -ENODEV;
+	}
+
+	if (!client_path) {
+		TELEMETRY_LOG_ERR("Error - Invalid client path\n");
+		goto einval_fail;
+	}
+
+	if (TAILQ_EMPTY(&telemetry->client_list_head)) {
+		TELEMETRY_LOG_ERR("Error - there are no clients currently "
+			"registered\n");
+		return -EPERM;
+	}
+	telemetry_client *client, *temp_client;
+	TAILQ_FOREACH_SAFE(client, &telemetry->client_list_head, client_list,
+			temp_client) {
+		if (strcmp(client_path, client->file_path) == 0) {
+			TAILQ_REMOVE(&telemetry->client_list_head, client,
+				client_list);
+			ret = rte_telemetry_client_cleanup(client);
+			if (ret < 0) {
+				TELEMETRY_LOG_ERR("Error - Client cleanup "
+					"failed\n");
+				return -EPERM;
+			}
+			return 0;
+		}
+	}
+	TELEMETRY_LOG_WARN("Warning - couldn't find client, possibly not "
+		"registered yet.\n");
+	return -1;
+
+einval_fail:
+	ret = rte_telemetry_send_error_response(telemetry, -EINVAL);
+	if (ret < 0)
+		TELEMETRY_LOG_ERR("Error - Could not send error\n");
+	return -EINVAL;
+}
+
+int32_t
+rte_telemetry_register_client(struct telemetry_impl *telemetry,
+	const char *client_path)
+{
+	int ret, fd;
+	if (!telemetry) {
+		TELEMETRY_LOG_ERR("Error - could not initialize "
+			"TELEMETRY API\n");
+		return -ENODEV;
+	}
+
+	if (!client_path) {
+		TELEMETRY_LOG_ERR("Error - Invalid client path\n");
+		return -EINVAL;
+	}
+
+	telemetry_client *client;
+	TAILQ_FOREACH(client, &telemetry->client_list_head, client_list) {
+		if (strcmp(client_path, client->file_path) == 0) {
+			TELEMETRY_LOG_WARN("Warning - '%s' already "
+				"registered\n", client_path);
+			return -EINVAL;
+		}
+	}
+	fd = socket(AF_UNIX, SOCK_STREAM, 0);
+	if (fd == -1) {
+		TELEMETRY_LOG_ERR("Error - Client socket error\n");
+		return -EACCES;
+	}
+	ret = rte_telemetry_set_socket_nonblock(fd);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - Could not set socket to NONBLOCK\n");
+		return -EPERM;
+	}
+
+	struct sockaddr_un addrs = {0};
+	addrs.sun_family = AF_UNIX;
+	strlcpy(addrs.sun_path, client_path, sizeof(addrs.sun_path));
+	telemetry_client *new_client =
+		(telemetry_client *)malloc(sizeof(telemetry_client));
+	new_client->file_path = strdup(client_path);
+	new_client->fd = fd;
+
+	if (connect(fd, (struct sockaddr *)&addrs, sizeof(addrs)) == -1) {
+		TELEMETRY_LOG_ERR("Error - TELEMETRY client connect to %s "
+			"didn't work\n", client_path);
+		ret = rte_telemetry_client_cleanup(new_client);
+		if (ret < 0) {
+			TELEMETRY_LOG_ERR("Error - Client cleanup failed\n");
+			return -EPERM;
+		}
+		return -EINVAL;
+	}
+	TAILQ_INSERT_HEAD(&telemetry->client_list_head, new_client,
+		client_list);
+
+	return 0;
+}
+
+int32_t
+rte_telemetry_parse_client_message(struct telemetry_impl *telemetry, char *buf)
+{
+	int ret;
+	json_error_t error;
+	json_t *root = json_loads(buf, 0, &error);
+
+	if (!root) {
+		TELEMETRY_LOG_WARN("Warning - Could not load JSON object from "
+			"data passed in : %s\n", error.text);
+		goto fail;
+	} else if (!json_is_object(root)) {
+		TELEMETRY_LOG_WARN("Warning - JSON Request is not a JSON "
+			"object\n");
+		json_decref(root);
+		goto fail;
+	}
+
+	json_t *action = json_object_get(root, "action");
+	if (!action) {
+		TELEMETRY_LOG_WARN("Warning - Request does not have action "
+			"field\n");
+		goto fail;
+	} else if (!json_is_integer(action)) {
+		TELEMETRY_LOG_WARN("Warning - Action value is not an "
+			"integer\n");
+		goto fail;
+	}
+
+	json_t *command = json_object_get(root, "command");
+	if (!command) {
+		TELEMETRY_LOG_WARN("Warning - Request does not have command "
+			"field\n");
+		goto fail;
+	} else if (!json_is_string(command)) {
+		TELEMETRY_LOG_WARN("Warning - Command value is not a string\n");
+		goto fail;
+	}
+
+	int action_int = json_integer_value(action);
+	if (action_int != ACTION_POST) {
+		TELEMETRY_LOG_WARN("Warning - Invalid action code\n");
+		goto fail;
+	}
+
+	const char *command_string = json_string_value(command);
+	if (strcmp(command_string, "clients") != 0) {
+		TELEMETRY_LOG_WARN("Warning - Invalid command\n");
+		goto fail;
+	}
+
+	json_t *data = json_object_get(root, "data");
+	if (!data) {
+		TELEMETRY_LOG_WARN("Warning - Request does not have data "
+			"field\n");
+		goto fail;
+	}
+
+	json_t *client_path = json_object_get(data, "client_path");
+	if (!client_path) {
+		TELEMETRY_LOG_WARN("Warning - Request does not have "
+			"client_path field\n");
+		goto fail;
+	}
+	if (!json_is_string(client_path)) {
+		TELEMETRY_LOG_WARN("Warning - client_path value is not a "
+			"string\n");
+		goto fail;
+	}
+
+	const char *client_path_string = json_string_value(client_path);
+
+	ret = rte_telemetry_register_client(telemetry, client_path_string);
+	if (ret < 0) {
+		TELEMETRY_LOG_ERR("Error - could not register client\n");
+		telemetry->register_fail_count++;
+		goto fail;
+	}
+
+	return 0;
+
+fail:
+	TELEMETRY_LOG_WARN("Warning - Client attempted to register with "
+		"invalid message\n");
+	return -1;
+}
+
 int telemetry_log_level;
 RTE_INIT(rte_telemetry_log_init);
 
diff --git a/lib/librte_telemetry/rte_telemetry_internal.h b/lib/librte_telemetry/rte_telemetry_internal.h
index 569d56a..e3292cf 100644
--- a/lib/librte_telemetry/rte_telemetry_internal.h
+++ b/lib/librte_telemetry/rte_telemetry_internal.h
@@ -3,6 +3,7 @@ 
  */
 
 #include <rte_log.h>
+#include <rte_tailq.h>
 
 #ifndef _RTE_TELEMETRY_INTERNAL_H_
 #define _RTE_TELEMETRY_INTERNAL_H_
@@ -23,6 +24,12 @@  extern int telemetry_log_level;
 #define TELEMETRY_LOG_INFO(fmt, args...) \
 	TELEMETRY_LOG(INFO, fmt, ## args)
 
+typedef struct telemetry_client {
+	char *file_path;
+	int fd;
+	TAILQ_ENTRY(telemetry_client) client_list;
+} telemetry_client;
+
 typedef struct telemetry_impl {
 	int accept_fd;
 	int server_fd;
@@ -31,6 +38,24 @@  typedef struct telemetry_impl {
 	uint32_t socket_id;
 	int reg_index;
 	int metrics_register_done;
+	TAILQ_HEAD(, telemetry_client) client_list_head;
+	struct telemetry_client *request_client;
+	int register_fail_count;
 } telemetry_impl;
 
+int32_t
+rte_telemetry_parse_client_message(struct telemetry_impl *telemetry, char *buf);
+
+int32_t
+rte_telemetry_send_error_response(struct telemetry_impl *telemetry,
+	int error_type);
+
+int32_t
+rte_telemetry_register_client(struct telemetry_impl *telemetry,
+	const char *client_path);
+
+int32_t
+rte_telemetry_unregister_client(struct telemetry_impl *telemetry,
+	const char *client_path);
+
 #endif
diff --git a/mk/rte.app.mk b/mk/rte.app.mk
index 8551191..1963812 100644
--- a/mk/rte.app.mk
+++ b/mk/rte.app.mk
@@ -80,7 +80,7 @@  _LDLIBS-$(CONFIG_RTE_LIBRTE_SECURITY)       += -lrte_security
 _LDLIBS-$(CONFIG_RTE_LIBRTE_COMPRESSDEV)    += -lrte_compressdev
 _LDLIBS-$(CONFIG_RTE_LIBRTE_EVENTDEV)       += -lrte_eventdev
 _LDLIBS-$(CONFIG_RTE_LIBRTE_RAWDEV)         += -lrte_rawdev
-_LDLIBS-$(CONFIG_RTE_LIBRTE_TELEMETRY)      += -lrte_metrics -lrte_telemetry
+_LDLIBS-$(CONFIG_RTE_LIBRTE_TELEMETRY)      += -lrte_metrics -lrte_telemetry -ljansson
 _LDLIBS-$(CONFIG_RTE_LIBRTE_TIMER)          += -lrte_timer
 _LDLIBS-$(CONFIG_RTE_LIBRTE_MEMPOOL)        += -lrte_mempool
 _LDLIBS-$(CONFIG_RTE_DRIVER_MEMPOOL_RING)   += -lrte_mempool_ring