RFC: autostart mongo patch

Pete Zaitcev zaitcev at redhat.com
Thu Oct 7 20:46:40 UTC 2010


Starting mongod makes a lot of sense for us, so I took the code that
autostarted tabled and adapted it to run mongod. Seems to work acceptably.

The RFC is:

 1. This uses -a to cause autostart, which was appropriate for Hail.
    For Mongo, we can do this "if we detected no node-wide Mongo,
    start our own". Is this what we want? Does it not violate KISS?

 2. directory layout is to use current directory... and do it before
    the fs back-end changed into local_path (not set for -a).
    Is this confusing?

-- Pete

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		\
diff --git a/auto.c b/auto.c
new file mode 100644
index 0000000..5e2c478
--- /dev/null
+++ b/auto.c
@@ -0,0 +1,262 @@
+#include <config.h>
+
+#include <errno.h>
+#include <error.h>
+#include <fcntl.h>
+#include <netdb.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 <httputil.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_mongod[] = {
+	"mongod",
+	"--port", AUTO_MONGOD_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_MONGOD_PORT,
+	"--dbpath", AUTO_DIR_DB,
+	"--logpath", AUTO_MONGOD_LOG,
+	NULL
+};
+
+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 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 0;
+}
+
+/*
+ * 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));
+		exit(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_MONGOD_PORT) != 0) {
+		/*
+		 * Most likely nothing else would work anyway. So abend.
+		 */
+		error(0, 0, "unable to resolve host %s port %s",
+		      AUTO_HOST, AUTO_MONGOD_PORT);
+		return -1;
+	}
+
+	DPRINTF("trying to connect to mongod (host %s port %s) ...\n",
+		AUTO_HOST, AUTO_MONGOD_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 "
+			      AUTO_MONGOD_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;
+}
+
+int
+auto_start (void)
+{
+	int rc;
+	char **earg;
+
+	if (auto_prepare_area() < 0)
+		return -1;
+
+	rc = auto_test_mongod();
+	if (rc < 0)
+		return -1;
+	if (rc) {
+		DPRINTF("auto-starting mongod\n");
+		earg = verbose ? auto_arg_mongod : auto_arg_mongod_quiet;
+		if (auto_spawn(AUTO_BIN_MONGOD, earg) < 0)
+			return -1;
+		if (auto_wait_mongod() < 0)
+			return -1;
+	}
+
+	DPRINTF("mongod listens on port %s\n", AUTO_MONGOD_PORT);
+	return 0;
+}
diff --git a/iwh.h b/iwh.h
index 091c3c0..d1e5e1d 100644
--- a/iwh.h
+++ b/iwh.h
@@ -66,3 +66,20 @@ 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.
+ *
+ * Mongo is run on the default port so that the node's instance is used if any.
+ */
+#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 "27017"
diff --git a/proxy.c b/proxy.c
index 73d67d7..5ead1d2 100644
--- a/proxy.c
+++ b/proxy.c
@@ -266,6 +266,44 @@ 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() != 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) {
+		abort();
+	}
+
+	if (json_array_size(config) != 1) {
+		abort();
+	}
+
+	if (!validate_server(0)) {
+		abort();
+	}
+
+	printf("no replication servers defined, auto-start in effect\n");
+	return set_config();
+}
+
 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..2d79ea1 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);
@@ -40,6 +41,9 @@ int	 get_provider		(int i, provider_t *out);
 void	 update_provider	(const char *provider,
 				 const char *username, const char *password);
 char	*get_provider_value	(int i, const char *fname);
+char	*follow_link		(char *object, char *key);
 int	 get_rep_count		(void);
 
+int      auto_start		(void);
+
 #endif
diff --git a/rest.c b/rest.c
index 17dad68..456d2a5 100644
--- a/rest.c
+++ b/rest.c
@@ -69,8 +69,8 @@ typedef struct {
 	MHD_AccessHandlerCallback	 handler;
 } rule;
 
-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 };
@@ -1736,6 +1737,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' },
@@ -1762,6 +1764,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\
@@ -1790,7 +1793,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;
@@ -1832,16 +1838,28 @@ main (int argc, char **argv)
 	}
 args_done:
 
-	if (!cfg_file) {
-		error (0, 0, "no configuration file specified");
-		usage (EXIT_FAILURE);
-	}
-
-	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) {


More information about the iwhd-devel mailing list