[6.0] 79bc5ace9 Add a cli send/expect facility to HAproxy support.

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Wed Oct 31 13:08:04 UTC 2018


commit 79bc5ace9396e6dba618bbd6e9a316703908ab50
Author: Poul-Henning Kamp <phk at FreeBSD.org>
Date:   Thu Aug 16 09:23:42 2018 +0000

    Add a cli send/expect facility to HAproxy support.
    
    Submitted by:   Frederic Lecaille <flecaille at haproxy.com>

diff --git a/bin/varnishtest/tests/h00001.vtc b/bin/varnishtest/tests/h00001.vtc
index 1b86d81a0..f8b98a2ec 100644
--- a/bin/varnishtest/tests/h00001.vtc
+++ b/bin/varnishtest/tests/h00001.vtc
@@ -30,3 +30,9 @@ client c1 -connect ${h1_fe1_sock} {
     expect resp.status == 200
     expect resp.body == "s1 >>> Hello world!"
 } -run
+
+haproxy h1 -cli {
+    send "show info"
+    expect ~ "Name: HAProxy"
+} -wait
+
diff --git a/bin/varnishtest/vtc_haproxy.c b/bin/varnishtest/vtc_haproxy.c
index a3e89c713..157d0e67c 100644
--- a/bin/varnishtest/vtc_haproxy.c
+++ b/bin/varnishtest/vtc_haproxy.c
@@ -34,12 +34,15 @@
 #include <stdlib.h>
 #include <string.h>
 #include <sys/stat.h> /* for MUSL (mode_t) */
+#include <sys/types.h>
+#include <sys/socket.h>
 #include <unistd.h>
 
 #include "vtc.h"
 
 #include "vfil.h"
 #include "vpf.h"
+#include "vre.h"
 #include "vtcp.h"
 #include "vtim.h"
 
@@ -50,6 +53,7 @@
 #define HAPROXY_EXPECT_EXIT	(128 + HAPROXY_SIGNAL)
 #define HAPROXY_GOOD_CONF	"Configuration file is valid"
 
+
 struct haproxy {
 	unsigned		magic;
 #define HAPROXY_MAGIC		0x8a45cf75
@@ -73,7 +77,10 @@ struct haproxy {
 	int			expect_signal;
 	int			its_dead_jim;
 
+	/* UNIX socket CLI. */
 	char			*cli_fn;
+	/* TCP socket CLI. */
+	struct haproxy_cli *cli;
 
 	char			*workdir;
 	struct vsb		*msgs;
@@ -82,6 +89,269 @@ struct haproxy {
 static VTAILQ_HEAD(, haproxy)	haproxies =
     VTAILQ_HEAD_INITIALIZER(haproxies);
 
+struct haproxy_cli {
+	unsigned		magic;
+#define HAPROXY_CLI_MAGIC	0xb09a4ed8
+	struct vtclog		*vl;
+	char			running;
+
+	char			*spec;
+
+	int			sock;
+	char			connect[256];
+
+	pthread_t		tp;
+	size_t			txbuf_sz;
+	char			*txbuf;
+	size_t			rxbuf_sz;
+	char			*rxbuf;
+
+	double			timeout;
+};
+
+/**********************************************************************
+ * Socket connect (same as client_tcp_connect()).
+ */
+
+static int
+haproxy_cli_tcp_connect(struct vtclog *vl, const char *addr, double tmo,
+    const char **errp)
+{
+	int fd;
+	char mabuf[32], mpbuf[32];
+
+	fd = VTCP_open(addr, NULL, tmo, errp);
+	if (fd < 0)
+		return fd;
+	VTCP_myname(fd, mabuf, sizeof mabuf, mpbuf, sizeof mpbuf);
+	vtc_log(vl, 3,
+	    "CLI connected fd %d from %s %s to %s", fd, mabuf, mpbuf, addr);
+	return fd;
+}
+
+/*
+ * SECTION: haproxy.cli haproxy CLI Specification
+ * SECTION: haproxy.cli.send
+ * send STRING
+ *         Push STRING on the CLI connection. STRING will be terminated by an
+ *         end of line character (\n).
+ */
+static void v_matchproto_(cmd_f)
+cmd_haproxy_cli_send(CMD_ARGS)
+{
+	struct vsb *vsb;
+	struct haproxy_cli *hc;
+	ssize_t wr;
+
+	(void)cmd;
+	(void)vl;
+	CAST_OBJ_NOTNULL(hc, priv, HAPROXY_CLI_MAGIC);
+	AZ(strcmp(av[0], "send"));
+	AN(av[1]);
+	AZ(av[2]);
+
+	vsb = VSB_new_auto();
+	AN(vsb);
+	AZ(VSB_cat(vsb, av[1]));
+	AZ(VSB_cat(vsb, "\n"));
+	AZ(VSB_finish(vsb));
+	if (hc->sock == -1) {
+		int fd;
+		const char *err;
+		struct vsb *vsb_connect;
+
+		vsb_connect = macro_expand(hc->vl, hc->connect);
+		AN(vsb_connect);
+		fd = haproxy_cli_tcp_connect(hc->vl,
+		    VSB_data(vsb_connect), 10., &err);
+		if (fd < 0)
+			vtc_fatal(hc->vl,
+			    "CLI failed to open %s: %s", VSB_data(vsb), err);
+		VSB_destroy(&vsb_connect);
+		hc->sock = fd;
+	}
+	vtc_dump(hc->vl, 4, "CLI send", VSB_data(vsb), -1);
+
+	wr = write(hc->sock, VSB_data(vsb), VSB_len(vsb));
+	if (wr != VSB_len(vsb))
+		vtc_fatal(hc->vl,
+		    "CLI fd %d send error %s", hc->sock, strerror(errno));
+
+	VSB_destroy(&vsb);
+}
+
+#define HAPROXY_CLI_RECV_LEN (1 << 14)
+static void
+haproxy_cli_recv(struct haproxy_cli *hc)
+{
+	ssize_t ret;
+	size_t rdz, left, off;
+
+	rdz = ret = off = 0;
+	/* We want to null terminate this buffer. */
+	left = hc->rxbuf_sz - 1;
+	while (!vtc_error && left > 0) {
+		VTCP_set_read_timeout(hc->sock, hc->timeout);
+
+		ret = recv(hc->sock, hc->rxbuf + off, HAPROXY_CLI_RECV_LEN, 0);
+		if (ret < 0) {
+			if (errno == EINTR || errno == EAGAIN)
+				continue;
+
+			vtc_fatal(hc->vl,
+			    "CLI fd %d recv() failed (%s)",
+			    hc->sock, strerror(errno));
+		}
+		/* Connection closed. */
+		if (ret == 0) {
+			if (hc->rxbuf[rdz - 1] != '\n')
+				vtc_fatal(hc->vl,
+				    "CLI rx timeout (fd: %d %.3fs ret: %zd)",
+				    hc->sock, hc->timeout, ret);
+
+			vtc_log(hc->vl, 4, "CLI connection normally closed");
+			vtc_log(hc->vl, 3, "CLI closing fd %d", hc->sock);
+			VTCP_close(&hc->sock);
+			break;
+		}
+
+		rdz += ret;
+		left -= ret;
+		off  += ret;
+	}
+	hc->rxbuf[rdz] = '\0';
+	vtc_dump(hc->vl, 4, "CLI recv", hc->rxbuf, rdz);
+}
+
+/*
+ * SECTION: haproxy.cli.expect
+ * expect OP STRING
+ *         Regex match the CLI reception buffer with STRING
+ *         if OP is ~ or, on the contraty, if OP is !~ check that there is
+ *         no regex match.
+ */
+static void v_matchproto_(cmd_f)
+cmd_haproxy_cli_expect(CMD_ARGS)
+{
+	struct haproxy_cli *hc;
+	vre_t *vre;
+	const char *error;
+	int erroroffset, i, ret;
+	char *cmp, *spec;
+
+	(void)cmd;
+	(void)vl;
+	CAST_OBJ_NOTNULL(hc, priv, HAPROXY_CLI_MAGIC);
+	AZ(strcmp(av[0], "expect"));
+	av++;
+
+	cmp = av[0];
+	spec = av[1];
+	AN(cmp);
+	AN(spec);
+	AZ(av[2]);
+
+	assert(!strcmp(cmp, "~") || !strcmp(cmp, "!~"));
+
+	haproxy_cli_recv(hc);
+
+	vre = VRE_compile(spec, 0, &error, &erroroffset);
+	if (!vre)
+		vtc_fatal(hc->vl, "CLI regexp error: '%s' (@%d) (%s)",
+		    error, erroroffset, spec);
+
+	i = VRE_exec(vre, hc->rxbuf, strlen(hc->rxbuf), 0, 0, NULL, 0, 0);
+
+	VRE_free(&vre);
+
+	ret = (i >= 0 && *cmp == '~') || (i < 0 && *cmp == '!');
+	if (!ret)
+		vtc_fatal(hc->vl, "CLI expect failed %s \"%s\"", cmp, spec);
+	else
+		vtc_log(hc->vl, 4, "CLI expect match %s \"%s\"", cmp, spec);
+}
+
+static const struct cmds haproxy_cli_cmds[] = {
+#define CMD_HAPROXY_CLI(n) { #n, cmd_haproxy_cli_##n },
+	CMD_HAPROXY_CLI(send)
+	CMD_HAPROXY_CLI(expect)
+#undef CMD_HAPROXY_CLI
+};
+
+/**********************************************************************
+ * HAProxy CLI client thread
+ */
+
+static void *
+haproxy_cli_thread(void *priv)
+{
+	struct haproxy_cli *hc;
+	struct vsb *vsb;
+	int fd;
+	const char *err;
+
+	CAST_OBJ_NOTNULL(hc, priv, HAPROXY_CLI_MAGIC);
+	AN(*hc->connect);
+
+	vsb = macro_expand(hc->vl, hc->connect);
+	AN(vsb);
+
+	fd = haproxy_cli_tcp_connect(hc->vl, VSB_data(vsb), 10., &err);
+	if (fd < 0)
+		vtc_fatal(hc->vl,
+		    "CLI failed to open %s: %s", VSB_data(vsb), err);
+	(void)VTCP_blocking(fd);
+	hc->sock = fd;
+	parse_string(hc->spec, haproxy_cli_cmds, hc, hc->vl);
+	vtc_log(hc->vl, 2, "CLI ending");
+	VSB_destroy(&vsb);
+	return (NULL);
+}
+
+/**********************************************************************
+ * Wait for the CLI client thread to stop
+ */
+
+static void
+haproxy_cli_wait(struct haproxy_cli *hc)
+{
+	void *res;
+
+	CHECK_OBJ_NOTNULL(hc, HAPROXY_CLI_MAGIC);
+	vtc_log(hc->vl, 2, "CLI waiting");
+	AZ(pthread_join(hc->tp, &res));
+	if (res != NULL)
+		vtc_fatal(hc->vl, "CLI returned \"%s\"", (char *)res);
+	REPLACE(hc->spec, NULL);
+	hc->tp = 0;
+	hc->running = 0;
+}
+
+/**********************************************************************
+ * Start the CLI client thread
+ */
+
+static void
+haproxy_cli_start(struct haproxy_cli *hc)
+{
+	CHECK_OBJ_NOTNULL(hc, HAPROXY_CLI_MAGIC);
+	vtc_log(hc->vl, 2, "CLI starting");
+	AZ(pthread_create(&hc->tp, NULL, haproxy_cli_thread, hc));
+	hc->running = 1;
+
+}
+
+/**********************************************************************
+ * Run the CLI client thread
+ */
+
+static void
+haproxy_cli_run(struct haproxy_cli *hc)
+{
+	haproxy_cli_start(hc);
+	haproxy_cli_wait(hc);
+}
+
 /**********************************************************************
  *
  */
@@ -131,6 +401,41 @@ haproxy_wait_pidfile(struct haproxy *h)
 		  h->name, buf_err);
 }
 
+/**********************************************************************
+ * Allocate and initialize a CLI client
+ */
+
+static struct haproxy_cli *
+haproxy_cli_new(struct haproxy *h)
+{
+	struct haproxy_cli *hc;
+
+	ALLOC_OBJ(hc, HAPROXY_CLI_MAGIC);
+	AN(hc);
+
+	hc->vl = h->vl;
+	hc->sock = -1;
+	bprintf(hc->connect, "${%s_cli_sock}", h->name);
+
+	hc->txbuf_sz = hc->rxbuf_sz = 2048 * 1024;
+	hc->txbuf = malloc(hc->txbuf_sz);
+	AN(hc->txbuf);
+	hc->rxbuf = malloc(hc->rxbuf_sz);
+	AN(hc->rxbuf);
+
+	return hc;
+}
+
+static void
+haproxy_cli_delete(struct haproxy_cli *hc)
+{
+	CHECK_OBJ_NOTNULL(hc, HAPROXY_CLI_MAGIC);
+	REPLACE(hc->spec, NULL);
+	REPLACE(hc->txbuf, NULL);
+	REPLACE(hc->rxbuf, NULL);
+	FREE_OBJ(hc);
+}
+
 /**********************************************************************
  * Allocate and initialize a haproxy
  */
@@ -170,6 +475,9 @@ haproxy_new(const char *name)
 	h->cfg_fn = strdup(buf);
 	AN(h->cfg_fn);
 
+	h->cli = haproxy_cli_new(h);
+	AN(h->cli);
+
 	bprintf(buf, "rm -rf %s ; mkdir -p %s", h->workdir, h->workdir);
 	AZ(system(buf));
 
@@ -201,6 +509,7 @@ haproxy_delete(struct haproxy *h)
 	free(h->cfg_fn);
 	free(h->pid_fn);
 	VSB_destroy(&h->args);
+	haproxy_cli_delete(h->cli);
 
 	/* XXX: MEMLEAK (?) */
 	FREE_OBJ(h);
@@ -323,6 +632,9 @@ haproxy_wait(struct haproxy *h)
 	if (h->pid < 0)
 		haproxy_start(h);
 
+	if (h->cli->spec)
+		haproxy_cli_run(h->cli);
+
 	closefd(&h->fds[1]);
 
 	sig = SIGINT;
@@ -445,6 +757,7 @@ haproxy_write_conf(const struct haproxy *h, const char *cfg, int auto_be)
 
 	VSB_printf(vsb, "    global\n\tstats socket %s "
 		   "level admin mode 600\n", h->cli_fn);
+	VSB_printf(vsb, "    stats socket \"fd@${cli}\" level admin\n");
 	AZ(VSB_cat(vsb, cfg));
 
 	if (auto_be)
@@ -508,6 +821,9 @@ haproxy_write_conf(const struct haproxy *h, const char *cfg, int auto_be)
  * \-arg STRING
  *         Pass an argument to haproxy, for example "-h simple_list".
  *
+ * \-cli STRING
+ *         Specify the spec to be run by the command line interface (CLI).
+ *
  * \-conf STRING
  *         Specify the configuration to be loaded by this HAProxy instance.
  *
@@ -598,6 +914,13 @@ cmd_haproxy(CMD_ARGS)
 			continue;
 		}
 
+		if (!strcmp(*av, "-cli")) {
+			REPLACE(h->cli->spec, av[1]);
+			if (h->tp)
+				haproxy_cli_run(h->cli);
+			av++;
+			continue;
+		}
 		if (!strcmp(*av, "-conf")) {
 			AN(av[1]);
 			haproxy_write_conf(h, av[1], 0);


More information about the varnish-commit mailing list