From 8ec8e8a051c57230443e97279b274fcb620e9540 Mon Sep 17 00:00:00 2001
From: tv <tv@krebsco.de>
Date: Sun, 29 Dec 2019 15:50:32 +0100
Subject: [WIP] prometheus module (with prometheus_vty)

---
 src/vty/Makefile.am      |   3 +-
 src/vty/prometheus_vty.c | 679 +++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 681 insertions(+), 1 deletion(-)
 create mode 100644 src/vty/prometheus_vty.c

(limited to 'src/vty')

diff --git a/src/vty/Makefile.am b/src/vty/Makefile.am
index abed92ac..450549d0 100644
--- a/src/vty/Makefile.am
+++ b/src/vty/Makefile.am
@@ -12,7 +12,8 @@ lib_LTLIBRARIES = libosmovty.la
 libosmovty_la_SOURCES = buffer.c command.c vty.c vector.c utils.c \
 			telnet_interface.c logging_vty.c stats_vty.c \
 			fsm_vty.c talloc_ctx_vty.c \
-			tdef_vty.c
+			tdef_vty.c \
+			prometheus_vty.c
 libosmovty_la_LDFLAGS = -version-info $(LIBVERSION) -no-undefined
 libosmovty_la_LIBADD = $(top_builddir)/src/libosmocore.la $(TALLOC_LIBS)
 endif
diff --git a/src/vty/prometheus_vty.c b/src/vty/prometheus_vty.c
new file mode 100644
index 00000000..89652444
--- /dev/null
+++ b/src/vty/prometheus_vty.c
@@ -0,0 +1,679 @@
+//#include <sys/socket.h>
+//#include <netinet/in.h>
+#include <errno.h>
+//#include <stdlib.h>
+//#include <stdio.h>
+#include <stdarg.h> // TODO killme?
+#include <string.h>
+#include <unistd.h>
+#include <termios.h> // TODO killme
+//
+//#include <osmocom/core/msgb.h>
+//XXX[osmo_counter]#include <osmocom/core/linuxlist.h>
+//XXX[osmo_counter]#include <osmocom/core/counter.h>
+#include <osmocom/core/socket.h>
+#include <osmocom/core/stat_item.h>
+#include <osmocom/core/talloc.h>
+#include <osmocom/core/logging.h>
+#include <osmocom/core/signal.h>
+#include <osmocom/core/select.h>
+//
+#include <osmocom/vty/prometheus.h>
+#include <osmocom/vty/buffer.h>
+#include <osmocom/vty/command.h>
+
+#include <osmocom/core/rate_ctr.h>
+
+
+/* per connection data */
+LLIST_HEAD(prom_active_connections);
+
+static void *prom_ctx;
+
+/* Vector which store each vty structure. */
+static vector prom_vtyvec;
+
+/* per network data */
+static int prom_new_connection(struct osmo_fd *fd, unsigned int what);
+
+static struct osmo_fd server_socket = {
+	.when	    = OSMO_FD_READ,
+	.cb	    = prom_new_connection,
+	.priv_nr    = 0,
+};
+
+/*! Create new vty structure. */
+//struct prom_vty *vty_create (int vty_sock, void *priv)
+//
+/*! Create new vty structure. */
+// TODO extern struct host prom_host;
+
+/*! Return if this VTY is a shell or not */
+//int prom_vty_shell(struct prom_vty *vty)
+//{
+//	return vty->type == VTY_SHELL ? 1 : 0;
+//}
+
+
+/*! close a telnet connection */
+int prom_telnet_close_client(struct osmo_fd *fd)
+{
+	struct prom_connection *conn = (struct prom_connection*)fd->data;
+	char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
+	int rc;
+
+	/* FIXME: getsockname() always fails: "Bad file descriptor" */
+	rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, fd->fd);
+	LOGP(DLGLOBAL, LOGL_INFO, "Closing telnet connection %s\n",
+	     (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);
+
+	close(fd->fd);
+	osmo_fd_unregister(fd);
+
+	if (conn->dbg) {
+		log_del_target(conn->dbg);
+		talloc_free(conn->dbg);
+	}
+
+	llist_del(&conn->entry);
+	talloc_free(conn);
+	return 0;
+}
+
+
+
+/*! callback from core VTY code about VTY related events */
+void prom_vty_event(enum event event, int sock, struct prom_vty *vty)
+{
+	struct vty_signal_data sig_data;
+	struct prom_connection *connection = vty->priv;
+	struct osmo_fd *bfd;
+
+	//if (vty->type != VTY_TERM)
+	//	return;
+
+	sig_data.event = event;
+	sig_data.sock = sock;
+	sig_data.vty = vty;
+	osmo_signal_dispatch(SS_L_VTY, S_VTY_EVENT, &sig_data);
+
+	if (!connection)
+		return;
+
+	bfd = &connection->fd;
+
+	switch (event) {
+	case VTY_READ:
+		bfd->when |= OSMO_FD_READ;
+		break;
+	case VTY_WRITE:
+		bfd->when |= OSMO_FD_WRITE;
+		break;
+	case VTY_CLOSED:
+		/* vty layer is about to free() vty */
+		prom_telnet_close_client(bfd);
+		break;
+	default:
+		break;
+	}
+}
+
+int prom_vty_out_va(struct prom_vty *vty, const char *format, va_list ap)
+{
+	int len = 0;
+	int size = 1024;
+	char buf[1024];
+	char *p = NULL;
+
+	// TODO don't use prom_vty_shell
+	//if (prom_vty_shell(vty)) {
+	//	vprintf(format, ap);
+	//} else {
+		va_list args;
+		/* Try to write to initial buffer.  */
+		va_copy(args, ap);
+		len = vsnprintf(buf, sizeof buf, format, args);
+		va_end(args);
+
+		/* Initial buffer is not enough.  */
+		if (len < 0 || len >= size) {
+			while (1) {
+				if (len > -1)
+					size = len + 1;
+				else
+					size = size * 2;
+
+				p = talloc_realloc_size(vty, p, size);
+				if (!p)
+					return -1;
+
+				va_copy(args, ap);
+				len = vsnprintf(p, size, format, args);
+				va_end(args);
+
+				if (len > -1 && len < size)
+					break;
+			}
+		}
+
+		/* When initial buffer is enough to store all output.  */
+		if (!p)
+			p = buf;
+
+		/* Pointer p must point out buffer. */
+		buffer_put(vty->obuf, (unsigned char *) p, len);
+
+		/* If p is not different with buf, it is allocated buffer.  */
+		if (p != buf)
+			talloc_free(p);
+	//}
+
+	prom_vty_event(VTY_WRITE, vty->fd, vty);
+
+	return len;
+}
+
+/*! VTY standard output function
+ *  \param[in] vty VTY to which we should print
+ *  \param[in] format variable-length format string
+ */
+int prom_vty_out(struct prom_vty *vty, const char *format, ...)
+{
+	va_list args;
+	int rc;
+	va_start(args, format);
+	rc = prom_vty_out_va(vty, format, args);
+	va_end(args);
+	return rc;
+}
+
+
+/*! Initialize telnet based VTY interface listening to 127.0.0.1
+ *  \param[in] tall_ctx \ref talloc context
+ *  \param[in] priv private data to be passed to callback
+ *  \param[in] port TCP port number to bind to
+ */
+//int telnet_init(void *tall_ctx, void *priv, int port)
+//{
+//	return telnet_init_dynif(tall_ctx, priv, "127.0.0.1", port);
+//}
+
+int osmo_prom_init(void *ctx, int port)
+{
+	return osmo_prom_init_dynif(ctx, "127.0.0.1", port);
+}
+
+/*! Initialize telnet based VTY interface
+ *  \param[in] tall_ctx \ref talloc context
+ *  \param[in] priv private data to be passed to callback
+ *  \param[in] ip IP to listen to ('::1' for localhost, '::0' for all, ...)
+ *  \param[in] port TCP port number to bind to
+ */
+//int telnet_init_dynif(void *tall_ctx, void *priv, const char *ip, int port)
+
+int osmo_prom_init_dynif(void *ctx, const char *host, int port)
+{
+	int rc;
+
+	prom_ctx = talloc_named_const(ctx, 1,
+			"prometheus_connection");
+
+	rc = osmo_sock_init_ofd(
+			&server_socket,
+			AF_UNSPEC, SOCK_STREAM, IPPROTO_TCP,
+			host, port, OSMO_SOCK_F_BIND
+			);
+
+	server_socket.data = NULL; // könnte priv
+
+	if (rc < 0) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "Cannot bind telnet at %s %d\n",
+		     host, port);
+		return -1;
+	}
+
+	prom_vtyvec = vector_init(VECTOR_MIN_SIZE);
+
+	LOGP(DLGLOBAL, LOGL_NOTICE, "Available via HTTP %s %d\n", host, port);
+	return 0;
+}
+
+/*! Unlock the configuration from a given VTY
+ *  \param[in] vty VTY from which the configuration shall be unlocked
+ *  \returns 0 in case of success
+ */
+int prom_vty_config_unlock(struct prom_vty *vty)
+{
+	//if (vty_config == 1 && vty->config == 1) {
+	//	vty->config = 0;
+	//	vty_config = 0;
+	//}
+	return vty->config;
+}
+
+
+void prom_vty_close(struct prom_vty *vty)
+{
+	int i;
+
+	/* VTY_CLOSED is handled by the telnet_interface */
+	prom_vty_event(VTY_CLOSED, vty->fd, vty);
+
+	if (vty->obuf)  {
+		/* Flush buffer. */
+		buffer_flush_all(vty->obuf, vty->fd);
+
+		/* Free input buffer. */
+		buffer_free(vty->obuf);
+		vty->obuf = NULL;
+	}
+
+	/* Free command history. */
+	for (i = 0; i < PROM_VTY_MAXHIST; i++)
+		if (vty->hist[i])
+			talloc_free(vty->hist[i]);
+
+	/* Unset vector. */
+	vector_unset(prom_vtyvec, vty->fd);
+
+	/* Close socket (ignore standard I/O streams). */
+	if (vty->fd > 2) {
+		close(vty->fd);
+		vty->fd = -1;
+	}
+
+	if (vty->buf) {
+		talloc_free(vty->buf);
+		vty->buf = NULL;
+	}
+
+	/* Check configure. */
+	prom_vty_config_unlock(vty);
+
+	/* OK free vty. */
+	talloc_free(vty);
+}
+
+//XXX[osmo_counter]static int osmo_counter_handler(struct osmo_counter *counter, void *vty_)
+//XXX[osmo_counter]{
+//XXX[osmo_counter]	struct vty *vty = vty_;
+//XXX[osmo_counter]	const char *description = counter->description;
+//XXX[osmo_counter]
+//XXX[osmo_counter]	if (!counter->description)
+//XXX[osmo_counter]		description = counter->name;
+//XXX[osmo_counter]
+//XXX[osmo_counter]	//vty_out(vty, " %s%s: %8lu%s",
+//XXX[osmo_counter]	//	vctx->prefix, description,
+//XXX[osmo_counter]	//	osmo_counter_get(counter), VTY_NEWLINE);
+//XXX[osmo_counter]
+//XXX[osmo_counter]	char buf[1024];
+//XXX[osmo_counter]
+//XXX[osmo_counter]	// TODO only export current value and let graphing tools sum it up?
+//XXX[osmo_counter]
+//XXX[osmo_counter]	/* TODO ensure name contains only [a-zA-Z0-] */
+//XXX[osmo_counter]	const char *name = description;
+//XXX[osmo_counter]
+//XXX[osmo_counter]	snprintf(buf, sizeof buf,
+//XXX[osmo_counter]		"%s %d\n",
+//XXX[osmo_counter]		name,
+//XXX[osmo_counter]		osmo_counter_get(counter)
+//XXX[osmo_counter]	);
+//XXX[osmo_counter]
+//XXX[osmo_counter]	prom_vty_out(vty, "%X\r\n", strlen(buf));
+//XXX[osmo_counter]	prom_vty_out(vty, "%s\r\n", buf);
+//XXX[osmo_counter]
+//XXX[osmo_counter]	return 0;
+//XXX[osmo_counter]}
+
+
+void sanitize_name(char *name) {
+	for (char c; (c = *name) != '\0'; name++) {
+		if ('a' <= c && c <= 'z') continue;
+		if ('A' <= c && c <= 'Z') continue;
+		if ('0' <= c && c <= '9') continue;
+		if (c == ':' || c == '_') continue;
+		*name = '_';
+	}
+}
+
+
+static int rate_ctr_handler(
+	struct rate_ctr_group *ctrg, struct rate_ctr *ctr,
+	const struct rate_ctr_desc *desc, void *vty_)
+{
+	struct vty *vty = vty_;
+
+	char buf[1024];
+	char name[1024];
+
+	// TODO only export current value and let graphing tools sum it up?
+
+	strcpy(name, desc->name);
+	sanitize_name(name);
+
+	char *group = ctrg->desc->group_name_prefix;
+	//char intv_sec_name[1024];
+	//char intv_min_name[1024];
+	//char intv_hour_name[1024];
+	//char intv_day_name[1024];
+
+	//strcpy(intv_sec_name, name);
+	//strcpy(intv_min_name, name);
+	//strcpy(intv_hour_name, name);
+	//strcpy(intv_day_name, name);
+
+	//strcat(intv_sec_name, "_intv_sec");
+	//strcat(intv_min_name, "_intv_min");
+	//strcat(intv_hour_name, "_intv_hour");
+	//strcat(intv_day_name, "_intv_day");
+
+	snprintf(buf, sizeof buf,
+		//"%s %d\n"
+		//"%s %d\n"
+		//"%s %d\n"
+		//"%s %d\n"
+		"%s{group=\"%s\"} %d\n",
+		//intv_sec_name, ctr->intv[RATE_CTR_INTV_SEC].rate,
+		//intv_min_name, ctr->intv[RATE_CTR_INTV_MIN].rate,
+		//intv_hour_name, ctr->intv[RATE_CTR_INTV_HOUR].rate,
+		//intv_day_name, ctr->intv[RATE_CTR_INTV_DAY].rate
+		name, group, ctr->current
+	);
+
+	prom_vty_out(vty, "%X\r\n", strlen(buf));
+	prom_vty_out(vty, "%s\r\n", buf);
+
+	return 0;
+}
+
+static int rate_ctr_group_handler(struct rate_ctr_group *ctrg, void *vty_)
+{
+	struct vty *vty = vty_;
+
+	//TODO
+//	if (ctrg->idx)
+//		prom_vty_out(vty, "%s (%d):\r\n",
+//			ctrg->desc->group_description, ctrg->idx);
+//	else
+//		prom_vty_out(vty, "%s:\r\n",
+//			ctrg->desc->group_description);
+
+	rate_ctr_for_each_counter(ctrg, rate_ctr_handler, vty);
+
+	return 0;
+}
+
+static int osmo_stat_item_handler(
+	struct osmo_stat_item_group *statg, struct osmo_stat_item *item, void *vty_)
+{
+	struct vty *vty = vty_;
+	const char *unit =
+		item->desc->unit != OSMO_STAT_ITEM_NO_UNIT ?
+		item->desc->unit : "";
+
+	char buf[1024];
+	char name[1024];
+
+	//vty_out(vty, " %s%s: %8" PRIi32 " %s%s",
+	//	vctx->prefix, item->desc->description,
+	//	osmo_stat_item_get_last(item),
+	//	unit, VTY_NEWLINE);
+
+	strcpy(name, item->desc->name);
+	sanitize_name(name);
+
+	char *group = statg->desc->group_name_prefix;
+	int value = osmo_stat_item_get_last(item);
+	// TODO unit
+	snprintf(buf, sizeof buf, "%s{group=\"%s\",unit=\"%s\"} %d\n", name, group, unit, value);
+
+	prom_vty_out(vty, "%X\r\n", strlen(buf));
+	prom_vty_out(vty, "%s\r\n", buf);
+
+	return 0;
+}
+
+static int osmo_stat_item_group_handler(struct osmo_stat_item_group *statg, void *vty_)
+{
+	struct vty *vty = vty_;
+
+	//if (statg->idx)
+	//	vty_out(vty, "%s%s (%d):%s", vctx->prefix,
+	//		statg->desc->group_description, statg->idx,
+	//		VTY_NEWLINE);
+	//else
+	//	vty_out(vty, "%s%s:%s", vctx->prefix,
+	//		statg->desc->group_description, VTY_NEWLINE);
+
+	osmo_stat_item_for_each_item(statg, osmo_stat_item_handler, vty);
+
+	return 0;
+}
+
+/*! Read data via vty socket. */
+int prom_vty_read(struct prom_vty *vty)
+{
+	//int i;
+	int nbytes;
+	unsigned char buf[VTY_READ_BUFSIZ];
+
+	int vty_sock = vty->fd;
+
+	/* Read raw data from socket */
+	if ((nbytes = read(vty->fd, buf, VTY_READ_BUFSIZ)) <= 0) {
+		if (nbytes < 0) {
+			if (ERRNO_IO_RETRY(errno)) {
+				prom_vty_event(VTY_READ, vty_sock, vty);
+				return 0;
+			}
+		}
+		buffer_reset(vty->obuf);
+		vty->status = PROM_VTY_CLOSE;
+	}
+
+	//LOGP(DLGLOBAL, LOGL_NOTICE, "read %d byte(s)\n", nbytes);
+	prom_vty_out(vty,
+	    "HTTP/1.1 200 OK\r\n"
+	    "Content-Type: text/plain\r\n"
+	    "Transfer-Encoding: chunked\r\n"
+	    // "Content-Length: 6\r\n"
+	    "\r\n"
+	);
+	//vty->status = PROM_VTY_CLOSE;
+
+	//XXX[osmo_counter]osmo_counters_for_each(osmo_counter_handler, vty);
+	rate_ctr_for_each_group(rate_ctr_group_handler, vty);
+	osmo_stat_item_for_each_group(osmo_stat_item_group_handler, vty);
+
+	/* finish chunked transfer encoding */
+	prom_vty_out(vty, "0\r\n\r\n");
+
+	// TODO parse
+
+	/* Check status. */
+	if (vty->status == PROM_VTY_CLOSE) {
+		prom_vty_close(vty);
+		return -EBADF;
+	} else {
+		prom_vty_event(VTY_WRITE, vty_sock, vty);
+		prom_vty_event(VTY_READ, vty_sock, vty);
+	}
+
+	return 0;
+}
+
+
+static int client_data(struct osmo_fd *fd, unsigned int what)
+{
+	struct prom_connection *conn = fd->data;
+	int rc = 0;
+
+	if (what & OSMO_FD_READ) {
+		conn->fd.when &= ~OSMO_FD_READ;
+		// TODO feed server
+		rc = prom_vty_read(conn->vty);
+	}
+
+	/* vty might have been closed from vithin prom_vty_read() */
+	if (rc == -EBADF)
+		return rc;
+
+	if (what & OSMO_FD_WRITE) {
+		rc = buffer_flush_all(conn->vty->obuf, fd->fd);
+		if (rc == BUFFER_EMPTY)
+			conn->fd.when &= ~OSMO_FD_WRITE;
+	}
+
+	return rc;
+}
+
+
+/*! Allocate a new vty interface structure */
+struct prom_vty *prom_vty_new(void)
+{
+	struct prom_vty *new = talloc_zero(tall_vty_ctx, struct vty);
+
+	if (!new)
+		goto out;
+
+	INIT_LLIST_HEAD(&new->parent_nodes);
+
+	new->obuf = buffer_new(new, 0);	/* Use default buffer size. */
+	if (!new->obuf)
+		goto out_new;
+	new->buf = _talloc_zero(new, PROM_HTTP_BUFSIZ, "prom_vty_new->buf");
+	if (!new->buf)
+		goto out_obuf;
+
+	new->max = PROM_HTTP_BUFSIZ;
+	new->fd = -1;
+
+	return new;
+
+out_obuf:
+	buffer_free(new->obuf);
+out_new:
+	talloc_free(new);
+	new = NULL;
+out:
+	return new;
+}
+
+struct vty *
+prom_vty_create (int vty_sock, void *priv)
+{
+  struct prom_vty *vty;
+
+	struct termios t = {};
+
+	tcgetattr(vty_sock, &t);
+	cfmakeraw(&t);
+	tcsetattr(vty_sock, TCSANOW, &t);
+
+  /* Allocate new vty structure and set up default values. */
+  vty = prom_vty_new ();
+  vty->fd = vty_sock;
+  vty->priv = priv;
+  //vty->type = VTY_TERM; // TODO kill vty->type
+  //if (!password_check)
+  //  {
+  //    if (prom_host.advanced)
+  //      vty->node = ENABLE_NODE;
+  //    else
+  //      vty->node = VIEW_NODE;
+  //  }
+  //else
+  //  vty->node = AUTH_NODE;
+  vty->fail = 0;
+  vty->cp = 0;
+  // TODO vty_clear_buf (vty);
+  vty->length = 0;
+  memset (vty->hist, 0, sizeof (vty->hist));
+  vty->hp = 0;
+  vty->hindex = 0;
+  vector_set_index (prom_vtyvec, vty_sock, vty);
+  vty->status = PROM_VTY_NORMAL;
+  //if (prom_host.lines >= 0)
+  //  vty->lines = prom_host.lines;
+  //else
+  //  vty->lines = -1;
+
+  //if (password_check)
+  //  {
+  //    /* Vty is not available if password isn't set. */
+  //    if (prom_host.password == NULL && prom_host.password_encrypt == NULL)
+  //      {
+  //        prom_vty_out (vty, "Vty password is not set.%s", VTY_NEWLINE);
+  //        vty->status = PROM_VTY_CLOSE;
+  //        prom_vty_close (vty);
+  //        return NULL;
+  //      }
+  //  }
+
+  /* Say hello to the world. */
+  //prom_vty_hello (vty);
+  //if (password_check)
+  //  prom_vty_out (vty, "%sUser Access Verification%s%s", VTY_NEWLINE, VTY_NEWLINE, VTY_NEWLINE);
+
+  /* Setting up terminal. */
+  // TODO vty_will_echo (vty);
+  //TODO vty_will_suppress_go_ahead (vty);
+
+  // TODO vty_dont_linemode (vty);
+  // TODO vty_do_window_size (vty);
+  /* vty_dont_lflow_ahead (vty); */
+
+  // TODO vty_prompt (vty);
+
+  /* Add read/write thread. */
+  prom_vty_event (VTY_WRITE, vty_sock, vty);
+  prom_vty_event (VTY_READ, vty_sock, vty);
+
+  return vty;
+}
+
+
+static int prom_new_connection(struct osmo_fd *fd, unsigned int what)
+{
+	//LOGP(DLGLOBAL, LOGL_ERROR, "derp\n");
+	struct prom_connection *connection;
+	struct sockaddr_in sockaddr;
+	socklen_t len = sizeof(sockaddr);
+	int new_connection = accept(fd->fd, (struct sockaddr*)&sockaddr, &len);
+	char sock_name_buf[OSMO_SOCK_NAME_MAXLEN];
+	int rc;
+
+	if (new_connection < 0) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "HTTP accept failed\n");
+		return new_connection;
+	}
+
+	rc = osmo_sock_get_name_buf(sock_name_buf, OSMO_SOCK_NAME_MAXLEN, new_connection);
+	LOGP(DLGLOBAL, LOGL_INFO, "Accept()ed new HTTP connection %s\n",
+	     (rc <= 0) ? "r=NULL<->l=NULL" : sock_name_buf);
+
+	connection = talloc_zero(prom_ctx, struct prom_connection);
+	connection->priv = fd->data;
+	connection->fd.data = connection;
+	connection->fd.fd = new_connection;
+	connection->fd.when = OSMO_FD_READ;
+	connection->fd.cb = client_data;
+	rc = osmo_fd_register(&connection->fd);
+	if (rc < 0) {
+		talloc_free(connection);
+		return rc;
+	}
+	llist_add_tail(&connection->entry, &prom_active_connections);
+
+	// TODO create server
+	connection->vty = prom_vty_create(new_connection, connection);
+	if (!connection->vty) {
+		LOGP(DLGLOBAL, LOGL_ERROR, "couldn't create VTY\n");
+		/* prom_vty_create() is already closing the fd if it returns NULL */
+		talloc_free(connection);
+		return -1;
+	}
+
+	return 0;
+}
+
-- 
cgit v1.2.3