diff --git a/redis.conf b/redis.conf index 1b8a62b6025..de1d322834f 100644 --- a/redis.conf +++ b/redis.conf @@ -1684,6 +1684,10 @@ aof-timestamp-enabled no # # cluster-announce-hostname "" +# Clusters can configure their announced nodename using this config. The nodename can be a human-readable name +# which will be exposed in places like info and logging for debugging purposes. +# cluster-announce-nodename "" + # Clusters can advertise how clients should connect to them using either their IP address, # a user defined hostname, or by declaring they have no endpoint. Which endpoint is # shown as the preferred endpoint is set by using the cluster-preferred-endpoint-type diff --git a/src/cluster.c b/src/cluster.c index 2f37b241b9a..ebc9724e2b8 100644 --- a/src/cluster.c +++ b/src/cluster.c @@ -217,18 +217,7 @@ int clusterLoadConfig(char *filename) { clusterAddNode(n); } /* Format for the node address information: - * ip:port[@cport][,hostname] */ - - /* Hostname is an optional argument that defines the endpoint - * that can be reported to clients instead of IP. */ - char *hostname = strchr(argv[1], ','); - if (hostname) { - *hostname = '\0'; - hostname++; - n->hostname = sdscpy(n->hostname, hostname); - } else if (sdslen(n->hostname) != 0) { - sdsclear(n->hostname); - } + * ip:port[@cport][,hostname][-nodename] */ /* Address and port */ if ((p = strrchr(argv[1],':')) == NULL) { @@ -249,6 +238,27 @@ int clusterLoadConfig(char *filename) { * base port. */ n->cport = busp ? atoi(busp) : n->port + CLUSTER_PORT_INCR; + /* Hostname is an optional argument that defines the endpoint + * that can be reported to clients instead of IP. */ + char *hostname = strchr(p, ','); + if (hostname) { + *hostname = '\0'; + hostname++; + n->hostname = sdscpy(n->hostname, hostname); + } else if (sdslen(n->hostname) != 0) { + sdsclear(n->hostname); + } + /* Nodename is an optional argument */ + char *nodename = strchr(p, '-'); + if (nodename) { + *nodename = '\0'; + nodename++; + zfree(n->nodename); + n->nodename = sdscpy(n->nodename, nodename); + } else if (sdslen(n->nodename) != 0) { + sdsclear(n->nodename); + } + /* The plaintext port for client in a TLS cluster (n->pport) is not * stored in nodes.conf. It is received later over the bus protocol. */ @@ -582,12 +592,31 @@ static void updateAnnouncedHostname(clusterNode *node, char *new) { } } +/* Update the nodename for the specified node with the provided C string. */ +static void updateAnnouncedNodename(clusterNode *node, char *new) { + /* Previous and new nodename are the same, no need to update. */ + if (new && !strcmp(new, node->nodename)) { + return; + } + + if (new) { + node->nodename = sdscpy(node->nodename, new); + } else if (sdslen(node->nodename) != 0) { + sdsclear(node->nodename); + } +} + /* Update my hostname based on server configuration values */ void clusterUpdateMyselfHostname(void) { if (!myself) return; updateAnnouncedHostname(myself, server.cluster_announce_hostname); } +void clusterUpdateMyselfNodename(void) { + if (!myself) return; + updateAnnouncedNodename(myself, server.cluster_announce_nodename); +} + void clusterInit(void) { int saveconf = 0; @@ -682,6 +711,7 @@ void clusterInit(void) { clusterUpdateMyselfFlags(); clusterUpdateMyselfIp(); clusterUpdateMyselfHostname(); + clusterUpdateMyselfNodename(); } /* Reset a node performing a soft or hard reset: @@ -955,6 +985,7 @@ clusterNode *createClusterNode(char *nodename, int flags) { node->inbound_link = NULL; memset(node->ip,0,sizeof(node->ip)); node->hostname = sdsempty(); + node->nodename = sdsempty(); node->port = 0; node->cport = 0; node->pport = 0; @@ -1121,6 +1152,7 @@ void freeClusterNode(clusterNode *n) { serverAssert(dictDelete(server.cluster->nodes,nodename) == DICT_OK); sdsfree(nodename); sdsfree(n->hostname); + sdsfree(n->nodename); /* Release links and associated data structures. */ if (n->link) freeClusterLink(n->link); @@ -1186,7 +1218,9 @@ clusterNode *clusterLookupNode(const char *name) { de = dictFind(server.cluster->nodes,s); sdsfree(s); - if (de == NULL) return NULL; + if (de == NULL) { + return NULL; + } return dictGetVal(de); } @@ -1344,9 +1378,9 @@ void clusterHandleConfigEpochCollision(clusterNode *sender) { myself->configEpoch = server.cluster->currentEpoch; clusterSaveConfigOrDie(1); serverLog(LL_VERBOSE, - "WARNING: configEpoch collision with node %.40s." + "WARNING: configEpoch collision with node %.40s (%s)." " configEpoch set to %llu", - sender->name, + sender->name, sender->nodename, (unsigned long long) myself->configEpoch); } @@ -1462,7 +1496,7 @@ void markNodeAsFailingIfNeeded(clusterNode *node) { if (failures < needed_quorum) return; /* No weak agreement from masters. */ serverLog(LL_NOTICE, - "Marking node %.40s as failing (quorum reached).", node->name); + "Marking node %.40s (%s) as failing (quorum reached).", node->name, node->nodename); /* Mark the node as failing. */ node->flags &= ~CLUSTER_NODE_PFAIL; @@ -1490,8 +1524,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) { * node again. */ if (nodeIsSlave(node) || node->numslots == 0) { serverLog(LL_NOTICE, - "Clear FAIL state for node %.40s: %s is reachable again.", - node->name, + "Clear FAIL state for node %.40s (%s): %s is reachable again.", + node->name, node->nodename, nodeIsSlave(node) ? "replica" : "master without slots"); node->flags &= ~CLUSTER_NODE_FAIL; clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); @@ -1506,8 +1540,8 @@ void clearNodeFailureIfNeeded(clusterNode *node) { (server.cluster_node_timeout * CLUSTER_FAIL_UNDO_TIME_MULT)) { serverLog(LL_NOTICE, - "Clear FAIL state for node %.40s: is reachable again and nobody is serving its slots after some time.", - node->name); + "Clear FAIL state for node %.40s (%s): is reachable again and nobody is serving its slots after some time.", + node->name, node->nodename); node->flags &= ~CLUSTER_NODE_FAIL; clusterDoBeforeSleep(CLUSTER_TODO_UPDATE_STATE|CLUSTER_TODO_SAVE_CONFIG); } @@ -1627,15 +1661,17 @@ void clusterProcessGossipSection(clusterMsg *hdr, clusterLink *link) { if (flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_PFAIL)) { if (clusterNodeAddFailureReport(node,sender)) { serverLog(LL_VERBOSE, - "Node %.40s reported node %.40s as not reachable.", - sender->name, node->name); + "Node %.40s (%s) reported node %.40s (%s) as not reachable.", + sender->name, sender->nodename, + node->name, node->nodename); } markNodeAsFailingIfNeeded(node); } else { if (clusterNodeDelFailureReport(node,sender)) { serverLog(LL_VERBOSE, - "Node %.40s reported node %.40s is back online.", - sender->name, node->name); + "Node %.40s (%s) reported node %.40s (%s) is back online.", + sender->name, sender->nodename, + node->name, node->nodename); } } } @@ -1761,8 +1797,8 @@ int nodeUpdateAddressIfNeeded(clusterNode *node, clusterLink *link, node->cport = cport; if (node->link) freeClusterLink(node->link); node->flags &= ~CLUSTER_NODE_NOADDR; - serverLog(LL_WARNING,"Address updated for node %.40s, now %s:%d", - node->name, node->ip, node->port); + serverLog(LL_WARNING,"Address updated for node %.40s (%s), now %s:%d", + node->name, node->nodename, node->ip, node->port); /* Check if this is our master and we have to change the * replication target as well. */ @@ -1948,6 +1984,16 @@ int getHostnamePingExtSize() { return totlen; } +/* Returns the exact size needed to store the nodename. The returned value + * will be 8 byte padded. */ +int getNodenamePingExtSize() { + /* If nodename is not set, we don't send this extension */ + if (sdslen(myself->nodename) == 0) return 0; + + int totlen = sizeof(clusterMsgPingExt) + EIGHT_BYTE_ALIGN(sdslen(myself->nodename) + 1); + return totlen; +} + /* Write the hostname ping extension at the start of the cursor. This function * will update the cursor to point to the end of the written extension and * will return the amount of bytes written. */ @@ -1968,19 +2014,43 @@ int writeHostnamePingExt(clusterMsgPingExt **cursor) { return extension_size; } +/* Write the nodename ping extension at the start of the cursor. This function + * will update the cursor to point to the end of the written extension and + * will return the amount of bytes written. */ +int writeNodenamePingExt(clusterMsgPingExt **cursor) { + /* If nodename is not set, we don't send this extension */ + if (sdslen(myself->nodename) == 0) return 0; + + /* Add the nodename information at the extension cursor */ + clusterMsgPingExtNodename *ext = &(*cursor)->ext[0].nodename; + memcpy(ext->nodename, myself->nodename, sdslen(myself->nodename)); + uint32_t extension_size = getNodenamePingExtSize(); + + /* Move the write cursor */ + (*cursor)->type = CLUSTERMSG_EXT_TYPE_NODENAME; + (*cursor)->length = htonl(extension_size); + /* Make sure the string is NULL terminated by adding 1 */ + *cursor = (clusterMsgPingExt *) (ext->nodename + EIGHT_BYTE_ALIGN(sdslen(myself->nodename) + 1)); + return extension_size; +} + /* We previously validated the extensions, so this function just needs to * handle the extensions. */ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { clusterNode *sender = link->node ? link->node : clusterLookupNode(hdr->sender); char *ext_hostname = NULL; + char *ext_nodename = NULL; uint16_t extensions = ntohs(hdr->extensions); /* Loop through all the extensions and process them */ clusterMsgPingExt *ext = getInitialPingExt(hdr, ntohs(hdr->count)); while (extensions--) { - uint16_t type = ntohs(ext->type); + uint16_t type = ext->type; if (type == CLUSTERMSG_EXT_TYPE_HOSTNAME) { clusterMsgPingExtHostname *hostname_ext = (clusterMsgPingExtHostname *) &(ext->ext[0].hostname); ext_hostname = hostname_ext->hostname; + } else if (type == CLUSTERMSG_EXT_TYPE_NODENAME) { + clusterMsgPingExtNodename *nodename_ext = (clusterMsgPingExtNodename *) &(ext->ext[0].nodename); + ext_nodename = nodename_ext->nodename; } else { /* Unknown type, we will ignore it but log what happened. */ serverLog(LL_WARNING, "Received unknown extension type %d", type); @@ -1993,6 +2063,7 @@ void clusterProcessPingExtensions(clusterMsg *hdr, clusterLink *link) { * they don't have an announced hostname. Otherwise, we'll * set it now. */ updateAnnouncedHostname(sender, ext_hostname); + updateAnnouncedNodename(sender, ext_nodename); } static clusterNode *getNodeFromLinkAndMsg(clusterLink *link, clusterMsg *hdr) { @@ -2223,8 +2294,9 @@ int clusterProcessPacket(clusterLink *link) { * IP/port of the node with the new one. */ if (sender) { serverLog(LL_VERBOSE, - "Handshake: we already know node %.40s, " - "updating the address if needed.", sender->name); + "Handshake: we already know node %.40s (%s), " + "updating the address if needed.", sender->name, sender->nodename); + if (nodeUpdateAddressIfNeeded(sender,link,hdr)) { clusterDoBeforeSleep(CLUSTER_TODO_SAVE_CONFIG| @@ -2250,8 +2322,9 @@ int clusterProcessPacket(clusterLink *link) { /* If the reply has a non matching node ID we * disconnect this node and set it as not having an associated * address. */ - serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s added %d ms ago, having flags %d", + serverLog(LL_DEBUG,"PONG contains mismatching sender ID. About node %.40s (%s) added %d ms ago, having flags %d", link->node->name, + link->node->nodename, (int)(now-(link->node->ctime)), link->node->flags); link->node->flags |= CLUSTER_NODE_NOADDR; @@ -2398,8 +2471,9 @@ int clusterProcessPacket(clusterLink *link) { { serverLog(LL_VERBOSE, "Node %.40s has old slots configuration, sending " - "an UPDATE message about %.40s", - sender->name, server.cluster->slots[j]->name); + "an UPDATE message about %.40s (%s)", + sender->name, server.cluster->slots[j]->name, + sender->nodename); clusterSendUpdate(sender->link, server.cluster->slots[j]); @@ -2435,8 +2509,8 @@ int clusterProcessPacket(clusterLink *link) { !(failing->flags & (CLUSTER_NODE_FAIL|CLUSTER_NODE_MYSELF))) { serverLog(LL_NOTICE, - "FAIL message received from %.40s about %.40s", - hdr->sender, hdr->data.fail.about.nodename); + "FAIL message received from %.40s about %.40s (%s)", + hdr->sender, hdr->data.fail.about.nodename, failing->nodename); failing->flags |= CLUSTER_NODE_FAIL; failing->fail_time = now; failing->flags &= ~CLUSTER_NODE_PFAIL; @@ -2587,8 +2661,8 @@ void clusterLinkConnectHandler(connection *conn) { /* Check if connection succeeded */ if (connGetState(conn) != CONN_STATE_CONNECTED) { - serverLog(LL_VERBOSE, "Connection with Node %.40s at %s:%d failed: %s", - node->name, node->ip, node->cport, + serverLog(LL_VERBOSE, "Connection with Node %.40s (%s) at %s:%d failed: %s", + node->name, node->nodename, node->ip, node->cport, connGetLastError(conn)); freeClusterLink(link); return; @@ -2760,7 +2834,7 @@ void clusterBuildMessageHdr(clusterMsg *hdr, int type) { hdr->sig[3] = 'b'; hdr->type = htons(type); memcpy(hdr->sender,myself->name,CLUSTER_NAMELEN); - + /* If cluster-announce-ip option is enabled, force the receivers of our * packets to use the specified address for this node. Otherwise if the * first byte is zero, they'll do auto discovery. */ @@ -2894,7 +2968,7 @@ void clusterSendPing(clusterLink *link, int type) { * to put inside the packet. */ estlen = sizeof(clusterMsg) - sizeof(union clusterMsgData); estlen += (sizeof(clusterMsgDataGossip)*(wanted + pfail_wanted)); - estlen += sizeof(clusterMsgPingExt) + getHostnamePingExtSize(); + estlen += sizeof(clusterMsgPingExt) + getHostnamePingExtSize() + getNodenamePingExtSize(); /* Note: clusterBuildMessageHdr() expects the buffer to be always at least * sizeof(clusterMsg) or more. */ @@ -2975,6 +3049,12 @@ void clusterSendPing(clusterLink *link, int type) { extensions++; } + if (sdslen(myself->nodename) != 0) { + hdr->mflags[0] |= CLUSTERMSG_FLAG0_EXT_DATA; + totlen += writeNodenamePingExt(&cursor); + extensions++; + } + /* Compute the actual total length and send! */ totlen += sizeof(clusterMsg)-sizeof(union clusterMsgData); totlen += (sizeof(clusterMsgDataGossip)*gossipcount); @@ -3262,8 +3342,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { * request, if the request epoch was greater. */ if (requestCurrentEpoch < server.cluster->currentEpoch) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: reqEpoch (%llu) < curEpoch(%llu)", - node->name, + "Failover auth denied to %.40s (%s): reqEpoch (%llu) < curEpoch(%llu)", + node->name, node->nodename, (unsigned long long) requestCurrentEpoch, (unsigned long long) server.cluster->currentEpoch); return; @@ -3272,8 +3352,8 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { /* I already voted for this epoch? Return ASAP. */ if (server.cluster->lastVoteEpoch == server.cluster->currentEpoch) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: already voted for epoch %llu", - node->name, + "Failover auth denied to %.40s (%s): already voted for epoch %llu", + node->name, node->nodename, (unsigned long long) server.cluster->currentEpoch); return; } @@ -3286,16 +3366,16 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { { if (nodeIsMaster(node)) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: it is a master node", - node->name); + "Failover auth denied to %.40s (%s): it is a master node", + node->name, node->nodename); } else if (master == NULL) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: I don't know its master", - node->name); + "Failover auth denied to %.40s (%s): I don't know its master", + node->name, node->nodename); } else if (!nodeFailed(master)) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: its master is up", - node->name); + "Failover auth denied to %.40s (%s): its master is up", + node->name, node->nodename); } return; } @@ -3306,9 +3386,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { if (mstime() - node->slaveof->voted_time < server.cluster_node_timeout * 2) { serverLog(LL_WARNING, - "Failover auth denied to %.40s: " + "Failover auth denied to %.40s (%s): " "can't vote about this master before %lld milliseconds", - node->name, + node->name, node->nodename, (long long) ((server.cluster_node_timeout*2)- (mstime() - node->slaveof->voted_time))); return; @@ -3328,9 +3408,9 @@ void clusterSendFailoverAuthIfNeeded(clusterNode *node, clusterMsg *request) { * is served by a master with a greater configEpoch than the one claimed * by the slave requesting our vote. Refuse to vote for this slave. */ serverLog(LL_WARNING, - "Failover auth denied to %.40s: " + "Failover auth denied to %.40s (%s): " "slot %d epoch (%llu) > reqEpoch (%llu)", - node->name, j, + node->name, node->nodename, j, (unsigned long long) server.cluster->slots[j]->configEpoch, (unsigned long long) requestConfigEpoch); return; @@ -3954,6 +4034,7 @@ void clusterCron(void) { iteration++; /* Number of times this function was called so far. */ clusterUpdateMyselfHostname(); + clusterUpdateMyselfNodename(); /* The handshake timeout is the time after which a handshake node that was * not turned into a normal node is removed from the nodes. Usually it is @@ -4107,8 +4188,8 @@ void clusterCron(void) { /* Timeout reached. Set the node as possibly failing if it is * not already in this state. */ if (!(node->flags & (CLUSTER_NODE_PFAIL|CLUSTER_NODE_FAIL))) { - serverLog(LL_DEBUG,"*** NODE %.40s possibly failing", - node->name); + serverLog(LL_DEBUG,"*** NODE %.40s (%s) possibly failing", + node->name, node->nodename); node->flags |= CLUSTER_NODE_PFAIL; update_state = 1; } @@ -4595,18 +4676,17 @@ sds clusterGenNodeDescription(clusterNode *node, int use_pport) { /* Node coordinates */ ci = sdscatlen(sdsempty(),node->name,CLUSTER_NAMELEN); + ci = sdscatfmt(ci," %s:%i@%i", + node->ip, + port, + node->cport); if (sdslen(node->hostname) != 0) { - ci = sdscatfmt(ci," %s:%i@%i,%s ", - node->ip, - port, - node->cport, - node->hostname); - } else { - ci = sdscatfmt(ci," %s:%i@%i ", - node->ip, - port, - node->cport); + ci = sdscatfmt(ci,",%s", node->hostname); + } + if (sdslen(node->nodename) != 0) { + ci = sdscatfmt(ci,"-%s", node->nodename); } + ci = sdscatlen(ci," ",1); /* Flags */ ci = representClusterNodeFlags(ci, node->flags); diff --git a/src/cluster.h b/src/cluster.h index a944056eb96..1eb32e76ca3 100644 --- a/src/cluster.h +++ b/src/cluster.h @@ -136,6 +136,7 @@ typedef struct clusterNode { long long repl_offset; /* Last known repl offset for this node. */ char ip[NET_IP_STR_LEN]; /* Latest known IP address of this node */ sds hostname; /* The known hostname for this node */ + sds nodename; /* The known human readable nodename for this node */ int port; /* Latest known clients port (TLS or plain). */ int pport; /* Latest known clients plaintext port. Only used if the main clients port is for TLS. */ @@ -251,6 +252,7 @@ typedef struct { * consistent manner. */ typedef enum { CLUSTERMSG_EXT_TYPE_HOSTNAME, + CLUSTERMSG_EXT_TYPE_NODENAME, } clusterMsgPingtypes; /* Helper function for making sure extensions are eight byte aligned. */ @@ -260,12 +262,17 @@ typedef struct { char hostname[1]; /* The announced hostname, ends with \0. */ } clusterMsgPingExtHostname; +typedef struct { + char nodename[1]; /* The announced nodename, ends with \0. */ +} clusterMsgPingExtNodename; + typedef struct { uint32_t length; /* Total length of this extension message (including this header) */ uint16_t type; /* Type of this extension message (see clusterMsgPingExtTypes) */ uint16_t unused; /* 16 bits of padding to make this structure 8 byte aligned. */ union { clusterMsgPingExtHostname hostname; + clusterMsgPingExtNodename nodename; } ext[]; /* Actual extension information, formatted so that the data is 8 * byte aligned, regardless of its content. */ } clusterMsgPingExt; @@ -396,5 +403,6 @@ void clusterUpdateMyselfIp(void); void slotToChannelAdd(sds channel); void slotToChannelDel(sds channel); void clusterUpdateMyselfHostname(void); +void clusterUpdateMyselfNodename(void); #endif /* __CLUSTER_H */ diff --git a/src/config.c b/src/config.c index e36315743af..6034321b95f 100644 --- a/src/config.c +++ b/src/config.c @@ -2409,6 +2409,12 @@ int updateClusterHostname(const char **err) { return 1; } +int updateClusterNodename(const char **err) { + UNUSED(err); + clusterUpdateMyselfNodename(); + return 1; +} + #ifdef USE_OPENSSL static int applyTlsCfg(const char **err) { UNUSED(err); @@ -2828,6 +2834,7 @@ standardConfig static_configs[] = { createStringConfig("cluster-announce-ip", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_ip, NULL, NULL, updateClusterIp), createStringConfig("cluster-config-file", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.cluster_configfile, "nodes.conf", NULL, NULL), createStringConfig("cluster-announce-hostname", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_hostname, NULL, isValidAnnouncedHostname, updateClusterHostname), + createStringConfig("cluster-announce-nodename", NULL, MODIFIABLE_CONFIG, EMPTY_STRING_IS_NULL, server.cluster_announce_nodename, NULL, isValidAnnouncedHostname, updateClusterNodename), createStringConfig("syslog-ident", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.syslog_ident, "redis", NULL, NULL), createStringConfig("dbfilename", NULL, MODIFIABLE_CONFIG | PROTECTED_CONFIG, ALLOW_EMPTY_STRING, server.rdb_filename, "dump.rdb", isValidDBfilename, NULL), createStringConfig("appendfilename", NULL, IMMUTABLE_CONFIG, ALLOW_EMPTY_STRING, server.aof_filename, "appendonly.aof", isValidAOFfilename, NULL), diff --git a/src/server.h b/src/server.h index 5f5417de169..f3b7d545e04 100644 --- a/src/server.h +++ b/src/server.h @@ -1848,7 +1848,8 @@ struct redisServer { int cluster_slave_no_failover; /* Prevent slave from starting a failover if the master is in failure state. */ char *cluster_announce_ip; /* IP address to announce on cluster bus. */ - char *cluster_announce_hostname; /* hostname to announce on cluster bus. */ + char *cluster_announce_hostname; /* IP address to announce on cluster bus. */ + char *cluster_announce_nodename; /* Human readable name assigned to a node. */ int cluster_preferred_endpoint_type; /* Use the announced hostname when available. */ int cluster_announce_port; /* base port to announce on cluster bus. */ int cluster_announce_tls_port; /* TLS port to announce on cluster bus. */ diff --git a/tests/cluster/cluster.tcl b/tests/cluster/cluster.tcl index 9c669e12854..a2b0c287588 100644 --- a/tests/cluster/cluster.tcl +++ b/tests/cluster/cluster.tcl @@ -218,6 +218,17 @@ proc are_hostnames_propagated {match_string} { return 1 } +# Check if cluster's view of nodename is consistent +proc are_nodenames_propagated {match_string} { + for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} { + set cfg [R $j cluster nodes] + if {! [string match $match_string $cfg]} { + return 0 + } + return 1 + } +} + # Returns a parsed CLUSTER LINKS output of the instance identified # by the given `id` as a list of dictionaries, with each dictionary # corresponds to a link. diff --git a/tests/cluster/tests/27-endpoints.tcl b/tests/cluster/tests/27-endpoints.tcl index 32e3e794db9..c5a8a261c8f 100644 --- a/tests/cluster/tests/27-endpoints.tcl +++ b/tests/cluster/tests/27-endpoints.tcl @@ -40,6 +40,21 @@ test "Set cluster hostnames and verify they are propagated" { wait_for_cluster_propagation } +test "Set cluster nodenames and verify they are propagated" { + for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} { + R $j config set cluster-announce-nodename "node-$j.com" + } + + wait_for_condition 50 100 { + [are_nodenames_propagated "*node-*.com*"] eq 1 + } else { + fail "cluster nodenames were not propagated" + } + + # Now that everything is propagated, assert everyone agrees + wait_for_cluster_propagation +} + test "Update hostnames and make sure they are all eventually propagated" { for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} { R $j config set cluster-announce-hostname "host-updated-$j.com" @@ -55,6 +70,21 @@ test "Update hostnames and make sure they are all eventually propagated" { wait_for_cluster_propagation } +test "Update nodenames and make sure they are all eventually propagated" { + for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} { + R $j config set cluster-announce-nodename "node-updated-$j.com" + } + + wait_for_condition 50 100 { + [are_nodenames_propagated "*node-updated-*.com*"] eq 1 + } else { + fail "cluster nodenames were not propagated" + } + + # Now that everything is propagated, assert everyone agrees + wait_for_cluster_propagation +} + test "Remove hostnames and make sure they are all eventually propagated" { for {set j 0} {$j < $::cluster_master_nodes + $::cluster_replica_nodes} {incr j} { R $j config set cluster-announce-hostname "" @@ -216,4 +246,11 @@ test "Test hostname validation" { # Note this isn't a valid hostname, but it passes our internal validation R 0 config set cluster-announce-hostname "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-." +} + +test "Test nodename validation" { + catch {R 0 config set cluster-announce-nodename [string repeat x 256]} err + assert_match "*must be less than 256 characters*" $err + catch {R 0 config set cluster-announce-nodename "?.com"} err + assert_match "*may only contain alphanumeric characters, hyphens or dots*" $err } \ No newline at end of file