[patch repod] add autostart of Mongo

Pete Zaitcev zaitcev at redhat.com
Tue Oct 12 03:10:26 UTC 2010


We would like to start the required database instance automatically,
so that it does not need to be configured. Naturally doing is requires
certain assumptions, in particular where the database is located
(we assume that it is in a subdirectory _db/ of the current directory).

This version of the patch attempts to stop the database that we launched.

---
 Makefile.am |    3 
 auto.c      |  338 ++++++++++++++++++++++++++++++++++++++++++++++++++
 iwh.h       |   21 ++-
 proxy.c     |   42 ++++++
 proxy.h     |    4 
 rest.c      |   38 ++++-
 6 files changed, 438 insertions(+), 8 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 19fb253..a704dfa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -27,6 +27,7 @@ EXTRA_DIST = query.leg
 # iwhd is short for Image WareHouse Daemon.
 
 iwhd_SOURCES = \
+  auto.c	\
   backend.c	\
   backend.h	\
   iwh.h		\
@@ -72,3 +73,5 @@ CLEANFILES = query-orig.c
 MOSTLYCLEANFILES = query.c query-orig.c-t
 MAINTAINERCLEANFILES = query.c query-orig.c
 EXTRA_DIST += query.c.diff
+
+iwhd_LDFLAGS	= -Lmongo
diff --git a/auto.c b/auto.c
new file mode 100644
index 0000000..f32a898
--- /dev/null
+++ b/auto.c
@@ -0,0 +1,338 @@
+#include <config.h>
+
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <netdb.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+#include <stdarg.h>		// for microhttpd.h (bug in there)
+#include <stdint.h>		// for microhttpd.h (bug in there)
+#include <microhttpd.h>		// for proxy.h
+#include <curl/curl.h>		// for proxy.h
+#include <glib.h>		// for pfoxy.h
+
+#include "proxy.h"
+#include "iwh.h"
+
+static char auto_arg_port[10];
+
+static char *auto_arg_mongod[] = {
+	"mongod",
+	"--port", auto_arg_port,
+	"--dbpath", AUTO_DIR_DB,
+	/* "--fork", */	/* chdirs god knows where, we cannot use this. */
+	/* "--logpath", AUTO_MONGOD_LOG, */	/* required by --fork */
+	/* "--logappend", */
+	NULL
+};
+
+/* The --quiet option in mongod is useless, so redirect instead. */
+static char *auto_arg_mongod_quiet[] = {
+	"mongod",
+	"--port", auto_arg_port,
+	"--dbpath", AUTO_DIR_DB,
+	"--logpath", AUTO_MONGOD_LOG,
+	NULL
+};
+
+static int auto_pid_mongod;
+
+static int
+auto_mkdir (const char *name)
+{
+	struct stat	statb;
+
+	if (mkdir(name, 0777) < 0) {
+		if (errno == EEXIST) {
+			if (stat(name, &statb) < 0) {
+				error (0, errno, "stat %s failed", name);
+				return -1;
+			}
+			if (!S_ISDIR(statb.st_mode)) {
+				error (0, 0, "path %s is not a directory",name);
+				return -1;
+			}
+			return 0;
+		}
+		error(0, errno, "Cannot create %s", name);
+		return -1;
+	}
+	return 0;
+}
+
+static int
+auto_prepare_area (void)
+{
+
+	if (auto_mkdir(AUTO_DIR_FS) < 0) {
+		return -1;
+	}
+	if (auto_mkdir(AUTO_DIR_DB) < 0) {
+		return -1;
+	}
+	return 0;
+}
+
+static void
+auto_kill_mongod (int sig)
+{
+	if (auto_pid_mongod) {
+		kill(auto_pid_mongod, sig);
+	}
+}
+
+static int
+auto_spawn (const char *prog, char *argv[])
+{
+	struct stat	statb;
+	pid_t		pid;
+
+	/*
+	 * The stat check is purely so that common errors, such as ENOENT
+	 * if the program is not available, were printed before the fork.
+	 * This serves no security purpose but only makes stderr more tidy.
+	 */
+	if (stat(prog, &statb) < 0) {
+		error (0, errno, "stat %s failed", prog);
+		return -1;
+	}
+	if (!S_ISREG(statb.st_mode)) {
+		error (0, 0, "path %s is not a regular file", prog);
+		return -1;
+	}
+
+	pid = fork();
+	if (pid < 0) {
+		error (0, errno, "fork failed");
+		return -1;
+	}
+
+	if (pid == 0) {
+		execvp(prog, argv);
+		error (EXIT_FAILURE, errno, "failed to run command %s", prog);
+	}
+
+	/*
+	 * This is where you'd normally run waitpid for your daemon, so that
+	 * argument check failures were caught at least. In case of mongod,
+	 * daemonizing it is a whole can of worms, so we do not. On the
+	 * upside, it stays on our session (and process group) and dies
+	 * cleanly on keyboard interrupt.
+	 */
+
+	return pid;
+}
+
+/*
+ * The server_node and node_resolve are taken verbatim from wait-for-listen.
+ * Is this time to factor?
+ */
+#define ADDRSIZE 40
+
+struct server_node {
+	unsigned		alen;
+	union {
+		struct sockaddr addr;
+		unsigned char x[ADDRSIZE];
+	} a;
+};
+
+static int node_resolve(struct server_node *sn,
+			const char *hostname, const char *portstr)
+{
+	struct addrinfo hints;
+	struct addrinfo *res, *res0;
+	int rc;
+
+	memset(&hints, 0, sizeof(struct addrinfo));
+	hints.ai_family = PF_UNSPEC;
+	hints.ai_socktype = SOCK_DGRAM;
+
+	rc = getaddrinfo(hostname, portstr, &hints, &res0);
+	if (rc) {
+		error(0, 0, "getaddrinfo(%s:%s) failed: %s",
+		       hostname, portstr, gai_strerror(rc));
+		return -1;
+	}
+
+	for (res = res0; res; res = res->ai_next) {
+		if (res->ai_family != AF_INET && res->ai_family != AF_INET6)
+			continue;
+
+		if (res->ai_addrlen > ADDRSIZE)		/* should not happen */
+			continue;
+
+		memcpy(&sn->a.addr, res->ai_addr, res->ai_addrlen);
+		sn->alen = res->ai_addrlen;
+
+		freeaddrinfo(res0);
+		return 0;
+	}
+
+	freeaddrinfo(res0);
+	return -1;
+}
+
+static int auto_test_mongod(void)
+{
+	struct server_node snode, *sn = &snode;
+	int sfd;
+	int rc;
+
+	memset(sn, 0, sizeof(struct server_node));
+	if (node_resolve(sn, AUTO_HOST, auto_arg_port) != 0) {
+		/*
+		 * Most likely nothing else would work anyway. So abend.
+		 */
+		error(0, 0, "unable to resolve host %s port %s",
+		      AUTO_HOST, auto_arg_port);
+		return -1;
+	}
+
+	DPRINTF("trying to connect to mongod (host %s port %s) ...\n",
+		AUTO_HOST, auto_arg_port);
+
+	sfd = socket(sn->a.addr.sa_family, SOCK_STREAM, 0);
+	if (sfd < 0) {
+		error(0, errno, "socket");
+		return -1;
+	}
+
+	rc = connect(sfd, &sn->a.addr, sn->alen);
+	if (rc != 0) {
+		// if (errno != ECONNREFUSED) {
+			DPRINTF("connect: %s\n", strerror(errno));
+		// }
+		close(sfd);
+		return 1;
+	}
+
+	close(sfd);
+	return 0;
+}
+
+static int
+auto_wait_mongod(void)
+{
+	struct timespec ts;
+	time_t start_time;
+	int rc;
+
+	start_time = time(NULL);
+	for (;;) {
+		rc = auto_test_mongod();
+		if (rc == 0)
+			break;
+		if (time(NULL) >= start_time + 5) {
+			error(0, 0, "failed to verify mongod using port %s",
+			      auto_arg_port);
+			return -1;
+		}
+
+		ts.tv_sec = 1;
+		ts.tv_nsec = 0;
+		nanosleep(&ts, NULL);
+	}
+	DPRINTF("mongod went up after %ld s\n", (long)time(NULL) - start_time);
+
+	return 0;
+}
+
+static void
+auto_action (int sig, siginfo_t *info, void *uctx)
+{
+	(void) info;
+	(void) uctx;
+
+	if (sig == SIGSEGV || sig == SIGILL || sig == SIGFPE || sig == SIGBUS) {
+		auto_kill_mongod(SIGTERM);
+	}
+	else {
+		auto_kill_mongod(info->si_signo);
+	}
+}
+
+static int
+auto_set_sig (void)
+{
+	struct sigaction actb;
+
+	memset(&actb, 0, sizeof(struct sigaction));
+	actb.sa_flags |= SA_SIGINFO;
+	actb.sa_sigaction = auto_action;
+
+	/* Not trapping SIGINT or SIGHUP since mongo is in our session. */
+	if (sigaction(SIGTERM, &actb, NULL) ||
+	    sigaction(SIGSEGV, &actb, NULL) ||
+	    sigaction(SIGILL, &actb, NULL) ||
+	    sigaction(SIGFPE, &actb, NULL) ||
+	    sigaction(SIGBUS, &actb, NULL) ||
+	    sigaction(SIGABRT, &actb, NULL)) {
+		error(0, errno, "sigaction");
+		return -1;
+	}
+	return 0;
+}
+
+int
+auto_start (int dbport)
+{
+	int rc;
+	char **earg;
+	int pid;
+
+	snprintf(auto_arg_port, sizeof(auto_arg_port), "%u", dbport);
+
+	if (auto_prepare_area() < 0)
+		return -1;
+
+	rc = auto_test_mongod();
+	if (rc < 0)
+		return -1;
+
+	/*
+	 * This is a trick. The auto_test_mongod() merely connects to a TCP
+	 * port, and does not execute a NO-OP in Mongo. Therefore, it succeeds
+	 * if a foreign application is listening on our private port.
+	 * We abort because we do not want anyone listening there.
+	 */
+	if (rc == 0) {
+		error (0, 0, "something is listening on port %s,"
+		       " not auto-starting Mongo", auto_arg_port);
+		return -1;
+	}
+
+	DPRINTF("auto-starting mongod\n");
+	earg = verbose ? auto_arg_mongod : auto_arg_mongod_quiet;
+	pid = auto_spawn(AUTO_BIN_MONGOD, earg);
+	if (pid < 0)
+		return -1;
+	auto_pid_mongod = pid;
+	if (auto_wait_mongod() < 0) {
+		auto_kill_mongod(SIGTERM);
+		return -1;
+	}
+
+	if (auto_set_sig() < 0) {
+		auto_kill_mongod(SIGTERM);
+		return -1;
+	}
+
+	DPRINTF("mongod listens on port %u\n", dbport);
+	return 0;
+}
+
+void
+auto_stop (void)
+{
+	auto_kill_mongod(SIGTERM);
+}
diff --git a/iwh.h b/iwh.h
index 091c3c0..f3c091e 100644
--- a/iwh.h
+++ b/iwh.h
@@ -48,7 +48,7 @@ GLOBAL(unsigned short,	master_port,	MY_PORT);
 GLOBAL(unsigned int,	s3mode,		0);		/* repod/S3 */
 GLOBAL(const char *,	local_path,	"/tmp");	/* FS */
 GLOBAL(const char *,	db_host,	"localhost");
-GLOBAL(unsigned short,	db_port,	27017);
+GLOBAL(unsigned short,	db_port,	0);
 GLOBAL(char *,          me,             "here");
 
 #define DPRINTF(fmt,args...) do {	\
@@ -66,3 +66,22 @@ GLOBAL(char *,          me,             "here");
 #ifndef ATTRIBUTE_UNUSED
 # define ATTRIBUTE_UNUSED __attribute__ ((__unused__))
 #endif
+
+/*
+ * Common parts of autostart
+ *
+ * Directories are relative so they are based off local_path.
+ * Notice that we continue to use the underscore convention even though
+ * buckets are inside the AUTO_DIR_FS and do not conflict. Visual help:
+ * you can see what to delete right away.
+ *
+ * We want our own Mongo instance for autostart. Mongo does not have
+ * a feature "listen on port 0 and tell us what you got" (like Hail),
+ * so we define a port and hope it's not in use...
+ */
+#define AUTO_HOST	"localhost"
+#define AUTO_DIR_FS	"_fs"
+#define AUTO_DIR_DB	"_db"
+#define AUTO_BIN_MONGOD	"/usr/bin/mongod"
+#define AUTO_MONGOD_LOG	"_mongod.log"
+#define AUTO_MONGOD_PORT 27018
diff --git a/proxy.c b/proxy.c
index 73d67d7..9e50f7c 100644
--- a/proxy.c
+++ b/proxy.c
@@ -266,6 +266,48 @@ err:
 	return 0;
 }
 
+char *
+auto_config(void)
+{
+	static const char auto_json[] = {
+		"[ {\n"
+		"  \"name\": \"fs_autostart\",\n"
+		"  \"type\": \"fs\",\n"
+		"  \"path\": \"" AUTO_DIR_FS "\"\n"
+		"} ]\n",
+	};
+	json_error_t	 err;
+
+	if (auto_start(db_port) != 0) {
+		return NULL;
+	}
+
+	config = json_loads(auto_json,&err);
+	if (!config) {
+		fprintf(stderr,"JSON error on line %d: %s\n",err.line,err.text);
+		return NULL;
+	}
+
+	if (json_typeof(config) != JSON_ARRAY) {
+		goto out_ie;
+	}
+
+	if (json_array_size(config) != 1) {
+		goto out_ie;
+	}
+
+	if (!validate_server(0)) {
+		goto out_ie;
+	}
+
+	printf("no replication servers defined, auto-start in effect\n");
+	return set_config();
+
+out_ie:
+	error(0, 0, "invalid autostart configuration (internal error)");
+	return NULL;
+}
+
 static size_t
 junk_writer (/* const */ void *ptr, size_t size, size_t nmemb, void *stream)
 {
diff --git a/proxy.h b/proxy.h
index 22efcd0..e09cfa4 100644
--- a/proxy.h
+++ b/proxy.h
@@ -31,6 +31,7 @@ typedef struct _provider {
 } provider_t;
 
 char	*parse_config		(void);
+char	*auto_config		(void);
 void	 repl_init		(void);
 void	 replicate		(const char *url, size_t size,
 				 const char *policy);
@@ -42,4 +43,7 @@ void	 update_provider	(const char *provider,
 char	*get_provider_value	(int i, const char *fname);
 int	 get_rep_count		(void);
 
+int	 auto_start		(int dbport);
+void	 auto_stop		(void);
+
 #endif
diff --git a/rest.c b/rest.c
index ca3dd6e..7043c48 100644
--- a/rest.c
+++ b/rest.c
@@ -71,6 +71,7 @@ typedef struct {
 
 static int			 fs_mode	= 0;
 static unsigned short		 my_port	= MY_PORT;
+static int			 autostart	= 0;
 const char *program_name;
 
 static char *(reserved_name[]) = { "_default", "_query", "_new", NULL };
@@ -1849,6 +1850,7 @@ enum
 };
 
 static const struct option my_options[] = {
+	{ "autostart", no_argument,     NULL, 'a' },
 	{ "config",  required_argument, NULL, 'c' },
 	{ "db",      required_argument, NULL, 'd' },
 	{ "master",  required_argument, NULL, 'm' },
@@ -1875,6 +1877,7 @@ Usage: %s [OPTION]\n\
 Deltacloud image-warehouse daemon.\n\
 A configuration file must be specified.\n\
 \n\
+  -a, --autostart         start necessary back-end services\n\
   -c, --config=FILE       config file [required]\n\
   -d, --db=HOST_PORT      database server as ip[:port]\n\
   -m, --master=HOST_PORT  master (upstream) server as ip[:port]\n\
@@ -1904,7 +1907,10 @@ main (int argc, char **argv)
 
 	program_name = argv[0];
 
-	for (;;) switch (getopt_long(argc,argv,"c:d:m:p:v",my_options,NULL)) {
+	for (;;) switch (getopt_long(argc,argv,"ac:d:m:p:v",my_options,NULL)) {
+	case 'a':
+		++autostart;
+		break;
 	case 'c':
 		cfg_file = optarg;
 		break;
@@ -1946,16 +1952,32 @@ main (int argc, char **argv)
 	}
 args_done:
 
-	if (!cfg_file) {
-		error (0, 0, "no configuration file specified");
-		usage (EXIT_FAILURE);
+	if (!db_port) {
+		db_port = autostart ? AUTO_MONGOD_PORT : 27017;
 	}
 
-	me = parse_config();
-	if (!me) {
-		fprintf(stderr,"could not parse %s\n",cfg_file);
+	if (autostart && cfg_file) {
+		error(0,0,"do not use -c and -a simultaneously");
 		return !0;
 	}
+	else if (autostart && !cfg_file) {
+		me = auto_config();
+		if (!me) {
+			/* error printed */
+			return !0;
+		}
+	}
+	else if (!autostart && cfg_file) {
+		me = parse_config();
+		if (!me) {
+			error(0,0,"could not parse %s",cfg_file);
+			return !0;
+		}
+	}
+	else {
+		error(0,0,"specify at least -c or -a");
+		usage (EXIT_FAILURE);
+	}
 
 	sem_init(&the_sem,0,0);
 	if (proxy_host) {
@@ -1999,9 +2021,11 @@ args_done:
 		MHD_OPTION_END);
 	if (!the_daemon) {
 		fprintf(stderr,"Could not create daemon.\n");
+		auto_stop();
 		return !0;
 	}
 
 	sem_wait(&the_sem);
+	auto_stop();
 	return 0;
 }


More information about the iwhd-devel mailing list