summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--sentinel.conf18
-rw-r--r--src/anet.c15
-rw-r--r--src/anet.h3
-rw-r--r--src/sentinel.c326
-rw-r--r--tests/instances.tcl11
-rw-r--r--tests/sentinel/tests/08-hostname-conf.tcl62
-rw-r--r--tests/sentinel/tests/09-acl-support.tcl50
-rw-r--r--tests/sentinel/tests/includes/init-tests.tcl6
8 files changed, 411 insertions, 80 deletions
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 <password> */
if (strlen(argv[1]))
sentinel.sentinel_auth_pass = sdsnew(argv[1]);
+ } else if (!strcasecmp(argv[0],"resolve-hostnames") && argc == 2) {
+ /* resolve-hostnames <yes|no> */
+ 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 <yes|no> */
+ 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 <option> */
+void sentinelConfigSetCommand(client *c) {
+ robj *o = c->argv[3];
+ robj *val = c->argv[4];
+ long long numval;
+ int drop_conns = 0;
+
+ if (!strcasecmp(o->ptr, "resolve-hostnames")) {
+ if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
+ sentinel.resolve_hostnames = numval;
+ } else if (!strcasecmp(o->ptr, "announce-hostnames")) {
+ if ((numval = yesnotoi(val->ptr)) == -1) goto badfmt;
+ sentinel.announce_hostnames = numval;
+ } else if (!strcasecmp(o->ptr, "announce-ip")) {
+ if (sentinel.announce_ip) sdsfree(sentinel.announce_ip);
+ sentinel.announce_ip = sdsnew(val->ptr);
+ } else if (!strcasecmp(o->ptr, "announce-port")) {
+ if (getLongLongFromObject(val, &numval) == C_ERR ||
+ numval < 0 || numval > 65535)
+ goto badfmt;
+ sentinel.announce_port = numval;
+ } else if (!strcasecmp(o->ptr, "sentinel-user")) {
+ sdsfree(sentinel.sentinel_auth_user);
+ sentinel.sentinel_auth_user = sdsnew(val->ptr);
+ drop_conns = 1;
+ } else if (!strcasecmp(o->ptr, "sentinel-pass")) {
+ sdsfree(sentinel.sentinel_auth_pass);
+ sentinel.sentinel_auth_pass = sdsnew(val->ptr);
+ drop_conns = 1;
+ } else {
+ addReplyErrorFormat(c, "Invalid argument '%s' to SENTINEL CONFIG SET",
+ (char *) o->ptr);
+ return;
+ }
+
+ sentinelFlushConfig();
+ addReply(c, shared.ok);
+
+ /* Drop Sentinel connections to initiate a reconnect if needed. */
+ if (drop_conns)
+ sentinelDropConnections();
+
+ return;
+
+badfmt:
+ addReplyErrorFormat(c, "Invalid value '%s' to SENTINEL CONFIG SET '%s'",
+ (char *) val->ptr, (char *) o->ptr);
+}
+
+/* SENTINEL CONFIG GET <option> */
+void sentinelConfigGetCommand(client *c) {
+ robj *o = c->argv[3];
+ const char *pattern = o->ptr;
+ void *replylen = addReplyDeferredLen(c);
+ int matches = 0;
+
+ if (stringmatch(pattern,"resolve-hostnames",1)) {
+ addReplyBulkCString(c,"resolve-hostnames");
+ addReplyBulkCString(c,sentinel.resolve_hostnames ? "yes" : "no");
+ matches++;
+ }
+
+ if (stringmatch(pattern, "announce-hostnames", 1)) {
+ addReplyBulkCString(c,"announce-hostnames");
+ addReplyBulkCString(c,sentinel.announce_hostnames ? "yes" : "no");
+ matches++;
+ }
+
+ if (stringmatch(pattern, "announce-ip", 1)) {
+ addReplyBulkCString(c,"announce-ip");
+ addReplyBulkCString(c,sentinel.announce_ip ? sentinel.announce_ip : "");
+ matches++;
+ }
+
+ if (stringmatch(pattern, "announce-port", 1)) {
+ addReplyBulkCString(c, "announce-port");
+ addReplyBulkLongLong(c, sentinel.announce_port);
+ matches++;
+ }
+
+ if (stringmatch(pattern, "sentinel-user", 1)) {
+ addReplyBulkCString(c, "sentinel-user");
+ addReplyBulkCString(c, sentinel.sentinel_auth_user ? sentinel.sentinel_auth_user : "");
+ matches++;
+ }
+
+ if (stringmatch(pattern, "sentinel-pass", 1)) {
+ addReplyBulkCString(c, "sentinel-pass");
+ addReplyBulkCString(c, sentinel.sentinel_auth_pass ? sentinel.sentinel_auth_pass : "");
+ matches++;
+ }
+
+ setDeferredMapLen(c, replylen, matches);
+}
+
const char *sentinelFailoverStateStr(int state) {
switch(state) {
case SENTINEL_FAILOVER_STATE_NONE: return "none";
@@ -3067,7 +3259,7 @@ void addReplySentinelRedisInstance(client *c, sentinelRedisInstance *ri) {
fields++;
addReplyBulkCString(c,"ip");
- addReplyBulkCString(c,ri->addr->ip);
+ addReplyBulkCString(c,announceSentinelAddr(ri->addr));
fields++;
addReplyBulkCString(c,"port");
@@ -3308,6 +3500,10 @@ void sentinelCommand(client *c) {
" Check if the current Sentinel configuration is able to reach the quorum",
" needed to failover a master and the majority needed to authorize the",
" failover.",
+"CONFIG SET <param> <value>",
+" Set a global Sentinel configuration parameter.",
+"CONFIG GET <param>",
+" Get global Sentinel configuration parameter.",
"GET-MASTER-ADDR-BY-NAME <master-name>",
" Return the ip and port number of the master with that name.",
"FAILOVER <master-name>",
@@ -3449,7 +3645,7 @@ NULL
sentinelAddr *addr = sentinelGetCurrentMasterAddress(ri);
addReplyArrayLen(c,2);
- addReplyBulkCString(c,addr->ip);
+ addReplyBulkCString(c,announceSentinelAddr(addr));
addReplyBulkLongLong(c,addr->port);
}
} else if (!strcasecmp(c->argv[1]->ptr,"failover")) {
@@ -3494,11 +3690,12 @@ NULL
return;
}
- /* Make sure the IP field is actually a valid IP before passing it
- * to createSentinelRedisInstance(), otherwise we may trigger a
- * DNS lookup at runtime. */
- if (anetResolveIP(NULL,c->argv[3]->ptr,ip,sizeof(ip)) == ANET_ERR) {
- addReplyError(c,"Invalid IP address specified");
+ /* If resolve-hostnames is used, actual DNS resolution may take place.
+ * Otherwise just validate address.
+ */
+ if (anetResolve(NULL,c->argv[3]->ptr,ip,sizeof(ip),
+ sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {
+ addReplyError(c, "Invalid IP address or hostname specified");
return;
}
@@ -3568,6 +3765,14 @@ NULL
} else if (!strcasecmp(c->argv[1]->ptr,"set")) {
if (c->argc < 3) goto numargserr;
sentinelSetCommand(c);
+ } else if (!strcasecmp(c->argv[1]->ptr,"config")) {
+ if (c->argc < 3) goto numargserr;
+ if (!strcasecmp(c->argv[2]->ptr,"set") && c->argc == 5)
+ sentinelConfigSetCommand(c);
+ else if (!strcasecmp(c->argv[2]->ptr,"get") && c->argc == 4)
+ sentinelConfigGetCommand(c);
+ else
+ addReplyError(c, "Only SENTINEL CONFIG GET <option> / SET <option> <value> are supported.");
} else if (!strcasecmp(c->argv[1]->ptr,"info-cache")) {
/* SENTINEL INFO-CACHE <name> */
if (c->argc < 2) goto numargserr;
@@ -3731,7 +3936,7 @@ void sentinelInfoCommand(client *c) {
"master%d:name=%s,status=%s,address=%s:%d,"
"slaves=%lu,sentinels=%lu\r\n",
master_id++, ri->name, status,
- ri->addr->ip, ri->addr->port,
+ announceSentinelAddr(ri->addr), ri->addr->port,
dictSize(ri->slaves),
dictSize(ri->sentinels)+1);
}
@@ -4127,7 +4332,7 @@ void sentinelAskMasterStateToOtherSentinels(sentinelRedisInstance *master, int f
sentinelReceiveIsMasterDownReply, ri,
"%s is-master-down-by-addr %s %s %llu %s",
sentinelInstanceMapCommand(ri,"SENTINEL"),
- master->addr->ip, port,
+ announceSentinelAddr(master->addr), port,
sentinel.current_epoch,
(master->failover_state > SENTINEL_FAILOVER_STATE_NONE) ?
sentinel.myid : "*");
@@ -4281,17 +4486,19 @@ char *sentinelGetLeader(sentinelRedisInstance *master, uint64_t epoch) {
* The command returns C_OK if the SLAVEOF command was accepted for
* (later) delivery otherwise C_ERR. The command replies are just
* discarded. */
-int sentinelSendSlaveOf(sentinelRedisInstance *ri, char *host, int port) {
+int sentinelSendSlaveOf(sentinelRedisInstance *ri, const sentinelAddr *addr) {
char portstr[32];
+ const char *host;
int retval;
- ll2string(portstr,sizeof(portstr),port);
-
/* If host is NULL we send SLAVEOF NO ONE that will turn the instance
- * into a master. */
- if (host == NULL) {
+ * into a master. */
+ if (!addr) {
host = "NO";
memcpy(portstr,"ONE",4);
+ } else {
+ host = announceSentinelAddr(addr);
+ ll2string(portstr,sizeof(portstr),addr->port);
}
/* In order to send SLAVEOF in a safe way, we send a transaction performing
@@ -4576,7 +4783,7 @@ void sentinelFailoverSendSlaveOfNoOne(sentinelRedisInstance *ri) {
* We actually register a generic callback for this command as we don't
* really care about the reply. We check if it worked indirectly observing
* if INFO returns a different role (master instead of slave). */
- retval = sentinelSendSlaveOf(ri->promoted_slave,NULL,0);
+ retval = sentinelSendSlaveOf(ri->promoted_slave,NULL);
if (retval != C_OK) return;
sentinelEvent(LL_NOTICE, "+failover-state-wait-promotion",
ri->promoted_slave,"%@");
@@ -4646,9 +4853,7 @@ void sentinelFailoverDetectEnd(sentinelRedisInstance *master) {
if (slave->flags & (SRI_PROMOTED|SRI_RECONF_DONE|SRI_RECONF_SENT)) continue;
if (slave->link->disconnected) continue;
- retval = sentinelSendSlaveOf(slave,
- master->promoted_slave->addr->ip,
- master->promoted_slave->addr->port);
+ retval = sentinelSendSlaveOf(slave,master->promoted_slave->addr);
if (retval == C_OK) {
sentinelEvent(LL_NOTICE,"+slave-reconf-sent-be",slave,"%@");
slave->flags |= SRI_RECONF_SENT;
@@ -4703,9 +4908,7 @@ void sentinelFailoverReconfNextSlave(sentinelRedisInstance *master) {
if (slave->link->disconnected) continue;
/* Send SLAVEOF <new master>. */
- retval = sentinelSendSlaveOf(slave,
- master->promoted_slave->addr->ip,
- master->promoted_slave->addr->port);
+ retval = sentinelSendSlaveOf(slave,master->promoted_slave->addr);
if (retval == C_OK) {
slave->flags |= SRI_RECONF_SENT;
slave->slave_reconf_sent_time = mstime();
@@ -4727,10 +4930,10 @@ void sentinelFailoverSwitchToPromotedSlave(sentinelRedisInstance *master) {
master->promoted_slave : master;
sentinelEvent(LL_WARNING,"+switch-master",master,"%s %s %d %s %d",
- master->name, master->addr->ip, master->addr->port,
- ref->addr->ip, ref->addr->port);
+ master->name, announceSentinelAddr(master->addr), master->addr->port,
+ announceSentinelAddr(ref->addr), ref->addr->port);
- sentinelResetMasterAndChangeAddress(master,ref->addr->ip,ref->addr->port);
+ sentinelResetMasterAndChangeAddress(master,ref->addr->hostname,ref->addr->port);
}
void sentinelFailoverStateMachine(sentinelRedisInstance *ri) {
@@ -4887,4 +5090,3 @@ void sentinelTimer(void) {
* election because of split brain voting). */
server.hz = CONFIG_DEFAULT_HZ + rand() % CONFIG_DEFAULT_HZ;
}
-
diff --git a/tests/instances.tcl b/tests/instances.tcl
index 03af2408b..b9d1b3fe5 100644
--- a/tests/instances.tcl
+++ b/tests/instances.tcl
@@ -28,6 +28,7 @@ set ::global_config {}
set ::sentinel_base_port 20000
set ::redis_base_port 30000
set ::redis_port_count 1024
+set ::host "127.0.0.1"
set ::pids {} ; # We kill everything at exit
set ::dirs {} ; # We remove all the temp dirs at exit
set ::run_matching {} ; # If non empty, only tests matching pattern are run.
@@ -128,18 +129,18 @@ proc spawn_instance {type base_port count {conf {}} {base_conf_file ""}} {
}
# Check availability finally
- if {[server_is_up 127.0.0.1 $port 100] == 0} {
+ if {[server_is_up $::host $port 100] == 0} {
set logfile [file join $dirname log.txt]
puts [exec tail $logfile]
abort_sentinel_test "Problems starting $type #$j: ping timeout, maybe server start failed, check $logfile"
}
# Push the instance into the right list
- set link [redis 127.0.0.1 $port 0 $::tls]
+ set link [redis $::host $port 0 $::tls]
$link reconnect 1
lappend ::${type}_instances [list \
pid $pid \
- host 127.0.0.1 \
+ host $::host \
port $port \
link $link \
]
@@ -241,6 +242,9 @@ proc parse_options {} {
set ::simulate_error 1
} elseif {$opt eq {--valgrind}} {
set ::valgrind 1
+ } elseif {$opt eq {--host}} {
+ incr j
+ set ::host ${val}
} elseif {$opt eq {--tls}} {
package require tls 1.6
::tls::init \
@@ -259,6 +263,7 @@ proc parse_options {} {
puts "--fail Simulate a test failure."
puts "--valgrind Run with valgrind."
puts "--tls Run tests in TLS mode."
+ puts "--host <host> Use hostname instead of 127.0.0.1."
puts "--config <k> <v> Extra config argument(s)."
puts "--help Shows this help."
exit 0
diff --git a/tests/sentinel/tests/08-hostname-conf.tcl b/tests/sentinel/tests/08-hostname-conf.tcl
new file mode 100644
index 000000000..45bb56687
--- /dev/null
+++ b/tests/sentinel/tests/08-hostname-conf.tcl
@@ -0,0 +1,62 @@
+proc set_redis_announce_ip {addr} {
+ foreach_redis_id id {
+ R $id config set replica-announce-ip $addr
+ }
+}
+
+proc set_sentinel_config {keyword value} {
+ foreach_sentinel_id id {
+ S $id sentinel config set $keyword $value
+ }
+}
+
+proc set_all_instances_hostname {hostname} {
+ foreach_sentinel_id id {
+ set_instance_attrib sentinel $id host $hostname
+ }
+ foreach_redis_id id {
+ set_instance_attrib redis $id host $hostname
+ }
+}
+
+test "(pre-init) Configure instances and sentinel for hostname use" {
+ set ::host "localhost"
+ restart_killed_instances
+ set_all_instances_hostname $::host
+ set_redis_announce_ip $::host
+ set_sentinel_config resolve-hostnames yes
+ set_sentinel_config announce-hostnames yes
+}
+
+source "../tests/includes/init-tests.tcl"
+
+proc verify_hostname_announced {hostname} {
+ foreach_sentinel_id id {
+ # Master is reported with its hostname
+ assert_equal [lindex [S $id SENTINEL GET-MASTER-ADDR-BY-NAME mymaster] 0] $hostname
+
+ # Replicas are reported with their hostnames
+ foreach replica [S $id SENTINEL REPLICAS mymaster] {
+ assert_equal [dict get $replica ip] $hostname
+ }
+ }
+}
+
+test "Sentinel announces hostnames" {
+ # Check initial state
+ verify_hostname_announced $::host
+
+ # Disable announce-hostnames and confirm IPs are used
+ set_sentinel_config announce-hostnames no
+ verify_hostname_announced "127.0.0.1"
+}
+
+# We need to revert any special configuration because all tests currently
+# share the same instances.
+test "(post-cleanup) Configure instances and sentinel for IPs" {
+ set ::host "127.0.0.1"
+ set_all_instances_hostname $::host
+ set_redis_announce_ip $::host
+ set_sentinel_config resolve-hostnames no
+ set_sentinel_config announce-hostnames no
+} \ No newline at end of file
diff --git a/tests/sentinel/tests/09-acl-support.tcl b/tests/sentinel/tests/09-acl-support.tcl
new file mode 100644
index 000000000..1366fc4d5
--- /dev/null
+++ b/tests/sentinel/tests/09-acl-support.tcl
@@ -0,0 +1,50 @@
+
+source "../tests/includes/init-tests.tcl"
+
+set ::user "testuser"
+set ::password "secret"
+
+proc setup_acl {} {
+ foreach_sentinel_id id {
+ assert_equal {OK} [S $id ACL SETUSER $::user >$::password +@all on]
+ assert_equal {OK} [S $id ACL SETUSER default off]
+
+ S $id CLIENT KILL USER default SKIPME no
+ assert_equal {OK} [S $id AUTH $::user $::password]
+ }
+}
+
+proc teardown_acl {} {
+ foreach_sentinel_id id {
+ assert_equal {OK} [S $id ACL SETUSER default on]
+ assert_equal {1} [S $id ACL DELUSER $::user]
+
+ S $id SENTINEL CONFIG SET sentinel-user ""
+ S $id SENTINEL CONFIG SET sentinel-pass ""
+ }
+}
+
+test "(post-init) Set up ACL configuration" {
+ setup_acl
+ assert_equal $::user [S 1 ACL WHOAMI]
+}
+
+test "SENTINEL CONFIG SET handles on-the-fly credentials reconfiguration" {
+ # Make sure we're starting with a broken state...
+ after 5000
+ catch {S 1 SENTINEL CKQUORUM mymaster} err
+ assert_match {*NOQUORUM*} $err
+
+ foreach_sentinel_id id {
+ assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-user $::user]
+ assert_equal {OK} [S $id SENTINEL CONFIG SET sentinel-pass $::password]
+ }
+
+ after 5000
+ assert_match {*OK*} [S 1 SENTINEL CKQUORUM mymaster]
+}
+
+test "(post-cleanup) Tear down ACL configuration" {
+ teardown_acl
+}
+
diff --git a/tests/sentinel/tests/includes/init-tests.tcl b/tests/sentinel/tests/includes/init-tests.tcl
index d6796fda6..b4626caed 100644
--- a/tests/sentinel/tests/includes/init-tests.tcl
+++ b/tests/sentinel/tests/includes/init-tests.tcl
@@ -1,6 +1,6 @@
# Initialization tests -- most units will start including this.
-test "(init) Restart killed instances" {
+proc restart_killed_instances {} {
foreach type {redis sentinel} {
foreach_${type}_id id {
if {[get_instance_attrib $type $id pid] == -1} {
@@ -12,6 +12,10 @@ test "(init) Restart killed instances" {
}
}
+test "(init) Restart killed instances" {
+ restart_killed_instances
+}
+
test "(init) Remove old master entry from sentinels" {
foreach_sentinel_id id {
catch {S $id SENTINEL REMOVE mymaster}