[master] 166ce40 Run background processes in varnishtest

Dridi Boukelmoune dridi.boukelmoune at gmail.com
Wed Jun 10 11:49:30 CEST 2015


commit 166ce403aaf7584041d195e885780d3f25f8f347
Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
Date:   Sat May 30 11:38:58 2015 +0200

    Run background processes in varnishtest
    
    Available operations on processes are:
    - start
    - wait
    - kill (send a signal)
    - stop (kill with TERM)
    - write (to its stdin)
    - close (its stdin)
    
    A process name starts with a 'p' and accepts a shell pipeline as the
    command line. Environment variables can be set directly in the command
    line.
    
    A process will create a macro with its PID, and its stdout and stderr
    are redirected to files in a dedicated directory.
    
    Also add a varnish "name" macro in varnishtest.

diff --git a/bin/varnishtest/Makefile.am b/bin/varnishtest/Makefile.am
index fe599d4..2506a8a 100644
--- a/bin/varnishtest/Makefile.am
+++ b/bin/varnishtest/Makefile.am
@@ -34,7 +34,8 @@ varnishtest_SOURCES = \
 		vtc_sema.c \
 		vtc_server.c \
 		vtc_varnish.c \
-		vtc_logexp.c
+		vtc_logexp.c \
+		vtc_process.c
 
 varnishtest_LDADD = \
 		$(top_builddir)/lib/libvarnish/libvarnish.la \
diff --git a/bin/varnishtest/tests/README b/bin/varnishtest/tests/README
index 456bf75..52e5061 100644
--- a/bin/varnishtest/tests/README
+++ b/bin/varnishtest/tests/README
@@ -27,4 +27,5 @@ Naming scheme
 	id ~ [r] --> Regression tests, same number as ticket
 	id ~ [s] --> Slow tests, expiry, grace etc.
 	id ~ [t] --> sTreaming tests
+	id ~ [u] --> Unusual background processes
 	id ~ [v] --> VCL tests: execute VRT functions
diff --git a/bin/varnishtest/tests/u00000.vtc b/bin/varnishtest/tests/u00000.vtc
new file mode 100644
index 0000000..abe5f0e
--- /dev/null
+++ b/bin/varnishtest/tests/u00000.vtc
@@ -0,0 +1,34 @@
+varnishtest "Simple process tests"
+
+# new & start
+process p1 "cat" -start
+process p2 "cat" -start
+process p3 "cat" -start
+
+# write
+process p1 -writeln "foo"
+process p2 -writeln "bar"
+process p3 -writeln "baz"
+
+# give enough time for the writes
+delay 0.5
+
+# stop
+process p1 -stop
+process p2 -close
+process p3 -kill "HUP"
+
+# wait
+process p1 -wait
+process p2 -wait
+process p3 -wait
+
+# check stdout
+shell "grep foo ${tmpdir}/p1/stdout >/dev/null 2>&1"
+shell "grep bar ${tmpdir}/p2/stdout >/dev/null 2>&1"
+shell "grep baz ${tmpdir}/p3/stdout >/dev/null 2>&1"
+
+# check stderr
+shell "test -f ${tmpdir}/p1/stderr -a ! -s ${tmpdir}/p1/stderr"
+shell "test -f ${tmpdir}/p2/stderr -a ! -s ${tmpdir}/p2/stderr"
+shell "test -f ${tmpdir}/p3/stderr -a ! -s ${tmpdir}/p3/stderr"
diff --git a/bin/varnishtest/tests/u00001.vtc b/bin/varnishtest/tests/u00001.vtc
new file mode 100644
index 0000000..7cf18c2
--- /dev/null
+++ b/bin/varnishtest/tests/u00001.vtc
@@ -0,0 +1,43 @@
+varnishtest "varnishncsa log file rotation"
+
+server s1 {
+	rxreq
+	expect req.url == "/foo"
+	txresp -status 200
+
+	rxreq
+	expect req.url == "/bar"
+	txresp -status 404
+} -start
+
+varnish v1 -vcl+backend "" -start
+
+process p1 "${varnishncsa} -n ${v1_name} -w ${tmpdir}/ncsa.log -F %s" -start
+
+# give varnishncsa enough time to open the VSM
+delay 1
+
+client c1 {
+	txreq -url "/foo"
+	rxresp
+} -run
+
+# give varnishncsa enough time to write
+delay 1
+
+# rotate logs
+shell "mv ${tmpdir}/ncsa.log ${tmpdir}/ncsa.old.log >/dev/null 2>&1"
+process p1 -kill "HUP"
+
+client c1 {
+	txreq -url "/bar"
+	rxresp
+} -run
+
+# give varnishncsa enough time to write
+delay 1
+
+process p1 -stop
+
+shell "grep 200 ${tmpdir}/ncsa.old.log >/dev/null"
+shell "grep 404 ${tmpdir}/ncsa.log >/dev/null"
diff --git a/bin/varnishtest/vtc.c b/bin/varnishtest/vtc.c
index cbda7bf..ae38f68 100644
--- a/bin/varnishtest/vtc.c
+++ b/bin/varnishtest/vtc.c
@@ -620,6 +620,7 @@ static const struct cmds cmds[] = {
 	{ "random",	cmd_random },
 	{ "feature",	cmd_feature },
 	{ "logexpect",	cmd_logexp },
+	{ "process",	cmd_process },
 	{ NULL,		NULL }
 };
 
diff --git a/bin/varnishtest/vtc.h b/bin/varnishtest/vtc.h
index e27c056..17b5790 100644
--- a/bin/varnishtest/vtc.h
+++ b/bin/varnishtest/vtc.h
@@ -63,6 +63,7 @@ cmd_f cmd_client;
 cmd_f cmd_varnish;
 cmd_f cmd_sema;
 cmd_f cmd_logexp;
+cmd_f cmd_process;
 
 extern volatile sig_atomic_t vtc_error; /* Error, bail out */
 extern int vtc_stop;		/* Abandon current test, no error */
diff --git a/bin/varnishtest/vtc_process.c b/bin/varnishtest/vtc_process.c
new file mode 100644
index 0000000..157bfa9
--- /dev/null
+++ b/bin/varnishtest/vtc_process.c
@@ -0,0 +1,389 @@
+/*-
+ * Copyright (c) 2015 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Dridi Boukelmoune <dridi at varnish-software.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 "config.h"
+
+#include <sys/resource.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "vtc.h"
+
+#include "vss.h"
+
+struct process {
+	unsigned		magic;
+#define PROCESS_MAGIC		0x1617b43e
+	char			*name;
+	struct vtclog		*vl;
+	VTAILQ_ENTRY(process)	list;
+
+	char			*spec;
+	char			*workdir;
+	char			*outdir;
+	char			*out;
+	char			*err;
+	int			fds[2];
+	pid_t			pid;
+
+	pthread_t		tp;
+	unsigned		running;
+	int			status;
+};
+
+static VTAILQ_HEAD(, process)	processes =
+    VTAILQ_HEAD_INITIALIZER(processes);
+
+/**********************************************************************
+ * Allocate and initialize a process
+ */
+
+#define PROCESS_EXPAND(field, format, ...)		\
+	do {						\
+		bprintf(buf, format, __VA_ARGS__);	\
+		vsb = macro_expand(p->vl, buf);		\
+		AN(vsb);				\
+		p->field = strdup(VSB_data(vsb));	\
+		AN(p->field);				\
+		VSB_delete(vsb);			\
+	} while (0)
+
+static struct process *
+process_new(const char *name)
+{
+	struct process *p;
+	struct vsb *vsb;
+	char buf[1024];
+
+	AN(name);
+	ALLOC_OBJ(p, PROCESS_MAGIC);
+	AN(p);
+	REPLACE(p->name, name);
+
+	p->vl = vtc_logopen(name);
+	AN(p->vl);
+
+	PROCESS_EXPAND(workdir, "%s", "${pwd}");
+	PROCESS_EXPAND(outdir, "${tmpdir}/%s", name);
+	PROCESS_EXPAND(out, "${tmpdir}/%s/stdout", name);
+	PROCESS_EXPAND(err, "${tmpdir}/%s/stderr", name);
+
+	bprintf(buf, "rm -rf %s ; mkdir -p %s ; touch %s %s",
+	    p->outdir, p->outdir, p->out, p->err);
+	AZ(system(buf));
+
+	p->fds[0] = -1;
+	p->fds[1] = -1;
+
+	if (*p->name != 'p')
+		vtc_log(p->vl, 0, "Process name must start with 'p'");
+
+	VTAILQ_INSERT_TAIL(&processes, p, list);
+	return (p);
+}
+
+#undef PROCESS_EXPAND
+
+/**********************************************************************
+ * Clean up process
+ */
+
+static void
+process_delete(struct process *p)
+{
+
+	CHECK_OBJ_NOTNULL(p, PROCESS_MAGIC);
+	vtc_logclose(p->vl);
+	free(p->name);
+	free(p->workdir);
+	free(p->outdir);
+	free(p->out);
+	free(p->err);
+
+	/*
+	 * We do not delete the outdir, it may contain useful stdout
+	 * and stderr files.
+	 */
+
+	/* XXX: MEMLEAK (?) */
+	FREE_OBJ(p);
+}
+
+/**********************************************************************
+ * Start the process thread
+ */
+
+static void *
+process_thread(void *priv)
+{
+	struct process *p;
+	struct rusage ru;
+	int r;
+
+	CAST_OBJ_NOTNULL(p, priv, PROCESS_MAGIC);
+	r = wait4(p->pid, &p->status, 0, &ru);
+	macro_undef(p->vl, p->name, "pid");
+	p->pid = 0;
+	p->running = 0;
+	vtc_log(p->vl, 2, "R %d Status: %04x (u %.6f s %.6f)", r, p->status,
+	    ru.ru_utime.tv_sec + 1e-6 * ru.ru_utime.tv_usec,
+	    ru.ru_stime.tv_sec + 1e-6 * ru.ru_stime.tv_usec
+	);
+
+	if (WIFEXITED(p->status) && WEXITSTATUS(p->status) == 0)
+		return (NULL);
+#ifdef WCOREDUMP
+	vtc_log(p->vl, 2, "Bad exit code: %04x sig %x exit %x core %x",
+	    p->status, WTERMSIG(p->status), WEXITSTATUS(p->status),
+	    WCOREDUMP(p->status));
+#else
+	vtc_log(p->vl, 2, "Bad exit code: %04x sig %x exit %x",
+	    p->status, WTERMSIG(p->status), WEXITSTATUS(p->status));
+#endif
+
+	(void)close(p->fds[1]);
+	p->fds[1] = -1;
+
+	return (NULL);
+}
+
+static void
+process_start(struct process *p)
+{
+	struct vsb *cl;
+	int i, out_fd, err_fd;
+
+	CHECK_OBJ_NOTNULL(p, PROCESS_MAGIC);
+
+	vtc_log(p->vl, 4, "CMD: %s", p->spec);
+
+	cl = macro_expand(p->vl, p->spec);
+	AN(cl);
+	AZ(pipe(p->fds));
+	out_fd = open(p->out, O_WRONLY|O_APPEND);
+	assert(out_fd >= 0);
+	err_fd = open(p->err, O_WRONLY|O_APPEND);
+	assert(err_fd >= 0);
+	p->pid = fork();
+	assert(p->pid >= 0);
+	p->running = 1;
+	if (p->pid == 0) {
+		assert(dup2(p->fds[0], 0) == 0);
+		assert(dup2(out_fd, 1) == 1);
+		assert(dup2(out_fd, 2) == 2);
+		for (i = 3; i <getdtablesize(); i++)
+			(void)close(i);
+		AZ(execl("/bin/sh", "/bin/sh", "-c", VSB_data(cl), (char*)0));
+		exit(1);
+	}
+	vtc_log(p->vl, 3, "PID: %ld", (long)p->pid);
+	macro_def(p->vl, p->name, "pid", "%ld", (long)p->pid);
+	AZ(close(p->fds[0]));
+	AZ(close(out_fd));
+	AZ(close(err_fd));
+	p->fds[0] = -1;
+	VSB_delete(cl);
+	AZ(pthread_create(&p->tp, NULL, process_thread, p));
+}
+
+/**********************************************************************
+ * Wait for process thread to stop
+ */
+
+static void
+process_wait(struct process *p)
+{
+	void *v;
+
+	if (p->running && p->pid)
+		AZ(pthread_join(p->tp, &v));
+}
+
+/**********************************************************************
+ * Send a signal to a process
+ */
+
+static void
+process_kill(struct process *p, const char *sig)
+{
+	int s, l;
+	char buf[64];
+
+	CHECK_OBJ_NOTNULL(p, PROCESS_MAGIC);
+	AN(sig);
+
+	if (!p->running || !p->pid) {
+		vtc_log(p->vl, 0, "Cannot signal a non-running process");
+		return;
+	}
+
+	vtc_log(p->vl, 4, "CMD: kill -%s %d", sig, p->pid);
+
+	l = snprintf(buf, sizeof buf, "kill -%s %d", sig, p->pid);
+	AN(l < sizeof buf);
+	s = system(buf);
+	if (s != 0)
+		vtc_log(p->vl, 0, "Failed to send signal (exit status: %d)", s);
+}
+
+static inline void
+process_stop(struct process *p)
+{
+
+	process_kill(p, "TERM");
+}
+
+static inline void
+process_terminate(struct process *p)
+{
+
+	process_kill(p, "TERM");
+	sleep(1);
+	if (p->running && p->pid)
+		process_kill(p, "KILL");
+}
+
+/**********************************************************************
+ * Write to a process' stdin
+ */
+
+static void
+process_write(struct process *p, const char *text)
+{
+	int r, len;
+
+	if (!p->running || !p->pid) {
+		vtc_log(p->vl, 0, "Cannot write to a non-running process");
+		return;
+	}
+
+	len = strlen(text);
+	vtc_log(p->vl, 4, "Writing %d bytes", len);
+	r = write(p->fds[1], text, len);
+	if (r < 0)
+		vtc_log(p->vl, 0, "Failed to write: %s (%d)",
+		    strerror(errno), errno);
+}
+
+static void
+process_close(struct process *p)
+{
+
+	if (!p->running || !p->pid) {
+		vtc_log(p->vl, 0, "Cannot close on a non-running process");
+		return;
+	}
+
+	AZ(close(p->fds[1]));
+	p->fds[1] = -1;
+}
+
+/**********************************************************************
+ * Process command dispatch
+ */
+
+void
+cmd_process(CMD_ARGS)
+{
+	struct process *p, *p2;
+
+	(void)priv;
+	(void)cmd;
+	(void)vl;
+
+	if (av == NULL) {
+		/* Reset and free */
+		VTAILQ_FOREACH_SAFE(p, &processes, list, p2) {
+			if (p->running && p->pid)
+				process_terminate(p);
+			VTAILQ_REMOVE(&processes, p, list);
+			process_delete(p);
+		}
+		return;
+	}
+
+	AZ(strcmp(av[0], "process"));
+	av++;
+
+	VTAILQ_FOREACH(p, &processes, list)
+		if (!strcmp(p->name, av[0]))
+			break;
+	if (p == NULL)
+		p = process_new(av[0]);
+	av++;
+
+	for (; *av != NULL; av++) {
+		if (vtc_error)
+			break;
+
+		if (!strcmp(*av, "-start")) {
+			process_start(p);
+			continue;
+		}
+		if (!strcmp(*av, "-wait")) {
+			process_wait(p);
+			continue;
+		}
+		if (!strcmp(*av, "-kill")) {
+			process_kill(p, av[1]);
+			av++;
+			continue;
+		}
+		if (!strcmp(*av, "-stop")) {
+			process_stop(p);
+			continue;
+		}
+		if (!strcmp(*av, "-write")) {
+			process_write(p, av[1]);
+			av++;
+			continue;
+		}
+		if (!strcmp(*av, "-writeln")) {
+			process_write(p, av[1]);
+			process_write(p, "\n");
+			av++;
+			continue;
+		}
+		if (!strcmp(*av, "-close")) {
+			process_close(p);
+			continue;
+		}
+		if (**av == '-')
+			vtc_log(p->vl, 0, "Unknown process argument: %s", *av);
+		REPLACE(p->spec, *av);
+	}
+}
diff --git a/bin/varnishtest/vtc_varnish.c b/bin/varnishtest/vtc_varnish.c
index a7b504f..c6cb52f 100644
--- a/bin/varnishtest/vtc_varnish.c
+++ b/bin/varnishtest/vtc_varnish.c
@@ -432,6 +432,7 @@ varnish_launch(struct varnish *v)
 	} else {
 		vtc_log(v->vl, 3, "PID: %ld", (long)v->pid);
 		macro_def(v->vl, v->name, "pid", "%ld", (long)v->pid);
+		macro_def(v->vl, v->name, "name", "%s", v->workdir);
 	}
 	AZ(close(v->fds[0]));
 	AZ(close(v->fds[3]));



More information about the varnish-commit mailing list