diff --git a/redis.conf b/redis.conf index b2ee853cdc610b225d49324c6cf1675fc109e59f..38e258698992981f1893a10c6f043eb0af3869ad 100644 --- a/redis.conf +++ b/redis.conf @@ -36,6 +36,17 @@ # Note that Redis will write a pid file in /var/run/redis.pid when daemonized. daemonize no +# If you run Redis from upstart or systemd, Redis can interact with your +# supervision tree. Options: +# supervised no - no supervision interaction +# supervised upstart - signal upstart by putting Redis into SIGSTOP mode +# supervised systemd - signal systemd by writing READY=1 to $NOTIFY_SOCKET +# supervised auto - detect upstart or systemd method based on +# UPSTART_JOB or NOTIFY_SOCKET environment variables +# Note: these supervision methods only signal "process is ready." +# They do not enable continuous liveness pings back to your supervisor. +supervised no + # When running daemonized, Redis writes a pid file in /var/run/redis.pid by # default. You can specify a custom pid file location here. pidfile /var/run/redis.pid diff --git a/src/config.c b/src/config.c index b0245954c048065f33429b5f3d3aa028efc2bfa5..8255a56b744b510c5235b152b0fc2eadb00dd48e 100644 --- a/src/config.c +++ b/src/config.c @@ -60,6 +60,8 @@ clientBufferLimitsConfig clientBufferLimitsDefaults[REDIS_CLIENT_TYPE_COUNT] = { * Config file parsing *----------------------------------------------------------------------------*/ +int supervisedToMode(const char *str); + int yesnotoi(char *s) { if (!strcasecmp(s,"yes")) return 1; else if (!strcasecmp(s,"no")) return 0; @@ -533,6 +535,15 @@ void loadServerConfigFromString(char *config) { goto loaderr; } server.notify_keyspace_events = flags; + } else if (!strcasecmp(argv[0],"supervised") && argc == 2) { + int mode = supervisedToMode(argv[1]); + + if (mode == -1) { + err = "Invalid option for 'supervised'. " + "Allowed values: 'upstart', 'systemd', 'auto', or 'no'"; + goto loaderr; + } + server.supervised_mode = mode; } else if (!strcasecmp(argv[0],"sentinel")) { /* argc == 1 is handled by main() as we need to enter the sentinel * mode ASAP. */ @@ -1022,6 +1033,33 @@ char *maxmemoryToString() { return s; } +int supervisedToMode(const char *str) { + int mode; + if (!strcasecmp(str,"upstart")) { + mode = REDIS_SUPERVISED_UPSTART; + } else if (!strcasecmp(str,"systemd")) { + mode = REDIS_SUPERVISED_SYSTEMD; + } else if (!strcasecmp(str,"auto")) { + mode = REDIS_SUPERVISED_AUTODETECT; + } else if (!strcasecmp(str,"no")) { + mode = REDIS_SUPERVISED_NONE; + } else { + mode = -1; + } + return mode; +} + +char *supervisedToString(void) { + char *s; + switch(server.supervised_mode) { + case REDIS_SUPERVISED_UPSTART: s = "upstart"; break; + case REDIS_SUPERVISED_SYSTEMD: s = "systemd"; break; + case REDIS_SUPERVISED_AUTODETECT: s = "auto"; break; + case REDIS_SUPERVISED_NONE: s = "no"; break; + default: s = "no"; break; + } + return s; +} void configGetCommand(redisClient *c) { robj *o = c->argv[2]; void *replylen = addDeferredMultiBulkLength(c); @@ -1177,6 +1215,11 @@ void configGetCommand(redisClient *c) { addReplyBulkCString(c,s); matches++; } + if (stringmatch(pattern,"supervised",0)) { + addReplyBulkCString(c,"supervised"); + addReplyBulkCString(c,supervisedToString()); + matches++; + } if (stringmatch(pattern,"client-output-buffer-limit",0)) { sds buf = sdsempty(); int j; @@ -1872,6 +1915,12 @@ int rewriteConfig(char *path) { rewriteConfigNumericalOption(state,"hz",server.hz,REDIS_DEFAULT_HZ); rewriteConfigYesNoOption(state,"aof-rewrite-incremental-fsync",server.aof_rewrite_incremental_fsync,REDIS_DEFAULT_AOF_REWRITE_INCREMENTAL_FSYNC); rewriteConfigYesNoOption(state,"aof-load-truncated",server.aof_load_truncated,REDIS_DEFAULT_AOF_LOAD_TRUNCATED); + rewriteConfigEnumOption(state,"supervised",server.supervised_mode, + "upstart", REDIS_SUPERVISED_UPSTART, + "systemd", REDIS_SUPERVISED_SYSTEMD, + "auto", REDIS_SUPERVISED_AUTODETECT, + "no", REDIS_SUPERVISED_NONE, + NULL, REDIS_SUPERVISED_NONE); if (server.sentinel_mode) rewriteConfigSentinelOption(state); /* Step 3: remove all the orphaned lines in the old file, that is, lines diff --git a/src/redis.c b/src/redis.c index fd0d39bb826d18fb8ff9191db47bfed0923ab52e..13df8d28e539b131b93b2f587c46cde80c93beeb 100644 --- a/src/redis.c +++ b/src/redis.c @@ -1416,6 +1416,7 @@ void initServerConfig(void) { server.syslog_facility = LOG_LOCAL0; server.daemonize = REDIS_DEFAULT_DAEMONIZE; server.supervised = 0; + server.supervised_mode = REDIS_SUPERVISED_NONE; server.aof_state = REDIS_AOF_OFF; server.aof_fsync = REDIS_DEFAULT_AOF_FSYNC; server.aof_no_fsync_on_rewrite = REDIS_DEFAULT_AOF_NO_FSYNC_ON_REWRITE; @@ -3591,8 +3592,23 @@ void redisSetProcTitle(char *title) { /* * Check whether systemd or upstart have been used to start redis. */ -int redisIsSupervised(void) { + +int redisSupervisedUpstart(void) { const char *upstart_job = getenv("UPSTART_JOB"); + + if (!upstart_job) { + redisLog(REDIS_WARNING, + "upstart supervision requested, but UPSTART_JOB not found"); + return 0; + } + + redisLog(REDIS_NOTICE, "supervised by upstart, will stop to signal readyness"); + raise(SIGSTOP); + unsetenv("UPSTART_JOB"); + return 1; +} + +int redisSupervisedSystemd(void) { const char *notify_socket = getenv("NOTIFY_SOCKET"); int fd = 1; struct sockaddr_un su; @@ -3600,31 +3616,24 @@ int redisIsSupervised(void) { struct msghdr hdr; int sendto_flags = 0; - if (upstart_job == NULL && notify_socket == NULL) + if (!notify_socket) { + redisLog(REDIS_WARNING, + "systemd supervision requested, but NOTIFY_SOCKET not found"); return 0; - - if (upstart_job != NULL) { - redisLog(REDIS_NOTICE, "supervised by upstart, will stop to signal readyness"); - raise(SIGSTOP); - unsetenv("UPSTART_JOB"); - - return 1; } - /* - * If we got here, we're supervised by systemd. - */ - if ((strchr("@/", notify_socket[0])) == NULL || - strlen(notify_socket) < 2) + if ((strchr("@/", notify_socket[0])) == NULL || strlen(notify_socket) < 2) { return 0; + } redisLog(REDIS_NOTICE, "supervised by systemd, will signal readyness"); - if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) < 0) { - redisLog(REDIS_WARNING, "cannot contact systemd socket %s", notify_socket); + if ((fd = socket(AF_UNIX, SOCK_DGRAM, 0)) == -1) { + redisLog(REDIS_WARNING, + "Can't connect to systemd socket %s", notify_socket); return 0; } - bzero(&su, sizeof(su)); + memset(&su, 0, sizeof(su)); su.sun_family = AF_UNIX; strncpy (su.sun_path, notify_socket, sizeof(su.sun_path) -1); su.sun_path[sizeof(su.sun_path) - 1] = '\0'; @@ -3632,11 +3641,11 @@ int redisIsSupervised(void) { if (notify_socket[0] == '@') su.sun_path[0] = '\0'; - bzero(&iov, sizeof(iov)); + memset(&iov, 0, sizeof(iov)); iov.iov_base = "READY=1"; iov.iov_len = strlen("READY=1"); - bzero(&hdr, sizeof(hdr)); + memset(&hdr, 0, sizeof(hdr)); hdr.msg_name = &su; hdr.msg_namelen = offsetof(struct sockaddr_un, sun_path) + strlen(notify_socket); @@ -3648,7 +3657,7 @@ int redisIsSupervised(void) { sendto_flags |= MSG_NOSIGNAL; #endif if (sendmsg(fd, &hdr, sendto_flags) < 0) { - redisLog(REDIS_WARNING, "Cannot send notification to systemd"); + redisLog(REDIS_WARNING, "Can't send notification to systemd"); close(fd); return 0; } @@ -3656,6 +3665,26 @@ int redisIsSupervised(void) { return 1; } +int redisIsSupervised(int mode) { + if (mode == REDIS_SUPERVISED_AUTODETECT) { + const char *upstart_job = getenv("UPSTART_JOB"); + const char *notify_socket = getenv("NOTIFY_SOCKET"); + + if (upstart_job) { + redisSupervisedUpstart(); + } else if (notify_socket) { + redisSupervisedSystemd(); + } + } else if (mode == REDIS_SUPERVISED_UPSTART) { + return redisSupervisedUpstart(); + } else if (mode == REDIS_SUPERVISED_SYSTEMD) { + return redisSupervisedSystemd(); + } + + return 0; +} + + int main(int argc, char **argv) { struct timeval tv; @@ -3762,8 +3791,8 @@ int main(int argc, char **argv) { redisLog(REDIS_WARNING, "Warning: no config file specified, using the default config. In order to specify a config file use %s /path/to/%s.conf", argv[0], server.sentinel_mode ? "sentinel" : "redis"); } - server.supervised = redisIsSupervised(); - int background = server.daemonize && server.supervised == 0; + server.supervised = redisIsSupervised(server.supervised_mode); + int background = server.daemonize && !server.supervised; if (background) daemonize(); initServer(); if (background || server.pidfile) createPidFile(); diff --git a/src/redis.h b/src/redis.h index 54046ef27051aee120ea1cebed21321966e8d016..89d97f55e1ce4c158445c176be020f1a3006386a 100644 --- a/src/redis.h +++ b/src/redis.h @@ -313,6 +313,12 @@ typedef long long mstime_t; /* millisecond time type. */ #define REDIS_LOG_RAW (1<<10) /* Modifier to log without timestamp */ #define REDIS_DEFAULT_VERBOSITY REDIS_NOTICE +/* Supervision options */ +#define REDIS_SUPERVISED_NONE 0 +#define REDIS_SUPERVISED_AUTODETECT 1 +#define REDIS_SUPERVISED_SYSTEMD 2 +#define REDIS_SUPERVISED_UPSTART 3 + /* Anti-warning macro... */ #define REDIS_NOTUSED(V) ((void) V) @@ -740,7 +746,8 @@ struct redisServer { int active_expire_enabled; /* Can be disabled for testing purposes. */ size_t client_max_querybuf_len; /* Limit for client query buffer length */ int dbnum; /* Total number of configured DBs */ - int supervised; /* True if supervised by upstart or systemd */ + int supervised; /* 1 if supervised, 0 otherwise. */ + int supervised_mode; /* See REDIS_SUPERVISED_* */ int daemonize; /* True if running as a daemon */ clientBufferLimitsConfig client_obuf_limits[REDIS_CLIENT_TYPE_COUNT]; /* AOF persistence */