diff --git a/sentinel.conf b/sentinel.conf index 39d6929e7..8647379d8 100644 --- a/sentinel.conf +++ b/sentinel.conf @@ -321,3 +321,21 @@ sentinel deny-scripts-reconfig yes # is possible to just rename a command to itself: # # SENTINEL rename-command mymaster CONFIG CONFIG + +# HOSTNAMES SUPPORT +# +# Normally Sentinel uses only IP addresses and requires SENTINEL MONITOR +# to specify an IP address. Also, it requires the Redis replica-announce-ip +# keyword to specify only IP addresses. +# +# You may enable hostnames support by enabling resolve-hostnames. Note +# that you must make sure your DNS is configured properly and that DNS +# resolution does not introduce very long delays. +# +SENTINEL resolve-hostnames no + +# When resolve-hostnames is enabled, Sentinel still uses IP addresses +# when exposing instances to users, configuration files, etc. If you want +# to retain the hostnames when announced, enable announce-hostnames below. +# +SENTINEL announce-hostnames no diff --git a/src/anet.c b/src/anet.c index f2c39b200..0bfa575f5 100644 --- a/src/anet.c +++ b/src/anet.c @@ -235,14 +235,13 @@ int anetRecvTimeout(char *err, int fd, long long ms) { return ANET_OK; } -/* anetGenericResolve() is called by anetResolve() and anetResolveIP() to - * do the actual work. It resolves the hostname "host" and set the string - * representation of the IP address into the buffer pointed by "ipbuf". +/* Resolve the hostname "host" and set the string representation of the + * IP address into the buffer pointed by "ipbuf". * * If flags is set to ANET_IP_ONLY the function only resolves hostnames * that are actually already IPv4 or IPv6 addresses. This turns the function * into a validating / normalizing function. */ -int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, +int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags) { struct addrinfo hints, *info; @@ -269,14 +268,6 @@ int anetGenericResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, return ANET_OK; } -int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len) { - return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_NONE); -} - -int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len) { - return anetGenericResolve(err,host,ipbuf,ipbuf_len,ANET_IP_ONLY); -} - static int anetSetReuseAddr(char *err, int fd) { int yes = 1; /* Make sure connection-intensive things like the redis benchmark diff --git a/src/anet.h b/src/anet.h index dc3cbeb32..5da2f3b46 100644 --- a/src/anet.h +++ b/src/anet.h @@ -60,8 +60,7 @@ int anetTcpNonBlockBestEffortBindConnect(char *err, const char *addr, int port, int anetUnixConnect(char *err, const char *path); int anetUnixNonBlockConnect(char *err, const char *path); int anetRead(int fd, char *buf, int count); -int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len); -int anetResolveIP(char *err, char *host, char *ipbuf, size_t ipbuf_len); +int anetResolve(char *err, char *host, char *ipbuf, size_t ipbuf_len, int flags); int anetTcpServer(char *err, int port, char *bindaddr, int backlog); int anetTcp6Server(char *err, int port, char *bindaddr, int backlog); int anetUnixServer(char *err, char *path, mode_t perm, int backlog); diff --git a/src/sentinel.c b/src/sentinel.c index 7f79e702a..a87766ebe 100644 --- a/src/sentinel.c +++ b/src/sentinel.c @@ -55,7 +55,8 @@ extern SSL_CTX *redis_tls_client_ctx; /* Address object, used to describe an ip:port pair. */ typedef struct sentinelAddr { - char *ip; + char *hostname; /* Hostname OR address, as specified */ + char *ip; /* Always a resolved address */ int port; } sentinelAddr; @@ -94,6 +95,8 @@ typedef struct sentinelAddr { #define SENTINEL_ELECTION_TIMEOUT 10000 #define SENTINEL_MAX_DESYNC 1000 #define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 1 +#define SENTINEL_DEFAULT_RESOLVE_HOSTNAMES 0 +#define SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES 0 /* Failover machine different states. */ #define SENTINEL_FAILOVER_STATE_NONE 0 /* No failover in progress. */ @@ -260,6 +263,8 @@ struct sentinelState { paths at runtime? */ char *sentinel_auth_pass; /* Password to use for AUTH against other sentinel */ char *sentinel_auth_user; /* Username for ACLs AUTH against other sentinel. */ + int resolve_hostnames; /* Support use of hostnames, assuming DNS is well configured. */ + int announce_hostnames; /* Announce hostnames instead of IPs when we have them. */ } sentinel; /* A script execution job. */ @@ -387,7 +392,7 @@ sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master); void sentinelScheduleScriptExecution(char *path, ...); void sentinelStartFailover(sentinelRedisInstance *master); void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privdata); -int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port); +int sentinelSendSlaveOf(sentinelRedisInstance *ri, const sentinelAddr *addr); char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch); void sentinelFlushConfig(void); void sentinelGenerateInitialMonitorEvents(void); @@ -455,6 +460,8 @@ void sentinelInfoCommand(client *c); void sentinelSetCommand(client *c); void sentinelPublishCommand(client *c); void sentinelRoleCommand(client *c); +void sentinelConfigGetCommand(client *c); +void sentinelConfigSetCommand(client *c); struct redisCommand sentinelcmds[] = { {"ping",pingCommand,1,"fast @connection",0,NULL,0,0,0,0,0}, @@ -535,6 +542,8 @@ void initSentinel(void) { sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG; sentinel.sentinel_auth_pass = NULL; sentinel.sentinel_auth_user = NULL; + sentinel.resolve_hostnames = SENTINEL_DEFAULT_RESOLVE_HOSTNAMES; + sentinel.announce_hostnames = SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES; memset(sentinel.myid,0,sizeof(sentinel.myid)); server.sentinel_config = NULL; } @@ -590,11 +599,13 @@ sentinelAddr *createSentinelAddr(char *hostname, int port) { errno = EINVAL; return NULL; } - if (anetResolve(NULL,hostname,ip,sizeof(ip)) == ANET_ERR) { + if (anetResolve(NULL,hostname,ip,sizeof(ip), + sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) { errno = ENOENT; return NULL; } sa = zmalloc(sizeof(*sa)); + sa->hostname = sdsnew(hostname); sa->ip = sdsnew(ip); sa->port = port; return sa; @@ -605,6 +616,7 @@ sentinelAddr *dupSentinelAddr(sentinelAddr *src) { sentinelAddr *sa; sa = zmalloc(sizeof(*sa)); + sa->hostname = sdsnew(src->hostname); sa->ip = sdsnew(src->ip); sa->port = src->port; return sa; @@ -612,6 +624,7 @@ sentinelAddr *dupSentinelAddr(sentinelAddr *src) { /* Free a Sentinel address. Can't fail. */ void releaseSentinelAddr(sentinelAddr *sa) { + sdsfree(sa->hostname); sdsfree(sa->ip); zfree(sa); } @@ -621,6 +634,21 @@ int sentinelAddrIsEqual(sentinelAddr *a, sentinelAddr *b) { return a->port == b->port && !strcasecmp(a->ip,b->ip); } +/* Return non-zero if a hostname matches an address. */ +int sentinelAddrEqualsHostname(sentinelAddr *a, char *hostname) { + char ip[NET_IP_STR_LEN]; + + /* We always resolve the hostname and compare it to the address */ + if (anetResolve(NULL, hostname, ip, sizeof(ip), + sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) + return 0; + return !strcasecmp(a->ip, ip); +} + +const char *announceSentinelAddr(const sentinelAddr *a) { + return sentinel.announce_hostnames ? a->hostname : a->ip; +} + /* =========================== Events notification ========================== */ /* Send an event to log, pub/sub, user notification script. @@ -661,12 +689,12 @@ void sentinelEvent(int level, char *type, sentinelRedisInstance *ri, if (master) { snprintf(msg, sizeof(msg), "%s %s %s %d @ %s %s %d", sentinelRedisInstanceTypeStr(ri), - ri->name, ri->addr->ip, ri->addr->port, - master->name, master->addr->ip, master->addr->port); + ri->name, announceSentinelAddr(ri->addr), ri->addr->port, + master->name, announceSentinelAddr(master->addr), master->addr->port); } else { snprintf(msg, sizeof(msg), "%s %s %s %d", sentinelRedisInstanceTypeStr(ri), - ri->name, ri->addr->ip, ri->addr->port); + ri->name, announceSentinelAddr(ri->addr), ri->addr->port); } fmt += 2; } else { @@ -988,7 +1016,8 @@ void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, cha sentinelScheduleScriptExecution(master->client_reconfig_script, master->name, (role == SENTINEL_LEADER) ? "leader" : "observer", - state, from->ip, fromport, to->ip, toport, NULL); + state, announceSentinelAddr(from), fromport, + announceSentinelAddr(to), toport, NULL); } /* =============================== instanceLink ============================= */ @@ -1114,6 +1143,35 @@ int sentinelTryConnectionSharing(sentinelRedisInstance *ri) { return C_ERR; } +/* Drop all connections to other sentinels. Returns the number of connections + * dropped.*/ +int sentinelDropConnections(void) { + dictIterator *di; + dictEntry *de; + int dropped = 0; + + di = dictGetIterator(sentinel.masters); + while ((de = dictNext(di)) != NULL) { + dictIterator *sdi; + dictEntry *sde; + + sentinelRedisInstance *ri = dictGetVal(de); + sdi = dictGetIterator(ri->sentinels); + while ((sde = dictNext(sdi)) != NULL) { + sentinelRedisInstance *si = dictGetVal(sde); + if (!si->link->disconnected) { + instanceLinkCloseConnection(si->link, si->link->pc); + instanceLinkCloseConnection(si->link, si->link->cc); + dropped++; + } + } + dictReleaseIterator(sdi); + } + dictReleaseIterator(di); + + return dropped; +} + /* When we detect a Sentinel to switch address (reporting a different IP/port * pair in Hello messages), let's update all the matching Sentinels in the * context of other masters as well and disconnect the links, so that everybody @@ -1226,7 +1284,7 @@ sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char * /* For slaves use ip:port as name. */ if (flags & SRI_SLAVE) { - anetFormatAddr(slavename, sizeof(slavename), hostname, port); + anetFormatAddr(slavename, sizeof(slavename), addr->ip, port); name = slavename; } @@ -1337,14 +1395,25 @@ void releaseSentinelRedisInstance(sentinelRedisInstance *ri) { /* Lookup a slave in a master Redis instance, by ip and port. */ sentinelRedisInstance *sentinelRedisInstanceLookupSlave( - sentinelRedisInstance *ri, char *ip, int port) + sentinelRedisInstance *ri, char *slave_addr, int port) { sds key; sentinelRedisInstance *slave; char buf[NET_ADDR_STR_LEN]; + sentinelAddr *addr; serverAssert(ri->flags & SRI_MASTER); - anetFormatAddr(buf,sizeof(buf),ip,port); + + /* We need to handle a slave_addr that is potentially a hostname. + * If that is the case, depending on configuration we either resolve + * it and use the IP addres or fail. + */ + addr = createSentinelAddr(slave_addr, port); + if (!addr) return NULL; + + anetFormatAddr(buf,sizeof(buf),addr->ip,addr->port); + releaseSentinelAddr(addr); + key = sdsnew(buf); slave = dictFetchValue(ri->slaves,key); sdsfree(key); @@ -1394,21 +1463,27 @@ int removeMatchingSentinelFromMaster(sentinelRedisInstance *master, char *runid) * of instances. Return NULL if not found, otherwise return the instance * pointer. * - * runid or ip can be NULL. In such a case the search is performed only + * runid or addr can be NULL. In such a case the search is performed only * by the non-NULL field. */ -sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, char *ip, int port, char *runid) { +sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, char *addr, int port, char *runid) { dictIterator *di; dictEntry *de; sentinelRedisInstance *instance = NULL; + sentinelAddr *ri_addr = NULL; - serverAssert(ip || runid); /* User must pass at least one search param. */ + serverAssert(addr || runid); /* User must pass at least one search param. */ + if (addr != NULL) { + /* Resolve addr, we use the IP as a key even if a hostname is used */ + ri_addr = createSentinelAddr(addr, port); + if (!ri_addr) return NULL; + } di = dictGetIterator(instances); while((de = dictNext(di)) != NULL) { sentinelRedisInstance *ri = dictGetVal(de); if (runid && !ri->runid) continue; if ((runid == NULL || strcmp(ri->runid, runid) == 0) && - (ip == NULL || (strcmp(ri->addr->ip, ip) == 0 && + (addr == NULL || (strcmp(ri->addr->ip, ri_addr->ip) == 0 && ri->addr->port == port))) { instance = ri; @@ -1416,6 +1491,9 @@ sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, c } } dictReleaseIterator(di); + if (ri_addr != NULL) + releaseSentinelAddr(ri_addr); + return instance; } @@ -1530,14 +1608,14 @@ int sentinelResetMastersByPattern(char *pattern, int flags) { * * The function returns C_ERR if the address can't be resolved for some * reason. Otherwise C_OK is returned. */ -int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, int port) { +int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hostname, int port) { sentinelAddr *oldaddr, *newaddr; sentinelAddr **slaves = NULL; int numslaves = 0, j; dictIterator *di; dictEntry *de; - newaddr = createSentinelAddr(ip,port); + newaddr = createSentinelAddr(hostname,port); if (newaddr == NULL) return C_ERR; /* There can be only 0 or 1 slave that has the newaddr. @@ -1551,8 +1629,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, sentinelRedisInstance *slave = dictGetVal(de); if (sentinelAddrIsEqual(slave->addr,newaddr)) continue; - slaves[numslaves++] = createSentinelAddr(slave->addr->ip, - slave->addr->port); + slaves[numslaves++] = dupSentinelAddr(slave->addr); } dictReleaseIterator(di); @@ -1560,8 +1637,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, * as a slave as well, so that we'll be able to sense / reconfigure * the old master. */ if (!sentinelAddrIsEqual(newaddr,master->addr)) { - slaves[numslaves++] = createSentinelAddr(master->addr->ip, - master->addr->port); + slaves[numslaves++] = dupSentinelAddr(master->addr); } /* Reset and switch address. */ @@ -1575,7 +1651,7 @@ int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *ip, for (j = 0; j < numslaves; j++) { sentinelRedisInstance *slave; - slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->ip, + slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->hostname, slaves[j]->port, master->quorum, master); releaseSentinelAddr(slaves[j]); if (slave) sentinelEvent(LL_NOTICE,"+slave",slave,"%@"); @@ -1959,6 +2035,16 @@ const char *sentinelHandleConfiguration(char **argv, int argc) { /* sentinel-pass */ if (strlen(argv[1])) sentinel.sentinel_auth_pass = sdsnew(argv[1]); + } else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) { + /* resolve-hostnames */ + if ((sentinel.resolve_hostnames = yesnotoi(argv[1])) == -1) { + return "Please specify yes or not for the resolve-hostnames option."; + } + } else if (!strcasecmp(argv[0],"announce-hostnames") && argc == 2) { + /* announce-hostnames */ + if ((sentinel.announce_hostnames = yesnotoi(argv[1])) == -1) { + return "Please specify yes or not for the announce-hostnames option."; + } } else { return "Unrecognized sentinel configuration statement."; } @@ -1985,6 +2071,21 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { rewriteConfigRewriteLine(state,"sentinel deny-scripts-reconfig",line, sentinel.deny_scripts_reconfig != SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG); + /* sentinel resolve-hostnames. + * This must be included early in the file so it is already in effect + * when reading the file. + */ + line = sdscatprintf(sdsempty(), "sentinel resolve-hostnames %s", + sentinel.resolve_hostnames ? "yes" : "no"); + rewriteConfigRewriteLine(state,"sentinel",line, + sentinel.resolve_hostnames != SENTINEL_DEFAULT_RESOLVE_HOSTNAMES); + + /* sentinel announce-hostnames. */ + line = sdscatprintf(sdsempty(), "sentinel announce-hostnames %s", + sentinel.announce_hostnames ? "yes" : "no"); + rewriteConfigRewriteLine(state,"sentinel",line, + sentinel.announce_hostnames != SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES); + /* For every master emit a "sentinel monitor" config entry. */ di = dictGetIterator(sentinel.masters); while((de = dictNext(di)) != NULL) { @@ -1995,7 +2096,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { master = dictGetVal(de); master_addr = sentinelGetCurrentMasterAddress(master); line = sdscatprintf(sdsempty(),"sentinel monitor %s %s %d %d", - master->name, master_addr->ip, master_addr->port, + master->name, announceSentinelAddr(master_addr), master_addr->port, master->quorum); rewriteConfigRewriteLine(state,"sentinel monitor",line,1); /* rewriteConfigMarkAsProcessed is handled after the loop */ @@ -2095,7 +2196,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { slave_addr = master->addr; line = sdscatprintf(sdsempty(), "sentinel known-replica %s %s %d", - master->name, slave_addr->ip, slave_addr->port); + master->name, announceSentinelAddr(slave_addr), slave_addr->port); rewriteConfigRewriteLine(state,"sentinel known-replica",line,1); /* rewriteConfigMarkAsProcessed is handled after the loop */ } @@ -2108,7 +2209,7 @@ void rewriteConfigSentinelOption(struct rewriteConfigState *state) { if (ri->runid == NULL) continue; line = sdscatprintf(sdsempty(), "sentinel known-sentinel %s %s %d %s", - master->name, ri->addr->ip, ri->addr->port, ri->runid); + master->name, announceSentinelAddr(ri->addr), ri->addr->port, ri->runid); rewriteConfigRewriteLine(state,"sentinel known-sentinel",line,1); /* rewriteConfigMarkAsProcessed is handled after the loop */ } @@ -2242,7 +2343,7 @@ void sentinelSendAuthIfNeeded(sentinelRedisInstance *ri, redisAsyncContext *c) { auth_user = ri->master->auth_user; } else if (ri->flags & SRI_SENTINEL) { /* If sentinel_auth_user is NULL, AUTH will use default user - with sentinel_auth_pass to autenticate */ + with sentinel_auth_pass to authenticate */ if (sentinel.sentinel_auth_pass) { auth_pass = sentinel.sentinel_auth_pass; auth_user = sentinel.sentinel_auth_user; @@ -2589,9 +2690,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { sentinelRedisInstanceNoDownFor(ri,wait_time) && mstime() - ri->role_reported_time > wait_time) { - int retval = sentinelSendSlaveOf(ri, - ri->master->addr->ip, - ri->master->addr->port); + int retval = sentinelSendSlaveOf(ri,ri->master->addr); if (retval == C_OK) sentinelEvent(LL_NOTICE,"+convert-to-slave",ri,"%@"); } @@ -2602,7 +2701,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { if ((ri->flags & SRI_SLAVE) && role == SRI_SLAVE && (ri->slave_master_port != ri->master->addr->port || - strcasecmp(ri->slave_master_host,ri->master->addr->ip))) + !sentinelAddrEqualsHostname(ri->master->addr, ri->slave_master_host))) { mstime_t wait_time = ri->master->failover_timeout; @@ -2612,9 +2711,7 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { sentinelRedisInstanceNoDownFor(ri,wait_time) && mstime() - ri->slave_conf_change_time > wait_time) { - int retval = sentinelSendSlaveOf(ri, - ri->master->addr->ip, - ri->master->addr->port); + int retval = sentinelSendSlaveOf(ri,ri->master->addr); if (retval == C_OK) sentinelEvent(LL_NOTICE,"+fix-slave-config",ri,"%@"); } @@ -2628,8 +2725,8 @@ void sentinelRefreshInstanceInfo(sentinelRedisInstance *ri, const char *info) { /* SRI_RECONF_SENT -> SRI_RECONF_INPROG. */ if ((ri->flags & SRI_RECONF_SENT) && ri->slave_master_host && - strcmp(ri->slave_master_host, - ri->master->promoted_slave->addr->ip) == 0 && + sentinelAddrEqualsHostname(ri->master->promoted_slave->addr, + ri->slave_master_host) && ri->slave_master_port == ri->master->promoted_slave->addr->port) { ri->flags &= ~SRI_RECONF_SENT; @@ -2806,7 +2903,7 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) { if (si && master->config_epoch < master_config_epoch) { master->config_epoch = master_config_epoch; if (master_port != master->addr->port || - strcmp(master->addr->ip, token[5])) + !sentinelAddrEqualsHostname(master->addr, token[5])) { sentinelAddr *old_addr; @@ -2814,7 +2911,7 @@ void sentinelProcessHelloMessage(char *hello, int hello_len) { sentinelEvent(LL_WARNING,"+switch-master", master,"%s %s %d %s %d", master->name, - master->addr->ip, master->addr->port, + announceSentinelAddr(master->addr), master->addr->port, token[5], master_port); old_addr = dupSentinelAddr(master->addr); @@ -2907,7 +3004,7 @@ int sentinelSendHello(sentinelRedisInstance *ri) { announce_ip, announce_port, sentinel.myid, (unsigned long long) sentinel.current_epoch, /* --- */ - master->name,master_addr->ip,master_addr->port, + master->name,announceSentinelAddr(master_addr),master_addr->port, (unsigned long long) master->config_epoch); retval = redisAsyncCommand(ri->link->cc, sentinelPublishReplyCallback, ri, "%s %s %s", @@ -3041,6 +3138,101 @@ void sentinelSendPeriodicCommands(sentinelRedisInstance *ri) { /* =========================== SENTINEL command ============================= */ +/* SENTINEL CONFIG SET