src/sentinel.c C 5,529 lines View on github.com → Search inside
File is large — showing lines 1–2,000 of 5,529.
1/* Redis Sentinel implementation2 *3 * Copyright (c) 2009-Present, Redis Ltd.4 * All rights reserved.5 *6 * Licensed under your choice of (a) the Redis Source Available License 2.07 * (RSALv2); or (b) the Server Side Public License v1 (SSPLv1); or (c) the8 * GNU Affero General Public License v3 (AGPLv3).9 */1011#include "server.h"12#include "hiredis.h"13#if USE_OPENSSL == 1 /* BUILD_YES */14#include "openssl/ssl.h"15#include "hiredis_ssl.h"16#endif17#include "async.h"1819#include <ctype.h>20#include <arpa/inet.h>21#include <sys/socket.h>22#include <sys/wait.h>23#include <fcntl.h>2425extern char **environ;2627#if USE_OPENSSL == 1 /* BUILD_YES */28extern SSL_CTX *redis_tls_ctx;29extern SSL_CTX *redis_tls_client_ctx;30#endif3132#define REDIS_SENTINEL_PORT 263793334/* ======================== Sentinel global state =========================== */3536/* Address object, used to describe an ip:port pair. */37typedef struct sentinelAddr {38    char *hostname;         /* Hostname OR address, as specified */39    char *ip;               /* Always a resolved address */40    int port;41} sentinelAddr;4243/* A Sentinel Redis Instance object is monitoring. */44#define SRI_MASTER  (1<<0)45#define SRI_SLAVE   (1<<1)46#define SRI_SENTINEL (1<<2)47#define SRI_S_DOWN (1<<3)   /* Subjectively down (no quorum). */48#define SRI_O_DOWN (1<<4)   /* Objectively down (confirmed by others). */49#define SRI_MASTER_DOWN (1<<5) /* A Sentinel with this flag set thinks that50                                   its master is down. */51#define SRI_FAILOVER_IN_PROGRESS (1<<6) /* Failover is in progress for52                                           this master. */53#define SRI_PROMOTED (1<<7)            /* Slave selected for promotion. */54#define SRI_RECONF_SENT (1<<8)     /* SLAVEOF <newmaster> sent. */55#define SRI_RECONF_INPROG (1<<9)   /* Slave synchronization in progress. */56#define SRI_RECONF_DONE (1<<10)     /* Slave synchronized with new master. */57#define SRI_FORCE_FAILOVER (1<<11)  /* Force failover with master up. */58#define SRI_SCRIPT_KILL_SENT (1<<12) /* SCRIPT KILL already sent on -BUSY */59#define SRI_MASTER_REBOOT  (1<<13)   /* Master was detected as rebooting */60/* Note: when adding new flags, please check the flags section in addReplySentinelRedisInstance. */6162/* Note: times are in milliseconds. */63#define SENTINEL_PING_PERIOD 10006465static mstime_t sentinel_info_period = 10000;66static mstime_t sentinel_ping_period = SENTINEL_PING_PERIOD;67static mstime_t sentinel_ask_period = 1000;68static mstime_t sentinel_publish_period = 2000;69static mstime_t sentinel_default_down_after = 30000;70static mstime_t sentinel_tilt_trigger = 2000;71static mstime_t sentinel_tilt_period = SENTINEL_PING_PERIOD * 30;72static mstime_t sentinel_slave_reconf_timeout = 10000;73static mstime_t sentinel_min_link_reconnect_period = 15000;74static mstime_t sentinel_election_timeout = 10000;75static mstime_t sentinel_script_max_runtime = 60000;  /* 60 seconds max exec time. */76static mstime_t sentinel_script_retry_delay = 30000;  /* 30 seconds between retries. */77static mstime_t sentinel_default_failover_timeout = 60*3*1000;7879#define SENTINEL_HELLO_CHANNEL "__sentinel__:hello"80#define SENTINEL_DEFAULT_SLAVE_PRIORITY 10081#define SENTINEL_DEFAULT_PARALLEL_SYNCS 182#define SENTINEL_MAX_PENDING_COMMANDS 1008384#define SENTINEL_MAX_DESYNC 100085#define SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG 186#define SENTINEL_DEFAULT_RESOLVE_HOSTNAMES 087#define SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES 08889/* Failover machine different states. */90#define SENTINEL_FAILOVER_STATE_NONE 0  /* No failover in progress. */91#define SENTINEL_FAILOVER_STATE_WAIT_START 1  /* Wait for failover_start_time*/92#define SENTINEL_FAILOVER_STATE_SELECT_SLAVE 2 /* Select slave to promote */93#define SENTINEL_FAILOVER_STATE_SEND_SLAVEOF_NOONE 3 /* Slave -> Master */94#define SENTINEL_FAILOVER_STATE_WAIT_PROMOTION 4 /* Wait slave to change role */95#define SENTINEL_FAILOVER_STATE_RECONF_SLAVES 5 /* SLAVEOF newmaster */96#define SENTINEL_FAILOVER_STATE_UPDATE_CONFIG 6 /* Monitor promoted slave. */9798#define SENTINEL_MASTER_LINK_STATUS_UP 099#define SENTINEL_MASTER_LINK_STATUS_DOWN 1100101/* Generic flags that can be used with different functions.102 * They use higher bits to avoid colliding with the function specific103 * flags. */104#define SENTINEL_NO_FLAGS 0105#define SENTINEL_GENERATE_EVENT (1<<16)106#define SENTINEL_LEADER (1<<17)107#define SENTINEL_OBSERVER (1<<18)108109/* Script execution flags and limits. */110#define SENTINEL_SCRIPT_NONE 0111#define SENTINEL_SCRIPT_RUNNING 1112#define SENTINEL_SCRIPT_MAX_QUEUE 256113#define SENTINEL_SCRIPT_MAX_RUNNING 16114#define SENTINEL_SCRIPT_MAX_RETRY 10115116/* SENTINEL SIMULATE-FAILURE command flags. */117#define SENTINEL_SIMFAILURE_NONE 0118#define SENTINEL_SIMFAILURE_CRASH_AFTER_ELECTION (1<<0)119#define SENTINEL_SIMFAILURE_CRASH_AFTER_PROMOTION (1<<1)120121/* The link to a sentinelRedisInstance. When we have the same set of Sentinels122 * monitoring many masters, we have different instances representing the123 * same Sentinels, one per master, and we need to share the hiredis connections124 * among them. Otherwise if 5 Sentinels are monitoring 100 masters we create125 * 500 outgoing connections instead of 5.126 *127 * So this structure represents a reference counted link in terms of the two128 * hiredis connections for commands and Pub/Sub, and the fields needed for129 * failure detection, since the ping/pong time are now local to the link: if130 * the link is available, the instance is available. This way we don't just131 * have 5 connections instead of 500, we also send 5 pings instead of 500.132 *133 * Links are shared only for Sentinels: master and slave instances have134 * a link with refcount = 1, always. */135typedef struct instanceLink {136    int refcount;          /* Number of sentinelRedisInstance owners. */137    int disconnected;      /* Non-zero if we need to reconnect cc or pc. */138    int pending_commands;  /* Number of commands sent waiting for a reply. */139    redisAsyncContext *cc; /* Hiredis context for commands. */140    redisAsyncContext *pc; /* Hiredis context for Pub / Sub. */141    mstime_t cc_conn_time; /* cc connection time. */142    mstime_t pc_conn_time; /* pc connection time. */143    mstime_t pc_last_activity; /* Last time we received any message. */144    mstime_t last_avail_time; /* Last time the instance replied to ping with145                                 a reply we consider valid. */146    mstime_t act_ping_time;   /* Time at which the last pending ping (no pong147                                 received after it) was sent. This field is148                                 set to 0 when a pong is received, and set again149                                 to the current time if the value is 0 and a new150                                 ping is sent. */151    mstime_t last_ping_time;  /* Time at which we sent the last ping. This is152                                 only used to avoid sending too many pings153                                 during failure. Idle time is computed using154                                 the act_ping_time field. */155    mstime_t last_pong_time;  /* Last time the instance replied to ping,156                                 whatever the reply was. That's used to check157                                 if the link is idle and must be reconnected. */158    mstime_t last_reconn_time;  /* Last reconnection attempt performed when159                                   the link was down. */160} instanceLink;161162typedef struct sentinelRedisInstance {163    int flags;      /* See SRI_... defines */164    char *name;     /* Master name from the point of view of this sentinel. */165    char *runid;    /* Run ID of this instance, or unique ID if is a Sentinel.*/166    uint64_t config_epoch;  /* Configuration epoch. */167    sentinelAddr *addr; /* Master host. */168    instanceLink *link; /* Link to the instance, may be shared for Sentinels. */169    mstime_t last_pub_time;   /* Last time we sent hello via Pub/Sub. */170    mstime_t last_hello_time; /* Only used if SRI_SENTINEL is set. Last time171                                 we received a hello from this Sentinel172                                 via Pub/Sub. */173    mstime_t last_master_down_reply_time; /* Time of last reply to174                                             SENTINEL is-master-down command. */175    mstime_t s_down_since_time; /* Subjectively down since time. */176    mstime_t o_down_since_time; /* Objectively down since time. */177    mstime_t down_after_period; /* Consider it down after that period. */178    mstime_t master_reboot_down_after_period; /* Consider master down after that period. */179    mstime_t master_reboot_since_time; /* master reboot time since time. */180    mstime_t info_refresh;  /* Time at which we received INFO output from it. */181    dict *renamed_commands;     /* Commands renamed in this instance:182                                   Sentinel will use the alternative commands183                                   mapped on this table to send things like184                                   SLAVEOF, CONFIG, INFO, ... */185186    /* Role and the first time we observed it.187     * This is useful in order to delay replacing what the instance reports188     * with our own configuration. We need to always wait some time in order189     * to give a chance to the leader to report the new configuration before190     * we do silly things. */191    int role_reported;192    mstime_t role_reported_time;193    mstime_t slave_conf_change_time; /* Last time slave master addr changed. */194195    /* Master specific. */196    dict *sentinels;    /* Other sentinels monitoring the same master. */197    dict *slaves;       /* Slaves for this master instance. */198    unsigned int quorum;/* Number of sentinels that need to agree on failure. */199    int parallel_syncs; /* How many slaves to reconfigure at same time. */200    char *auth_pass;    /* Password to use for AUTH against master & replica. */201    char *auth_user;    /* Username for ACLs AUTH against master & replica. */202203    /* Slave specific. */204    mstime_t master_link_down_time; /* Slave replication link down time. */205    int slave_priority; /* Slave priority according to its INFO output. */206    int replica_announced; /* Replica announcing according to its INFO output. */207    mstime_t slave_reconf_sent_time; /* Time at which we sent SLAVE OF <new> */208    struct sentinelRedisInstance *master; /* Master instance if it's slave. */209    char *slave_master_host;    /* Master host as reported by INFO */210    int slave_master_port;      /* Master port as reported by INFO */211    int slave_master_link_status; /* Master link status as reported by INFO */212    unsigned long long slave_repl_offset; /* Slave replication offset. */213    /* Failover */214    char *leader;       /* If this is a master instance, this is the runid of215                           the Sentinel that should perform the failover. If216                           this is a Sentinel, this is the runid of the Sentinel217                           that this Sentinel voted as leader. */218    uint64_t leader_epoch; /* Epoch of the 'leader' field. */219    uint64_t failover_epoch; /* Epoch of the currently started failover. */220    int failover_state; /* See SENTINEL_FAILOVER_STATE_* defines. */221    mstime_t failover_state_change_time;222    mstime_t failover_start_time;   /* Last failover attempt start time. */223    mstime_t failover_timeout;      /* Max time to refresh failover state. */224    mstime_t failover_delay_logged; /* For what failover_start_time value we225                                       logged the failover delay. */226    struct sentinelRedisInstance *promoted_slave; /* Promoted slave instance. */227    /* Scripts executed to notify admin or reconfigure clients: when they228     * are set to NULL no script is executed. */229    char *notification_script;230    char *client_reconfig_script;231    sds info; /* cached INFO output */232} sentinelRedisInstance;233234/* Main state. */235struct sentinelState {236    char myid[CONFIG_RUN_ID_SIZE+1]; /* This sentinel ID. */237    uint64_t current_epoch;         /* Current epoch. */238    dict *masters;      /* Dictionary of master sentinelRedisInstances.239                           Key is the instance name, value is the240                           sentinelRedisInstance structure pointer. */241    int tilt;           /* Are we in TILT mode? */242    int total_tilt;   /* Number of tilt. */243    int running_scripts;    /* Number of scripts in execution right now. */244    mstime_t tilt_start_time;       /* When TITL started. */245    mstime_t previous_time;         /* Last time we ran the time handler. */246    list *scripts_queue;            /* Queue of user scripts to execute. */247    char *announce_ip;  /* IP addr that is gossiped to other sentinels if248                           not NULL. */249    int announce_port;  /* Port that is gossiped to other sentinels if250                           non zero. */251    unsigned long simfailure_flags; /* Failures simulation. */252    int deny_scripts_reconfig; /* Allow SENTINEL SET ... to change script253                                  paths at runtime? */254    char *sentinel_auth_pass;    /* Password to use for AUTH against other sentinel */255    char *sentinel_auth_user;    /* Username for ACLs AUTH against other sentinel. */256    int resolve_hostnames;       /* Support use of hostnames, assuming DNS is well configured. */257    int announce_hostnames;      /* Announce hostnames instead of IPs when we have them. */258} sentinel;259260/* A script execution job. */261typedef struct sentinelScriptJob {262    int flags;              /* Script job flags: SENTINEL_SCRIPT_* */263    int retry_num;          /* Number of times we tried to execute it. */264    char **argv;            /* Arguments to call the script. */265    mstime_t start_time;    /* Script execution time if the script is running,266                               otherwise 0 if we are allowed to retry the267                               execution at any time. If the script is not268                               running and it's not 0, it means: do not run269                               before the specified time. */270    pid_t pid;              /* Script execution pid. */271} sentinelScriptJob;272273/* ======================= hiredis ae.c adapters =============================274 * Note: this implementation is taken from hiredis/adapters/ae.h, however275 * we have our modified copy for Sentinel in order to use our allocator276 * and to have full control over how the adapter works. */277278typedef struct redisAeEvents {279    redisAsyncContext *context;280    aeEventLoop *loop;281    int fd;282    int reading, writing;283} redisAeEvents;284285static void redisAeReadEvent(aeEventLoop *el, int fd, void *privdata, int mask) {286    ((void)el); ((void)fd); ((void)mask);287288    redisAeEvents *e = (redisAeEvents*)privdata;289    redisAsyncHandleRead(e->context);290}291292static void redisAeWriteEvent(aeEventLoop *el, int fd, void *privdata, int mask) {293    ((void)el); ((void)fd); ((void)mask);294295    redisAeEvents *e = (redisAeEvents*)privdata;296    redisAsyncHandleWrite(e->context);297}298299static void redisAeAddRead(void *privdata) {300    redisAeEvents *e = (redisAeEvents*)privdata;301    aeEventLoop *loop = e->loop;302    if (!e->reading) {303        e->reading = 1;304        aeCreateFileEvent(loop,e->fd,AE_READABLE,redisAeReadEvent,e);305    }306}307308static void redisAeDelRead(void *privdata) {309    redisAeEvents *e = (redisAeEvents*)privdata;310    aeEventLoop *loop = e->loop;311    if (e->reading) {312        e->reading = 0;313        aeDeleteFileEvent(loop,e->fd,AE_READABLE);314    }315}316317static void redisAeAddWrite(void *privdata) {318    redisAeEvents *e = (redisAeEvents*)privdata;319    aeEventLoop *loop = e->loop;320    if (!e->writing) {321        e->writing = 1;322        aeCreateFileEvent(loop,e->fd,AE_WRITABLE,redisAeWriteEvent,e);323    }324}325326static void redisAeDelWrite(void *privdata) {327    redisAeEvents *e = (redisAeEvents*)privdata;328    aeEventLoop *loop = e->loop;329    if (e->writing) {330        e->writing = 0;331        aeDeleteFileEvent(loop,e->fd,AE_WRITABLE);332    }333}334335static void redisAeCleanup(void *privdata) {336    redisAeEvents *e = (redisAeEvents*)privdata;337    redisAeDelRead(privdata);338    redisAeDelWrite(privdata);339    zfree(e);340}341342static int redisAeAttach(aeEventLoop *loop, redisAsyncContext *ac) {343    redisContext *c = &(ac->c);344    redisAeEvents *e;345346    /* Nothing should be attached when something is already attached */347    if (ac->ev.data != NULL)348        return C_ERR;349350    /* Create container for context and r/w events */351    e = (redisAeEvents*)zmalloc(sizeof(*e));352    e->context = ac;353    e->loop = loop;354    e->fd = c->fd;355    e->reading = e->writing = 0;356357    /* Register functions to start/stop listening for events */358    ac->ev.addRead = redisAeAddRead;359    ac->ev.delRead = redisAeDelRead;360    ac->ev.addWrite = redisAeAddWrite;361    ac->ev.delWrite = redisAeDelWrite;362    ac->ev.cleanup = redisAeCleanup;363    ac->ev.data = e;364365    return C_OK;366}367368/* ============================= Prototypes ================================= */369370void sentinelLinkEstablishedCallback(const redisAsyncContext *c, int status);371void sentinelDisconnectCallback(const redisAsyncContext *c, int status);372void sentinelReceiveHelloMessages(redisAsyncContext *c, void *reply, void *privdata);373sentinelRedisInstance *sentinelGetMasterByName(char *name);374char *sentinelGetSubjectiveLeader(sentinelRedisInstance *master);375char *sentinelGetObjectiveLeader(sentinelRedisInstance *master);376void instanceLinkConnectionError(const redisAsyncContext *c);377const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri);378void sentinelAbortFailover(sentinelRedisInstance *ri);379void sentinelEvent(int level, char *type, sentinelRedisInstance *ri, const char *fmt, ...);380sentinelRedisInstance *sentinelSelectSlave(sentinelRedisInstance *master);381void sentinelScheduleScriptExecution(char *path, ...);382void sentinelStartFailover(sentinelRedisInstance *master);383void sentinelDiscardReplyCallback(redisAsyncContext *c, void *reply, void *privdata);384int sentinelSendSlaveOf(sentinelRedisInstance *ri, const sentinelAddr *addr);385char *sentinelVoteLeader(sentinelRedisInstance *master, uint64_t req_epoch, char *req_runid, uint64_t *leader_epoch);386int sentinelFlushConfig(void);387void sentinelGenerateInitialMonitorEvents(void);388int sentinelSendPing(sentinelRedisInstance *ri);389int sentinelForceHelloUpdateForMaster(sentinelRedisInstance *master);390sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, char *ip, int port, char *runid);391void sentinelSimFailureCrash(void);392393/* ========================= Dictionary types =============================== */394395void releaseSentinelRedisInstance(sentinelRedisInstance *ri);396397void dictInstancesValDestructor (dict *d, void *obj) {398    UNUSED(d);399    releaseSentinelRedisInstance(obj);400}401402/* Instance name (sds) -> instance (sentinelRedisInstance pointer)403 *404 * also used for: sentinelRedisInstance->sentinels dictionary that maps405 * sentinels ip:port to last seen time in Pub/Sub hello message. */406dictType instancesDictType = {407    dictSdsHash,               /* hash function */408    NULL,                      /* key dup */409    NULL,                      /* val dup */410    dictSdsKeyCompare,         /* key compare */411    NULL,                      /* key destructor */412    dictInstancesValDestructor,/* val destructor */413    NULL                       /* allow to expand */414};415416/* Instance runid (sds) -> votes (long casted to void*)417 *418 * This is useful into sentinelGetObjectiveLeader() function in order to419 * count the votes and understand who is the leader. */420dictType leaderVotesDictType = {421    dictSdsHash,               /* hash function */422    NULL,                      /* key dup */423    NULL,                      /* val dup */424    dictSdsKeyCompare,         /* key compare */425    NULL,                      /* key destructor */426    NULL,                      /* val destructor */427    NULL                       /* allow to expand */428};429430/* Instance renamed commands table. */431dictType renamedCommandsDictType = {432    dictSdsCaseHash,           /* hash function */433    NULL,                      /* key dup */434    NULL,                      /* val dup */435    dictSdsKeyCaseCompare,     /* key compare */436    dictSdsDestructor,         /* key destructor */437    dictSdsDestructor,         /* val destructor */438    NULL                       /* allow to expand */439};440441/* =========================== Initialization =============================== */442443void sentinelSetCommand(client *c);444void sentinelConfigGetCommand(client *c);445void sentinelConfigSetCommand(client *c);446447/* this array is used for sentinel config lookup, which need to be loaded448 * before monitoring masters config to avoid dependency issues */449const char *preMonitorCfgName[] = { 450    "announce-ip",451    "announce-port",452    "deny-scripts-reconfig",453    "sentinel-user",454    "sentinel-pass",455    "current-epoch",456    "myid",457    "resolve-hostnames",458    "announce-hostnames"459};460461/* Returns 1 if the string contains control characters (0x00-0x1F or 0x7F),462 * which must be rejected to prevent config injection via newlines/etc. */463int sentinelStringContainsControlChars(sds s) {464    for (size_t i = 0; i < sdslen(s); i++) {465        unsigned char c = (unsigned char)s[i];466        if (c < 0x20 || c == 0x7F) return 1;467    }468    return 0;469}470471/* Append an sds value to dest, quoting it with sdscatrepr only if the value472 * contains characters that need escaping (spaces, quotes, control chars, etc.).473 * Simple values are appended as-is, preserving the traditional config format. */474static sds sentinelSdscatConfigArg(sds dest, sds value) {475    if (sdsneedsrepr(value))476        return sdscatrepr(dest, value, sdslen(value));477    return sdscatsds(dest, value);478}479480/* This function overwrites a few normal Redis config default with Sentinel481 * specific defaults. */482void initSentinelConfig(void) {483    server.port = REDIS_SENTINEL_PORT;484    server.protected_mode = 0; /* Sentinel must be exposed. */485}486487void freeSentinelLoadQueueEntry(void *item);488489/* Perform the Sentinel mode initialization. */490void initSentinel(void) {491    /* Initialize various data structures. */492    sentinel.current_epoch = 0;493    sentinel.masters = dictCreate(&instancesDictType);494    sentinel.tilt = 0;495    sentinel.tilt_start_time = 0;496    sentinel.total_tilt = 0;497    sentinel.previous_time = mstime();498    sentinel.running_scripts = 0;499    sentinel.scripts_queue = listCreate();500    sentinel.announce_ip = NULL;501    sentinel.announce_port = 0;502    sentinel.simfailure_flags = SENTINEL_SIMFAILURE_NONE;503    sentinel.deny_scripts_reconfig = SENTINEL_DEFAULT_DENY_SCRIPTS_RECONFIG;504    sentinel.sentinel_auth_pass = NULL;505    sentinel.sentinel_auth_user = NULL;506    sentinel.resolve_hostnames = SENTINEL_DEFAULT_RESOLVE_HOSTNAMES;507    sentinel.announce_hostnames = SENTINEL_DEFAULT_ANNOUNCE_HOSTNAMES;508    memset(sentinel.myid,0,sizeof(sentinel.myid));509    server.sentinel_config = NULL;510}511512/* This function is for checking whether sentinel config file has been set,513 * also checking whether we have write permissions. */514void sentinelCheckConfigFile(void) {515    if (server.configfile == NULL) {516        serverLog(LL_WARNING,517            "Sentinel needs config file on disk to save state. Exiting...");518        exit(1);519    } else if (access(server.configfile,W_OK) == -1) {520        serverLog(LL_WARNING,521            "Sentinel config file %s is not writable: %s. Exiting...",522            server.configfile,strerror(errno));523        exit(1);524    }525}526527/* This function gets called when the server is in Sentinel mode, started,528 * loaded the configuration, and is ready for normal operations. */529void sentinelIsRunning(void) {530    int j;531532    /* If this Sentinel has yet no ID set in the configuration file, we533     * pick a random one and persist the config on disk. From now on this534     * will be this Sentinel ID across restarts. */535    for (j = 0; j < CONFIG_RUN_ID_SIZE; j++)536        if (sentinel.myid[j] != 0) break;537538    if (j == CONFIG_RUN_ID_SIZE) {539        /* Pick ID and persist the config. */540        getRandomHexChars(sentinel.myid,CONFIG_RUN_ID_SIZE);541        sentinelFlushConfig();542    }543544    /* Log its ID to make debugging of issues simpler. */545    serverLog(LL_NOTICE,"Sentinel ID is %s", sentinel.myid);546547    /* We want to generate a +monitor event for every configured master548     * at startup. */549    sentinelGenerateInitialMonitorEvents();550}551552/* ============================== sentinelAddr ============================== */553554/* Create a sentinelAddr object and return it on success.555 * On error NULL is returned and errno is set to:556 *  ENOENT: Can't resolve the hostname, unless accept_unresolved is non-zero.557 *  EINVAL: Invalid port number.558 */559sentinelAddr *createSentinelAddr(char *hostname, int port, int is_accept_unresolved) {560    char ip[NET_IP_STR_LEN];561    sentinelAddr *sa;562563    if (port < 0 || port > 65535) {564        errno = EINVAL;565        return NULL;566    }567    if (anetResolve(NULL,hostname,ip,sizeof(ip),568                    sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {569        serverLog(LL_WARNING, "Failed to resolve hostname '%s'", hostname);570        if (sentinel.resolve_hostnames && is_accept_unresolved) {571            ip[0] = '\0';572        }573        else {574            errno = ENOENT;575            return NULL;576        }577    }578    sa = zmalloc(sizeof(*sa));579    sa->hostname = sdsnew(hostname);580    sa->ip = sdsnew(ip);581    sa->port = port;582    return sa;583}584585/* Return a duplicate of the source address. */586sentinelAddr *dupSentinelAddr(sentinelAddr *src) {587    sentinelAddr *sa;588589    sa = zmalloc(sizeof(*sa));590    sa->hostname = sdsnew(src->hostname);591    sa->ip = sdsnew(src->ip);592    sa->port = src->port;593    return sa;594}595596/* Free a Sentinel address. Can't fail. */597void releaseSentinelAddr(sentinelAddr *sa) {598    sdsfree(sa->hostname);599    sdsfree(sa->ip);600    zfree(sa);601}602603/* Return non-zero if the two addresses are equal, either by address604 * or by hostname if they could not have been resolved.605 */606int sentinelAddrOrHostnameEqual(sentinelAddr *a, sentinelAddr *b) {607    return a->port == b->port &&608            (!strcmp(a->ip, b->ip)  ||609            !strcasecmp(a->hostname, b->hostname));610}611612/* Return non-zero if a hostname matches an address. */613int sentinelAddrEqualsHostname(sentinelAddr *a, char *hostname) {614    char ip[NET_IP_STR_LEN];615616    /* Try resolve the hostname and compare it to the address */617    if (anetResolve(NULL, hostname, ip, sizeof(ip),618                    sentinel.resolve_hostnames ? ANET_NONE : ANET_IP_ONLY) == ANET_ERR) {619620        /* If failed resolve then compare based on hostnames. That is our best effort as621         * long as the server is unavailable for some reason. It is fine since Redis 622         * instance cannot have multiple hostnames for a given setup */623        return !strcasecmp(sentinel.resolve_hostnames ? a->hostname : a->ip, hostname);624    }625    /* Compare based on address */626    return !strcasecmp(a->ip, ip);627}628629const char *announceSentinelAddr(const sentinelAddr *a) {630    return sentinel.announce_hostnames ? a->hostname : a->ip;631}632633/* Return an allocated sds with hostname/address:port. IPv6634 * addresses are bracketed the same way anetFormatAddr() does.635 */636sds announceSentinelAddrAndPort(const sentinelAddr *a) {637    const char *addr = announceSentinelAddr(a);638    if (strchr(addr, ':') != NULL)639        return sdscatprintf(sdsempty(), "[%s]:%d", addr, a->port);640    else641        return sdscatprintf(sdsempty(), "%s:%d", addr, a->port);642}643644/* =========================== Events notification ========================== */645646/* Send an event to log, pub/sub, user notification script.647 *648 * 'level' is the log level for logging. Only LL_WARNING events will trigger649 * the execution of the user notification script.650 *651 * 'type' is the message type, also used as a pub/sub channel name.652 *653 * 'ri', is the redis instance target of this event if applicable, and is654 * used to obtain the path of the notification script to execute.655 *656 * The remaining arguments are printf-alike.657 * If the format specifier starts with the two characters "%@" then ri is658 * not NULL, and the message is prefixed with an instance identifier in the659 * following format:660 *661 *  <instance type> <instance name> <ip> <port>662 *663 *  If the instance type is not master, than the additional string is664 *  added to specify the originating master:665 *666 *  @ <master name> <master ip> <master port>667 *668 *  Any other specifier after "%@" is processed by printf itself.669 */670void sentinelEvent(int level, char *type, sentinelRedisInstance *ri,671                   const char *fmt, ...) {672    va_list ap;673    char msg[LOG_MAX_LEN];674    robj *channel, *payload;675676    /* Handle %@ */677    if (fmt[0] == '%' && fmt[1] == '@') {678        sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ?679                                         NULL : ri->master;680681        if (master) {682            snprintf(msg, sizeof(msg), "%s %s %s %d @ %s %s %d",683                sentinelRedisInstanceTypeStr(ri),684                ri->name, announceSentinelAddr(ri->addr), ri->addr->port,685                master->name, announceSentinelAddr(master->addr), master->addr->port);686        } else {687            snprintf(msg, sizeof(msg), "%s %s %s %d",688                sentinelRedisInstanceTypeStr(ri),689                ri->name, announceSentinelAddr(ri->addr), ri->addr->port);690        }691        fmt += 2;692    } else {693        msg[0] = '\0';694    }695696    /* Use vsprintf for the rest of the formatting if any. */697    if (fmt[0] != '\0') {698        va_start(ap, fmt);699        vsnprintf(msg+strlen(msg), sizeof(msg)-strlen(msg), fmt, ap);700        va_end(ap);701    }702703    /* Log the message if the log level allows it to be logged. */704    if (level >= server.verbosity)705        serverLog(level,"%s %s",type,msg);706707    /* Publish the message via Pub/Sub if it's not a debugging one. */708    if (level != LL_DEBUG) {709        channel = createStringObject(type,strlen(type));710        payload = createStringObject(msg,strlen(msg));711        pubsubPublishMessage(channel,payload,0);712        decrRefCount(channel);713        decrRefCount(payload);714    }715716    /* Call the notification script if applicable. */717    if (level == LL_WARNING && ri != NULL) {718        sentinelRedisInstance *master = (ri->flags & SRI_MASTER) ?719                                         ri : ri->master;720        if (master && master->notification_script) {721            sentinelScheduleScriptExecution(master->notification_script,722                type,msg,NULL);723        }724    }725}726727/* This function is called only at startup and is used to generate a728 * +monitor event for every configured master. The same events are also729 * generated when a master to monitor is added at runtime via the730 * SENTINEL MONITOR command. */731void sentinelGenerateInitialMonitorEvents(void) {732    dictIterator di;733    dictEntry *de;734735    dictInitIterator(&di, sentinel.masters);736    while((de = dictNext(&di)) != NULL) {737        sentinelRedisInstance *ri = dictGetVal(de);738        sentinelEvent(LL_WARNING,"+monitor",ri,"%@ quorum %d",ri->quorum);739    }740    dictResetIterator(&di);741}742743/* ============================ script execution ============================ */744745/* Release a script job structure and all the associated data. */746void sentinelReleaseScriptJob(sentinelScriptJob *sj) {747    int j = 0;748749    while(sj->argv[j]) sdsfree(sj->argv[j++]);750    zfree(sj->argv);751    zfree(sj);752}753754#define SENTINEL_SCRIPT_MAX_ARGS 16755void sentinelScheduleScriptExecution(char *path, ...) {756    va_list ap;757    char *argv[SENTINEL_SCRIPT_MAX_ARGS+1];758    int argc = 1;759    sentinelScriptJob *sj;760761    va_start(ap, path);762    while(argc < SENTINEL_SCRIPT_MAX_ARGS) {763        argv[argc] = va_arg(ap,char*);764        if (!argv[argc]) break;765        argv[argc] = sdsnew(argv[argc]); /* Copy the string. */766        argc++;767    }768    va_end(ap);769    argv[0] = sdsnew(path);770771    sj = zmalloc(sizeof(*sj));772    sj->flags = SENTINEL_SCRIPT_NONE;773    sj->retry_num = 0;774    sj->argv = zmalloc(sizeof(char*)*(argc+1));775    sj->start_time = 0;776    sj->pid = 0;777    memcpy(sj->argv,argv,sizeof(char*)*(argc+1));778779    listAddNodeTail(sentinel.scripts_queue,sj);780781    /* Remove the oldest non running script if we already hit the limit. */782    if (listLength(sentinel.scripts_queue) > SENTINEL_SCRIPT_MAX_QUEUE) {783        listNode *ln;784        listIter li;785786        listRewind(sentinel.scripts_queue,&li);787        while ((ln = listNext(&li)) != NULL) {788            sj = ln->value;789790            if (sj->flags & SENTINEL_SCRIPT_RUNNING) continue;791            /* The first node is the oldest as we add on tail. */792            listDelNode(sentinel.scripts_queue,ln);793            sentinelReleaseScriptJob(sj);794            break;795        }796        serverAssert(listLength(sentinel.scripts_queue) <=797                    SENTINEL_SCRIPT_MAX_QUEUE);798    }799}800801/* Lookup a script in the scripts queue via pid, and returns the list node802 * (so that we can easily remove it from the queue if needed). */803listNode *sentinelGetScriptListNodeByPid(pid_t pid) {804    listNode *ln;805    listIter li;806807    listRewind(sentinel.scripts_queue,&li);808    while ((ln = listNext(&li)) != NULL) {809        sentinelScriptJob *sj = ln->value;810811        if ((sj->flags & SENTINEL_SCRIPT_RUNNING) && sj->pid == pid)812            return ln;813    }814    return NULL;815}816817/* Run pending scripts if we are not already at max number of running818 * scripts. */819void sentinelRunPendingScripts(void) {820    listNode *ln;821    listIter li;822    mstime_t now = mstime();823824    /* Find jobs that are not running and run them, from the top to the825     * tail of the queue, so we run older jobs first. */826    listRewind(sentinel.scripts_queue,&li);827    while (sentinel.running_scripts < SENTINEL_SCRIPT_MAX_RUNNING &&828           (ln = listNext(&li)) != NULL)829    {830        sentinelScriptJob *sj = ln->value;831        pid_t pid;832833        /* Skip if already running. */834        if (sj->flags & SENTINEL_SCRIPT_RUNNING) continue;835836        /* Skip if it's a retry, but not enough time has elapsed. */837        if (sj->start_time && sj->start_time > now) continue;838839        sj->flags |= SENTINEL_SCRIPT_RUNNING;840        sj->start_time = mstime();841        sj->retry_num++;842        pid = fork();843844        if (pid == -1) {845            /* Parent (fork error).846             * We report fork errors as signal 99, in order to unify the847             * reporting with other kind of errors. */848            sentinelEvent(LL_WARNING,"-script-error",NULL,849                          "%s %d %d", sj->argv[0], 99, 0);850            sj->flags &= ~SENTINEL_SCRIPT_RUNNING;851            sj->pid = 0;852        } else if (pid == 0) {853            /* Child */854            connTypeCleanupAll();855            execve(sj->argv[0],sj->argv,environ);856            /* If we are here an error occurred. */857            _exit(2); /* Don't retry execution. */858        } else {859            sentinel.running_scripts++;860            sj->pid = pid;861            sentinelEvent(LL_DEBUG,"+script-child",NULL,"%ld",(long)pid);862        }863    }864}865866/* How much to delay the execution of a script that we need to retry after867 * an error?868 *869 * We double the retry delay for every further retry we do. So for instance870 * if RETRY_DELAY is set to 30 seconds and the max number of retries is 10871 * starting from the second attempt to execute the script the delays are:872 * 30 sec, 60 sec, 2 min, 4 min, 8 min, 16 min, 32 min, 64 min, 128 min. */873mstime_t sentinelScriptRetryDelay(int retry_num) {874    mstime_t delay = sentinel_script_retry_delay;875876    while (retry_num-- > 1) delay *= 2;877    return delay;878}879880/* Check for scripts that terminated, and remove them from the queue if the881 * script terminated successfully. If instead the script was terminated by882 * a signal, or returned exit code "1", it is scheduled to run again if883 * the max number of retries did not already elapsed. */884void sentinelCollectTerminatedScripts(void) {885    int statloc;886    pid_t pid;887888    while ((pid = waitpid(-1, &statloc, WNOHANG)) > 0) {889        int exitcode = WEXITSTATUS(statloc);890        int bysignal = 0;891        listNode *ln;892        sentinelScriptJob *sj;893894        if (WIFSIGNALED(statloc)) bysignal = WTERMSIG(statloc);895        sentinelEvent(LL_DEBUG,"-script-child",NULL,"%ld %d %d",896            (long)pid, exitcode, bysignal);897898        ln = sentinelGetScriptListNodeByPid(pid);899        if (ln == NULL) {900            serverLog(LL_WARNING,"waitpid() returned a pid (%ld) we can't find in our scripts execution queue!", (long)pid);901            continue;902        }903        sj = ln->value;904905        /* If the script was terminated by a signal or returns an906         * exit code of "1" (that means: please retry), we reschedule it907         * if the max number of retries is not already reached. */908        if ((bysignal || exitcode == 1) &&909            sj->retry_num != SENTINEL_SCRIPT_MAX_RETRY)910        {911            sj->flags &= ~SENTINEL_SCRIPT_RUNNING;912            sj->pid = 0;913            sj->start_time = mstime() +914                             sentinelScriptRetryDelay(sj->retry_num);915        } else {916            /* Otherwise let's remove the script, but log the event if the917             * execution did not terminated in the best of the ways. */918            if (bysignal || exitcode != 0) {919                sentinelEvent(LL_WARNING,"-script-error",NULL,920                              "%s %d %d", sj->argv[0], bysignal, exitcode);921            }922            listDelNode(sentinel.scripts_queue,ln);923            sentinelReleaseScriptJob(sj);924        }925        sentinel.running_scripts--;926    }927}928929/* Kill scripts in timeout, they'll be collected by the930 * sentinelCollectTerminatedScripts() function. */931void sentinelKillTimedoutScripts(void) {932    listNode *ln;933    listIter li;934    mstime_t now = mstime();935936    listRewind(sentinel.scripts_queue,&li);937    while ((ln = listNext(&li)) != NULL) {938        sentinelScriptJob *sj = ln->value;939940        if (sj->flags & SENTINEL_SCRIPT_RUNNING &&941            (now - sj->start_time) > sentinel_script_max_runtime)942        {943            sentinelEvent(LL_WARNING,"-script-timeout",NULL,"%s %ld",944                sj->argv[0], (long)sj->pid);945            kill(sj->pid,SIGKILL);946        }947    }948}949950/* Implements SENTINEL PENDING-SCRIPTS command. */951void sentinelPendingScriptsCommand(client *c) {952    listNode *ln;953    listIter li;954955    addReplyArrayLen(c,listLength(sentinel.scripts_queue));956    listRewind(sentinel.scripts_queue,&li);957    while ((ln = listNext(&li)) != NULL) {958        sentinelScriptJob *sj = ln->value;959        int j = 0;960961        addReplyMapLen(c,5);962963        addReplyBulkCString(c,"argv");964        while (sj->argv[j]) j++;965        addReplyArrayLen(c,j);966        j = 0;967        while (sj->argv[j]) addReplyBulkCString(c,sj->argv[j++]);968969        addReplyBulkCString(c,"flags");970        addReplyBulkCString(c,971            (sj->flags & SENTINEL_SCRIPT_RUNNING) ? "running" : "scheduled");972973        addReplyBulkCString(c,"pid");974        addReplyBulkLongLong(c,sj->pid);975976        if (sj->flags & SENTINEL_SCRIPT_RUNNING) {977            addReplyBulkCString(c,"run-time");978            addReplyBulkLongLong(c,mstime() - sj->start_time);979        } else {980            mstime_t delay = sj->start_time ? (sj->start_time-mstime()) : 0;981            if (delay < 0) delay = 0;982            addReplyBulkCString(c,"run-delay");983            addReplyBulkLongLong(c,delay);984        }985986        addReplyBulkCString(c,"retry-num");987        addReplyBulkLongLong(c,sj->retry_num);988    }989}990991/* This function calls, if any, the client reconfiguration script with the992 * following parameters:993 *994 * <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>995 *996 * It is called every time a failover is performed.997 *998 * <state> is currently always "start".999 * <role> is either "leader" or "observer".1000 *1001 * from/to fields are respectively master -> promoted slave addresses for1002 * "start" and "end". */1003void sentinelCallClientReconfScript(sentinelRedisInstance *master, int role, char *state, sentinelAddr *from, sentinelAddr *to) {1004    char fromport[32], toport[32];10051006    if (master->client_reconfig_script == NULL) return;1007    ll2string(fromport,sizeof(fromport),from->port);1008    ll2string(toport,sizeof(toport),to->port);1009    sentinelScheduleScriptExecution(master->client_reconfig_script,1010        master->name,1011        (role == SENTINEL_LEADER) ? "leader" : "observer",1012        state, announceSentinelAddr(from), fromport,1013        announceSentinelAddr(to), toport, NULL);1014}10151016/* =============================== instanceLink ============================= */10171018/* Create a not yet connected link object. */1019instanceLink *createInstanceLink(void) {1020    instanceLink *link = zmalloc(sizeof(*link));10211022    link->refcount = 1;1023    link->disconnected = 1;1024    link->pending_commands = 0;1025    link->cc = NULL;1026    link->pc = NULL;1027    link->cc_conn_time = 0;1028    link->pc_conn_time = 0;1029    link->last_reconn_time = 0;1030    link->pc_last_activity = 0;1031    /* We set the act_ping_time to "now" even if we actually don't have yet1032     * a connection with the node, nor we sent a ping.1033     * This is useful to detect a timeout in case we'll not be able to connect1034     * with the node at all. */1035    link->act_ping_time = mstime();1036    link->last_ping_time = 0;1037    link->last_avail_time = mstime();1038    link->last_pong_time = mstime();1039    return link;1040}10411042/* Disconnect a hiredis connection in the context of an instance link. */1043void instanceLinkCloseConnection(instanceLink *link, redisAsyncContext *c) {1044    if (c == NULL) return;10451046    if (link->cc == c) {1047        link->cc = NULL;1048        link->pending_commands = 0;1049    }1050    if (link->pc == c) link->pc = NULL;1051    c->data = NULL;1052    link->disconnected = 1;1053    redisAsyncFree(c);1054}10551056/* Decrement the refcount of a link object, if it drops to zero, actually1057 * free it and return NULL. Otherwise don't do anything and return the pointer1058 * to the object.1059 *1060 * If we are not going to free the link and ri is not NULL, we rebind all the1061 * pending requests in link->cc (hiredis connection for commands) to a1062 * callback that will just ignore them. This is useful to avoid processing1063 * replies for an instance that no longer exists. */1064instanceLink *releaseInstanceLink(instanceLink *link, sentinelRedisInstance *ri)1065{1066    serverAssert(link->refcount > 0);1067    link->refcount--;1068    if (link->refcount != 0) {1069        if (ri && ri->link->cc) {1070            /* This instance may have pending callbacks in the hiredis async1071             * context, having as 'privdata' the instance that we are going to1072             * free. Let's rewrite the callback list, directly exploiting1073             * hiredis internal data structures, in order to bind them with1074             * a callback that will ignore the reply at all. */1075            redisCallback *cb;1076            redisCallbackList *callbacks = &link->cc->replies;10771078            cb = callbacks->head;1079            while(cb) {1080                if (cb->privdata == ri) {1081                    cb->fn = sentinelDiscardReplyCallback;1082                    cb->privdata = NULL; /* Not strictly needed. */1083                }1084                cb = cb->next;1085            }1086        }1087        return link; /* Other active users. */1088    }10891090    instanceLinkCloseConnection(link,link->cc);1091    instanceLinkCloseConnection(link,link->pc);1092    zfree(link);1093    return NULL;1094}10951096/* This function will attempt to share the instance link we already have1097 * for the same Sentinel in the context of a different master, with the1098 * instance we are passing as argument.1099 *1100 * This way multiple Sentinel objects that refer all to the same physical1101 * Sentinel instance but in the context of different masters will use1102 * a single connection, will send a single PING per second for failure1103 * detection and so forth.1104 *1105 * Return C_OK if a matching Sentinel was found in the context of a1106 * different master and sharing was performed. Otherwise C_ERR1107 * is returned. */1108int sentinelTryConnectionSharing(sentinelRedisInstance *ri) {1109    serverAssert(ri->flags & SRI_SENTINEL);1110    dictIterator di;1111    dictEntry *de;11121113    if (ri->runid == NULL) return C_ERR; /* No way to identify it. */1114    if (ri->link->refcount > 1) return C_ERR; /* Already shared. */11151116    dictInitIterator(&di, sentinel.masters);1117    while((de = dictNext(&di)) != NULL) {1118        sentinelRedisInstance *master = dictGetVal(de), *match;1119        /* We want to share with the same physical Sentinel referenced1120         * in other masters, so skip our master. */1121        if (master == ri->master) continue;1122        match = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,1123                                                       NULL,0,ri->runid);1124        if (match == NULL) continue; /* No match. */1125        if (match == ri) continue; /* Should never happen but... safer. */11261127        /* We identified a matching Sentinel, great! Let's free our link1128         * and use the one of the matching Sentinel. */1129        releaseInstanceLink(ri->link,NULL);1130        ri->link = match->link;1131        match->link->refcount++;1132        dictResetIterator(&di);1133        return C_OK;1134    }1135    dictResetIterator(&di);1136    return C_ERR;1137}11381139/* Disconnect the relevant master and its replicas. */1140void dropInstanceConnections(sentinelRedisInstance *ri) {1141    serverAssert(ri->flags & SRI_MASTER);11421143    /* Disconnect with the master. */1144    instanceLinkCloseConnection(ri->link, ri->link->cc);1145    instanceLinkCloseConnection(ri->link, ri->link->pc);1146    1147    /* Disconnect with all replicas. */1148    dictIterator di;1149    dictEntry *de;1150    sentinelRedisInstance *repl_ri;11511152    dictInitIterator(&di, ri->slaves);1153    while ((de = dictNext(&di)) != NULL) {1154        repl_ri = dictGetVal(de);1155        instanceLinkCloseConnection(repl_ri->link, repl_ri->link->cc);1156        instanceLinkCloseConnection(repl_ri->link, repl_ri->link->pc);1157    }1158    dictResetIterator(&di);1159}11601161/* Drop all connections to other sentinels. Returns the number of connections1162 * dropped.*/1163int sentinelDropConnections(void) {1164    dictIterator di;1165    dictEntry *de;1166    int dropped = 0;11671168    dictInitIterator(&di, sentinel.masters);1169    while ((de = dictNext(&di)) != NULL) {1170        dictIterator sdi;1171        dictEntry *sde;11721173        sentinelRedisInstance *ri = dictGetVal(de);1174        dictInitIterator(&sdi, ri->sentinels);1175        while ((sde = dictNext(&sdi)) != NULL) {1176            sentinelRedisInstance *si = dictGetVal(sde);1177            if (!si->link->disconnected) {1178                instanceLinkCloseConnection(si->link, si->link->pc);1179                instanceLinkCloseConnection(si->link, si->link->cc);1180                dropped++;1181            }1182        }1183        dictResetIterator(&sdi);1184    }1185    dictResetIterator(&di);11861187    return dropped;1188}11891190/* When we detect a Sentinel to switch address (reporting a different IP/port1191 * pair in Hello messages), let's update all the matching Sentinels in the1192 * context of other masters as well and disconnect the links, so that everybody1193 * will be updated.1194 *1195 * Return the number of updated Sentinel addresses. */1196int sentinelUpdateSentinelAddressInAllMasters(sentinelRedisInstance *ri) {1197    serverAssert(ri->flags & SRI_SENTINEL);1198    dictIterator di;1199    dictEntry *de;1200    int reconfigured = 0;12011202    dictInitIterator(&di, sentinel.masters);1203    while((de = dictNext(&di)) != NULL) {1204        sentinelRedisInstance *master = dictGetVal(de), *match;1205        match = getSentinelRedisInstanceByAddrAndRunID(master->sentinels,1206                                                       NULL,0,ri->runid);1207        /* If there is no match, this master does not know about this1208         * Sentinel, try with the next one. */1209        if (match == NULL) continue;12101211        /* Disconnect the old links if connected. */1212        if (match->link->cc != NULL)1213            instanceLinkCloseConnection(match->link,match->link->cc);1214        if (match->link->pc != NULL)1215            instanceLinkCloseConnection(match->link,match->link->pc);12161217        if (match == ri) continue; /* Address already updated for it. */12181219        /* Update the address of the matching Sentinel by copying the address1220         * of the Sentinel object that received the address update. */1221        releaseSentinelAddr(match->addr);1222        match->addr = dupSentinelAddr(ri->addr);1223        reconfigured++;1224    }1225    dictResetIterator(&di);1226    if (reconfigured)1227        sentinelEvent(LL_NOTICE,"+sentinel-address-update", ri,1228                    "%@ %d additional matching instances", reconfigured);1229    return reconfigured;1230}12311232/* This function is called when a hiredis connection reported an error.1233 * We set it to NULL and mark the link as disconnected so that it will be1234 * reconnected again.1235 *1236 * Note: we don't free the hiredis context as hiredis will do it for us1237 * for async connections. */1238void instanceLinkConnectionError(const redisAsyncContext *c) {1239    instanceLink *link = c->data;1240    int pubsub;12411242    if (!link) return;12431244    pubsub = (link->pc == c);1245    if (pubsub)1246        link->pc = NULL;1247    else1248        link->cc = NULL;1249    link->disconnected = 1;1250}12511252/* Hiredis connection established / disconnected callbacks. We need them1253 * just to cleanup our link state. */1254void sentinelLinkEstablishedCallback(const redisAsyncContext *c, int status) {1255    if (status != C_OK) instanceLinkConnectionError(c);1256}12571258void sentinelDisconnectCallback(const redisAsyncContext *c, int status) {1259    UNUSED(status);1260    instanceLinkConnectionError(c);1261}12621263/* ========================== sentinelRedisInstance ========================= */12641265/* Create a redis instance, the following fields must be populated by the1266 * caller if needed:1267 * runid: set to NULL but will be populated once INFO output is received.1268 * info_refresh: is set to 0 to mean that we never received INFO so far.1269 *1270 * If SRI_MASTER is set into initial flags the instance is added to1271 * sentinel.masters table.1272 *1273 * if SRI_SLAVE or SRI_SENTINEL is set then 'master' must be not NULL and the1274 * instance is added into master->slaves or master->sentinels table.1275 *1276 * If the instance is a slave, the name parameter is ignored and is created1277 * automatically as ip/hostname:port.1278 *1279 * The function fails if hostname can't be resolved or port is out of range.1280 * When this happens NULL is returned and errno is set accordingly to the1281 * createSentinelAddr() function.1282 *1283 * The function may also fail and return NULL with errno set to EBUSY if1284 * a master with the same name, a slave with the same address, or a sentinel1285 * with the same ID already exists. */12861287sentinelRedisInstance *createSentinelRedisInstance(char *name, int flags, char *hostname, int port, int quorum, sentinelRedisInstance *master) {1288    sentinelRedisInstance *ri;1289    sentinelAddr *addr;1290    dict *table = NULL;1291    sds sdsname;12921293    serverAssert(flags & (SRI_MASTER|SRI_SLAVE|SRI_SENTINEL));1294    serverAssert((flags & SRI_MASTER) || master != NULL);12951296    /* Check address validity. */1297    addr = createSentinelAddr(hostname,port,1);1298    if (addr == NULL) return NULL;12991300    /* For slaves use ip/host:port as name. */1301    if (flags & SRI_SLAVE)1302        sdsname = announceSentinelAddrAndPort(addr);1303    else1304        sdsname = sdsnew(name);13051306    /* Make sure the entry is not duplicated. This may happen when the same1307     * name for a master is used multiple times inside the configuration or1308     * if we try to add multiple times a slave or sentinel with same ip/port1309     * to a master. */1310    if (flags & SRI_MASTER) table = sentinel.masters;1311    else if (flags & SRI_SLAVE) table = master->slaves;1312    else if (flags & SRI_SENTINEL) table = master->sentinels;1313    if (dictFind(table,sdsname)) {1314        releaseSentinelAddr(addr);1315        sdsfree(sdsname);1316        errno = EBUSY;1317        return NULL;1318    }13191320    /* Create the instance object. */1321    ri = zmalloc(sizeof(*ri));1322    /* Note that all the instances are started in the disconnected state,1323     * the event loop will take care of connecting them. */1324    ri->flags = flags;1325    ri->name = sdsname;1326    ri->runid = NULL;1327    ri->config_epoch = 0;1328    ri->addr = addr;1329    ri->link = createInstanceLink();1330    ri->last_pub_time = mstime();1331    ri->last_hello_time = mstime();1332    ri->last_master_down_reply_time = mstime();1333    ri->s_down_since_time = 0;1334    ri->o_down_since_time = 0;1335    ri->down_after_period = master ? master->down_after_period : sentinel_default_down_after;1336    ri->master_reboot_down_after_period = 0;1337    ri->master_link_down_time = 0;1338    ri->auth_pass = NULL;1339    ri->auth_user = NULL;1340    ri->slave_priority = SENTINEL_DEFAULT_SLAVE_PRIORITY;1341    ri->replica_announced = 1;1342    ri->slave_reconf_sent_time = 0;1343    ri->slave_master_host = NULL;1344    ri->slave_master_port = 0;1345    ri->slave_master_link_status = SENTINEL_MASTER_LINK_STATUS_DOWN;1346    ri->slave_repl_offset = 0;1347    ri->sentinels = dictCreate(&instancesDictType);1348    ri->quorum = quorum;1349    ri->parallel_syncs = SENTINEL_DEFAULT_PARALLEL_SYNCS;1350    ri->master = master;1351    ri->slaves = dictCreate(&instancesDictType);1352    ri->info_refresh = 0;1353    ri->renamed_commands = dictCreate(&renamedCommandsDictType);13541355    /* Failover state. */1356    ri->leader = NULL;1357    ri->leader_epoch = 0;1358    ri->failover_epoch = 0;1359    ri->failover_state = SENTINEL_FAILOVER_STATE_NONE;1360    ri->failover_state_change_time = 0;1361    ri->failover_start_time = 0;1362    ri->failover_timeout = sentinel_default_failover_timeout;1363    ri->failover_delay_logged = 0;1364    ri->promoted_slave = NULL;1365    ri->notification_script = NULL;1366    ri->client_reconfig_script = NULL;1367    ri->info = NULL;13681369    /* Role */1370    ri->role_reported = ri->flags & (SRI_MASTER|SRI_SLAVE);1371    ri->role_reported_time = mstime();1372    ri->slave_conf_change_time = mstime();13731374    /* Add into the right table. */1375    dictAdd(table, ri->name, ri);1376    return ri;1377}13781379/* Release this instance and all its slaves, sentinels, hiredis connections.1380 * This function does not take care of unlinking the instance from the main1381 * masters table (if it is a master) or from its master sentinels/slaves table1382 * if it is a slave or sentinel. */1383void releaseSentinelRedisInstance(sentinelRedisInstance *ri) {1384    /* Release all its slaves or sentinels if any. */1385    dictRelease(ri->sentinels);1386    dictRelease(ri->slaves);13871388    /* Disconnect the instance. */1389    releaseInstanceLink(ri->link,ri);13901391    /* Free other resources. */1392    sdsfree(ri->name);1393    sdsfree(ri->runid);1394    sdsfree(ri->notification_script);1395    sdsfree(ri->client_reconfig_script);1396    sdsfree(ri->slave_master_host);1397    sdsfree(ri->leader);1398    sdsfree(ri->auth_pass);1399    sdsfree(ri->auth_user);1400    sdsfree(ri->info);1401    releaseSentinelAddr(ri->addr);1402    dictRelease(ri->renamed_commands);14031404    /* Clear state into the master if needed. */1405    if ((ri->flags & SRI_SLAVE) && (ri->flags & SRI_PROMOTED) && ri->master)1406        ri->master->promoted_slave = NULL;14071408    zfree(ri);1409}14101411/* Lookup a slave in a master Redis instance, by ip and port. */1412sentinelRedisInstance *sentinelRedisInstanceLookupSlave(1413                sentinelRedisInstance *ri, char *slave_addr, int port)1414{1415    sds key;1416    sentinelRedisInstance *slave;1417    sentinelAddr *addr;14181419    serverAssert(ri->flags & SRI_MASTER);14201421    /* We need to handle a slave_addr that is potentially a hostname.1422     * If that is the case, depending on configuration we either resolve1423     * it and use the IP address or fail.1424     */1425    addr = createSentinelAddr(slave_addr, port, 0);1426    if (!addr) return NULL;1427    key = announceSentinelAddrAndPort(addr);1428    releaseSentinelAddr(addr);14291430    slave = dictFetchValue(ri->slaves,key);1431    sdsfree(key);1432    return slave;1433}14341435/* Return the name of the type of the instance as a string. */1436const char *sentinelRedisInstanceTypeStr(sentinelRedisInstance *ri) {1437    if (ri->flags & SRI_MASTER) return "master";1438    else if (ri->flags & SRI_SLAVE) return "slave";1439    else if (ri->flags & SRI_SENTINEL) return "sentinel";1440    else return "unknown";1441}14421443/* This function remove the Sentinel with the specified ID from the1444 * specified master.1445 *1446 * If "runid" is NULL the function returns ASAP.1447 *1448 * This function is useful because on Sentinels address switch, we want to1449 * remove our old entry and add a new one for the same ID but with the new1450 * address.1451 *1452 * The function returns 1 if the matching Sentinel was removed, otherwise1453 * 0 if there was no Sentinel with this ID. */1454int removeMatchingSentinelFromMaster(sentinelRedisInstance *master, char *runid) {1455    dictIterator di;1456    dictEntry *de;1457    int removed = 0;14581459    if (runid == NULL) return 0;14601461    dictInitSafeIterator(&di, master->sentinels);1462    while((de = dictNext(&di)) != NULL) {1463        sentinelRedisInstance *ri = dictGetVal(de);14641465        if (ri->runid && strcmp(ri->runid,runid) == 0) {1466            dictDelete(master->sentinels,ri->name);1467            removed++;1468        }1469    }1470    dictResetIterator(&di);1471    return removed;1472}14731474/* Search an instance with the same runid, ip and port into a dictionary1475 * of instances. Return NULL if not found, otherwise return the instance1476 * pointer.1477 *1478 * runid or addr can be NULL. In such a case the search is performed only1479 * by the non-NULL field. */1480sentinelRedisInstance *getSentinelRedisInstanceByAddrAndRunID(dict *instances, char *addr, int port, char *runid) {1481    dictIterator di;1482    dictEntry *de;1483    sentinelRedisInstance *instance = NULL;1484    sentinelAddr *ri_addr = NULL;14851486    serverAssert(addr || runid);   /* User must pass at least one search param. */1487    if (addr != NULL) {1488        /* Try to resolve addr. If hostnames are used, we're accepting an ri_addr1489         * that contains an hostname only and can still be matched based on that.1490         */1491        ri_addr = createSentinelAddr(addr,port,1);1492        if (!ri_addr) return NULL;1493    }1494    dictInitIterator(&di, instances);1495    while((de = dictNext(&di)) != NULL) {1496        sentinelRedisInstance *ri = dictGetVal(de);14971498        if (runid && !ri->runid) continue;1499        if ((runid == NULL || strcmp(ri->runid, runid) == 0) &&1500            (addr == NULL || sentinelAddrOrHostnameEqual(ri->addr, ri_addr)))1501        {1502            instance = ri;1503            break;1504        }1505    }1506    dictResetIterator(&di);1507    if (ri_addr != NULL)1508        releaseSentinelAddr(ri_addr);15091510    return instance;1511}15121513/* Master lookup by name */1514sentinelRedisInstance *sentinelGetMasterByName(char *name) {1515    sentinelRedisInstance *ri;1516    sds sdsname = sdsnew(name);15171518    ri = dictFetchValue(sentinel.masters,sdsname);1519    sdsfree(sdsname);1520    return ri;1521}15221523/* Reset the state of a monitored master:1524 * 1) Remove all slaves.1525 * 2) Remove all sentinels.1526 * 3) Remove most of the flags resulting from runtime operations.1527 * 4) Reset timers to their default value. For example after a reset it will be1528 *    possible to failover again the same master ASAP, without waiting the1529 *    failover timeout delay.1530 * 5) In the process of doing this undo the failover if in progress.1531 * 6) Disconnect the connections with the master (will reconnect automatically).1532 */15331534#define SENTINEL_RESET_NO_SENTINELS (1<<0)1535void sentinelResetMaster(sentinelRedisInstance *ri, int flags) {1536    serverAssert(ri->flags & SRI_MASTER);1537    dictRelease(ri->slaves);1538    ri->slaves = dictCreate(&instancesDictType);1539    if (!(flags & SENTINEL_RESET_NO_SENTINELS)) {1540        dictRelease(ri->sentinels);1541        ri->sentinels = dictCreate(&instancesDictType);1542    }1543    instanceLinkCloseConnection(ri->link,ri->link->cc);1544    instanceLinkCloseConnection(ri->link,ri->link->pc);1545    ri->flags &= SRI_MASTER;1546    if (ri->leader) {1547        sdsfree(ri->leader);1548        ri->leader = NULL;1549    }1550    ri->failover_state = SENTINEL_FAILOVER_STATE_NONE;1551    ri->failover_state_change_time = 0;1552    ri->failover_start_time = 0; /* We can failover again ASAP. */1553    ri->promoted_slave = NULL;1554    sdsfree(ri->runid);1555    sdsfree(ri->slave_master_host);1556    ri->runid = NULL;1557    ri->slave_master_host = NULL;1558    ri->link->act_ping_time = mstime();1559    ri->link->last_ping_time = 0;1560    ri->link->last_avail_time = mstime();1561    ri->link->last_pong_time = mstime();1562    ri->role_reported_time = mstime();1563    ri->role_reported = SRI_MASTER;1564    if (flags & SENTINEL_GENERATE_EVENT)1565        sentinelEvent(LL_WARNING,"+reset-master",ri,"%@");1566}15671568/* Call sentinelResetMaster() on every master with a name matching the specified1569 * pattern. */1570int sentinelResetMastersByPattern(char *pattern, int flags) {1571    dictIterator di;1572    dictEntry *de;1573    int reset = 0;15741575    dictInitIterator(&di, sentinel.masters);1576    while((de = dictNext(&di)) != NULL) {1577        sentinelRedisInstance *ri = dictGetVal(de);15781579        if (ri->name) {1580            if (stringmatch(pattern,ri->name,0)) {1581                sentinelResetMaster(ri,flags);1582                reset++;1583            }1584        }1585    }1586    dictResetIterator(&di);1587    return reset;1588}15891590/* Reset the specified master with sentinelResetMaster(), and also change1591 * the ip:port address, but take the name of the instance unmodified.1592 *1593 * This is used to handle the +switch-master event.1594 *1595 * The function returns C_ERR if the address can't be resolved for some1596 * reason. Otherwise C_OK is returned.  */1597int sentinelResetMasterAndChangeAddress(sentinelRedisInstance *master, char *hostname, int port) {1598    sentinelAddr *oldaddr, *newaddr;1599    sentinelAddr **slaves = NULL;1600    int numslaves = 0, j;1601    dictIterator di;1602    dictEntry *de;16031604    newaddr = createSentinelAddr(hostname,port,0);1605    if (newaddr == NULL) return C_ERR;16061607    /* There can be only 0 or 1 slave that has the newaddr.1608     * and It can add old master 1 more slave. 1609     * so It allocates dictSize(master->slaves) + 1          */1610    slaves = zmalloc(sizeof(sentinelAddr*)*(dictSize(master->slaves) + 1));1611    1612    /* Don't include the one having the address we are switching to. */1613    dictInitIterator(&di, master->slaves);1614    while((de = dictNext(&di)) != NULL) {1615        sentinelRedisInstance *slave = dictGetVal(de);16161617        if (sentinelAddrOrHostnameEqual(slave->addr,newaddr)) continue;1618        slaves[numslaves++] = dupSentinelAddr(slave->addr);1619    }1620    dictResetIterator(&di);16211622    /* If we are switching to a different address, include the old address1623     * as a slave as well, so that we'll be able to sense / reconfigure1624     * the old master. */1625    if (!sentinelAddrOrHostnameEqual(newaddr,master->addr)) {1626        slaves[numslaves++] = dupSentinelAddr(master->addr);1627    }16281629    /* Reset and switch address. */1630    sentinelResetMaster(master,SENTINEL_RESET_NO_SENTINELS);1631    oldaddr = master->addr;1632    master->addr = newaddr;1633    master->o_down_since_time = 0;1634    master->s_down_since_time = 0;16351636    /* Add slaves back. */1637    for (j = 0; j < numslaves; j++) {1638        sentinelRedisInstance *slave;16391640        slave = createSentinelRedisInstance(NULL,SRI_SLAVE,slaves[j]->hostname,1641                    slaves[j]->port, master->quorum, master);1642        releaseSentinelAddr(slaves[j]);1643        if (slave) sentinelEvent(LL_NOTICE,"+slave",slave,"%@");1644    }1645    zfree(slaves);16461647    /* Release the old address at the end so we are safe even if the function1648     * gets the master->addr->ip and master->addr->port as arguments. */1649    releaseSentinelAddr(oldaddr);1650    sentinelFlushConfig();1651    return C_OK;1652}16531654/* Return non-zero if there was no SDOWN or ODOWN error associated to this1655 * instance in the latest 'ms' milliseconds. */1656int sentinelRedisInstanceNoDownFor(sentinelRedisInstance *ri, mstime_t ms) {1657    mstime_t most_recent;16581659    most_recent = ri->s_down_since_time;1660    if (ri->o_down_since_time > most_recent)1661        most_recent = ri->o_down_since_time;1662    return most_recent == 0 || (mstime() - most_recent) > ms;1663}16641665/* Return the current master address, that is, its address or the address1666 * of the promoted slave if already operational. */1667sentinelAddr *sentinelGetCurrentMasterAddress(sentinelRedisInstance *master) {1668    /* If we are failing over the master, and the state is already1669     * SENTINEL_FAILOVER_STATE_RECONF_SLAVES or greater, it means that we1670     * already have the new configuration epoch in the master, and the1671     * slave acknowledged the configuration switch. Advertise the new1672     * address. */1673    if ((master->flags & SRI_FAILOVER_IN_PROGRESS) &&1674        master->promoted_slave &&1675        master->failover_state >= SENTINEL_FAILOVER_STATE_RECONF_SLAVES)1676    {1677        return master->promoted_slave->addr;1678    } else {1679        return master->addr;1680    }1681}16821683/* This function sets the down_after_period field value in 'master' to all1684 * the slaves and sentinel instances connected to this master. */1685void sentinelPropagateDownAfterPeriod(sentinelRedisInstance *master) {1686    dictIterator di;1687    dictEntry *de;1688    int j;1689    dict *d[] = {master->slaves, master->sentinels, NULL};16901691    for (j = 0; d[j]; j++) {1692        dictInitIterator(&di, d[j]);1693        while((de = dictNext(&di)) != NULL) {1694            sentinelRedisInstance *ri = dictGetVal(de);1695            ri->down_after_period = master->down_after_period;1696        }1697        dictResetIterator(&di);1698    }1699}17001701/* This function is used in order to send commands to Redis instances: the1702 * commands we send from Sentinel may be renamed, a common case is a master1703 * with CONFIG and SLAVEOF commands renamed for security concerns. In that1704 * case we check the ri->renamed_command table (or if the instance is a slave,1705 * we check the one of the master), and map the command that we should send1706 * to the set of renamed commands. However, if the command was not renamed,1707 * we just return "command" itself. */1708char *sentinelInstanceMapCommand(sentinelRedisInstance *ri, char *command) {1709    sds sc = sdsnew(command);1710    if (ri->master) ri = ri->master;1711    char *retval = dictFetchValue(ri->renamed_commands, sc);1712    sdsfree(sc);1713    return retval ? retval : command;1714}17151716/* ============================ Config handling ============================= */17171718/* Generalise handling create instance error. Use SRI_MASTER, SRI_SLAVE or1719 * SRI_SENTINEL as a role value. */1720const char *sentinelCheckCreateInstanceErrors(int role) {1721    switch(errno) {1722    case EBUSY:1723        switch (role) {1724        case SRI_MASTER:1725            return "Duplicate master name.";1726        case SRI_SLAVE:1727            return "Duplicate hostname and port for replica.";1728        case SRI_SENTINEL:1729            return "Duplicate runid for sentinel.";1730        default:1731            serverAssert(0);1732            break;1733        }1734        break;1735    case ENOENT:1736        return "Can't resolve instance hostname.";1737    case EINVAL:1738        return "Invalid port number.";1739    default:1740        return "Unknown Error for creating instances.";1741    }1742}17431744/* init function for server.sentinel_config */1745void initializeSentinelConfig(void) {1746    server.sentinel_config = zmalloc(sizeof(struct sentinelConfig));1747    server.sentinel_config->monitor_cfg = listCreate();1748    server.sentinel_config->pre_monitor_cfg = listCreate();1749    server.sentinel_config->post_monitor_cfg = listCreate();1750    listSetFreeMethod(server.sentinel_config->monitor_cfg,freeSentinelLoadQueueEntry);1751    listSetFreeMethod(server.sentinel_config->pre_monitor_cfg,freeSentinelLoadQueueEntry);1752    listSetFreeMethod(server.sentinel_config->post_monitor_cfg,freeSentinelLoadQueueEntry);1753}17541755/* destroy function for server.sentinel_config */1756void freeSentinelConfig(void) {1757    /* release these three config queues since we will not use it anymore */1758    listRelease(server.sentinel_config->pre_monitor_cfg);1759    listRelease(server.sentinel_config->monitor_cfg);1760    listRelease(server.sentinel_config->post_monitor_cfg);1761    zfree(server.sentinel_config);1762    server.sentinel_config = NULL;1763}17641765/* Search config name in pre monitor config name array, return 1 if found,1766 * 0 if not found. */1767int searchPreMonitorCfgName(const char *name) {1768    for (unsigned int i = 0; i < sizeof(preMonitorCfgName)/sizeof(preMonitorCfgName[0]); i++) {1769        if (!strcasecmp(preMonitorCfgName[i],name)) return 1;1770    }1771    return 0;1772}17731774/* free method for sentinelLoadQueueEntry when release the list */1775void freeSentinelLoadQueueEntry(void *item) {1776    struct sentinelLoadQueueEntry *entry = item;1777    sdsfreesplitres(entry->argv,entry->argc);1778    sdsfree(entry->line);1779    zfree(entry);1780}17811782/* This function is used for queuing sentinel configuration, the main1783 * purpose of this function is to delay parsing the sentinel config option1784 * in order to avoid the order dependent issue from the config. */1785void queueSentinelConfig(sds *argv, int argc, int linenum, sds line) {1786    int i;1787    struct sentinelLoadQueueEntry *entry;17881789    /* initialize sentinel_config for the first call */1790    if (server.sentinel_config == NULL) initializeSentinelConfig();17911792    entry = zmalloc(sizeof(struct sentinelLoadQueueEntry));1793    entry->argv = zmalloc(sizeof(char*)*argc);1794    entry->argc = argc;1795    entry->linenum = linenum;1796    entry->line = sdsdup(line);1797    for (i = 0; i < argc; i++) {1798        entry->argv[i] = sdsdup(argv[i]);1799    }1800    /*  Separate config lines with pre monitor config, monitor config and1801     *  post monitor config, in order to parsing config dependencies1802     *  correctly. */1803    if (!strcasecmp(argv[0],"monitor")) {1804        listAddNodeTail(server.sentinel_config->monitor_cfg,entry);1805    } else if (searchPreMonitorCfgName(argv[0])) {1806        listAddNodeTail(server.sentinel_config->pre_monitor_cfg,entry);1807    } else{1808        listAddNodeTail(server.sentinel_config->post_monitor_cfg,entry);1809    }1810}18111812/* This function is used for loading the sentinel configuration from1813 * pre_monitor_cfg, monitor_cfg and post_monitor_cfg list */1814void loadSentinelConfigFromQueue(void) {1815    const char *err = NULL;1816    listIter li;1817    listNode *ln;1818    int linenum = 0;1819    sds line = NULL;1820    unsigned int j;18211822    /* if there is no sentinel_config entry, we can return immediately */1823    if (server.sentinel_config == NULL) return;18241825    list *sentinel_configs[3] = {1826        server.sentinel_config->pre_monitor_cfg,1827        server.sentinel_config->monitor_cfg,1828        server.sentinel_config->post_monitor_cfg1829    };1830    /* loading from pre monitor config queue first to avoid dependency issues1831     * loading from monitor config queue1832     * loading from the post monitor config queue */1833    for (j = 0; j < sizeof(sentinel_configs) / sizeof(sentinel_configs[0]); j++) {1834        listRewind(sentinel_configs[j],&li);1835        while((ln = listNext(&li))) {1836            struct sentinelLoadQueueEntry *entry = ln->value;1837            err = sentinelHandleConfiguration(entry->argv,entry->argc);1838            if (err) {1839                linenum = entry->linenum;1840                line = entry->line;1841                goto loaderr;1842            }1843        }1844    }18451846    /* free sentinel_config when config loading is finished */1847    freeSentinelConfig();1848    return;18491850loaderr:1851    fprintf(stderr, "\n*** FATAL CONFIG FILE ERROR (Redis %s) ***\n",1852        REDIS_VERSION);1853    fprintf(stderr, "Reading the configuration file, at line %d\n", linenum);1854    fprintf(stderr, ">>> '%s'\n", line);1855    fprintf(stderr, "%s\n", err);1856    exit(1);1857}18581859const char *sentinelHandleConfiguration(char **argv, int argc) {18601861    sentinelRedisInstance *ri;18621863    if (!strcasecmp(argv[0],"monitor") && argc == 5) {1864        /* monitor <name> <host> <port> <quorum> */1865        int quorum = atoi(argv[4]);18661867        if (quorum <= 0) return "Quorum must be 1 or greater.";1868        if (createSentinelRedisInstance(argv[1],SRI_MASTER,argv[2],1869                                        atoi(argv[3]),quorum,NULL) == NULL)1870        {1871            return sentinelCheckCreateInstanceErrors(SRI_MASTER);1872        }1873    } else if (!strcasecmp(argv[0],"down-after-milliseconds") && argc == 3) {1874        /* down-after-milliseconds <name> <milliseconds> */1875        ri = sentinelGetMasterByName(argv[1]);1876        if (!ri) return "No such master with specified name.";1877        ri->down_after_period = atoi(argv[2]);1878        if (ri->down_after_period <= 0)1879            return "negative or zero time parameter.";1880        sentinelPropagateDownAfterPeriod(ri);1881    } else if (!strcasecmp(argv[0],"failover-timeout") && argc == 3) {1882        /* failover-timeout <name> <milliseconds> */1883        ri = sentinelGetMasterByName(argv[1]);1884        if (!ri) return "No such master with specified name.";1885        ri->failover_timeout = atoi(argv[2]);1886        if (ri->failover_timeout <= 0)1887            return "negative or zero time parameter.";1888    } else if (!strcasecmp(argv[0],"parallel-syncs") && argc == 3) {1889        /* parallel-syncs <name> <milliseconds> */1890        ri = sentinelGetMasterByName(argv[1]);1891        if (!ri) return "No such master with specified name.";1892        ri->parallel_syncs = atoi(argv[2]);1893    } else if (!strcasecmp(argv[0],"notification-script") && argc == 3) {1894        /* notification-script <name> <path> */1895        ri = sentinelGetMasterByName(argv[1]);1896        if (!ri) return "No such master with specified name.";1897        if (access(argv[2],X_OK) == -1)1898            return "Notification script seems non existing or non executable.";1899        ri->notification_script = sdsnew(argv[2]);1900    } else if (!strcasecmp(argv[0],"client-reconfig-script") && argc == 3) {1901        /* client-reconfig-script <name> <path> */1902        ri = sentinelGetMasterByName(argv[1]);1903        if (!ri) return "No such master with specified name.";1904        if (access(argv[2],X_OK) == -1)1905            return "Client reconfiguration script seems non existing or "1906                   "non executable.";1907        ri->client_reconfig_script = sdsnew(argv[2]);1908    } else if (!strcasecmp(argv[0],"auth-pass") && argc == 3) {1909        /* auth-pass <name> <password> */1910        ri = sentinelGetMasterByName(argv[1]);1911        if (!ri) return "No such master with specified name.";1912        ri->auth_pass = sdsnew(argv[2]);1913    } else if (!strcasecmp(argv[0],"auth-user") && argc == 3) {1914        /* auth-user <name> <username> */1915        ri = sentinelGetMasterByName(argv[1]);1916        if (!ri) return "No such master with specified name.";1917        ri->auth_user = sdsnew(argv[2]);1918    } else if (!strcasecmp(argv[0],"current-epoch") && argc == 2) {1919        /* current-epoch <epoch> */1920        unsigned long long current_epoch = strtoull(argv[1],NULL,10);1921        if (current_epoch > sentinel.current_epoch)1922            sentinel.current_epoch = current_epoch;1923    } else if (!strcasecmp(argv[0],"myid") && argc == 2) {1924        if (strlen(argv[1]) != CONFIG_RUN_ID_SIZE)1925            return "Malformed Sentinel id in myid option.";1926        memcpy(sentinel.myid,argv[1],CONFIG_RUN_ID_SIZE);1927    } else if (!strcasecmp(argv[0],"config-epoch") && argc == 3) {1928        /* config-epoch <name> <epoch> */1929        ri = sentinelGetMasterByName(argv[1]);1930        if (!ri) return "No such master with specified name.";1931        ri->config_epoch = strtoull(argv[2],NULL,10);1932        /* The following update of current_epoch is not really useful as1933         * now the current epoch is persisted on the config file, but1934         * we leave this check here for redundancy. */1935        if (ri->config_epoch > sentinel.current_epoch)1936            sentinel.current_epoch = ri->config_epoch;1937    } else if (!strcasecmp(argv[0],"leader-epoch") && argc == 3) {1938        /* leader-epoch <name> <epoch> */1939        ri = sentinelGetMasterByName(argv[1]);1940        if (!ri) return "No such master with specified name.";1941        ri->leader_epoch = strtoull(argv[2],NULL,10);1942    } else if ((!strcasecmp(argv[0],"known-slave") ||1943                !strcasecmp(argv[0],"known-replica")) && argc == 4)1944    {1945        sentinelRedisInstance *slave;19461947        /* known-replica <name> <ip> <port> */1948        ri = sentinelGetMasterByName(argv[1]);1949        if (!ri) return "No such master with specified name.";1950        if ((slave = createSentinelRedisInstance(NULL,SRI_SLAVE,argv[2],1951                    atoi(argv[3]), ri->quorum, ri)) == NULL)1952        {1953            return sentinelCheckCreateInstanceErrors(SRI_SLAVE);1954        }1955    } else if (!strcasecmp(argv[0],"known-sentinel") &&1956               (argc == 4 || argc == 5)) {1957        sentinelRedisInstance *si;19581959        if (argc == 5) { /* Ignore the old form without runid. */1960            /* known-sentinel <name> <ip> <port> [runid] */1961            ri = sentinelGetMasterByName(argv[1]);1962            if (!ri) return "No such master with specified name.";1963            if ((si = createSentinelRedisInstance(argv[4],SRI_SENTINEL,argv[2],1964                        atoi(argv[3]), ri->quorum, ri)) == NULL)1965            {1966                return sentinelCheckCreateInstanceErrors(SRI_SENTINEL);1967            }1968            si->runid = sdsnew(argv[4]);1969            sentinelTryConnectionSharing(si);1970        }1971    } else if (!strcasecmp(argv[0],"rename-command") && argc == 4) {1972        /* rename-command <name> <command> <renamed-command> */1973        ri = sentinelGetMasterByName(argv[1]);1974        if (!ri) return "No such master with specified name.";1975        sds oldcmd = sdsnew(argv[2]);1976        sds newcmd = sdsnew(argv[3]);1977        if (dictAdd(ri->renamed_commands,oldcmd,newcmd) != DICT_OK) {1978            sdsfree(oldcmd);1979            sdsfree(newcmd);1980            return "Same command renamed multiple times with rename-command.";1981        }1982    } else if (!strcasecmp(argv[0],"announce-ip") && argc == 2) {1983        /* announce-ip <ip-address> */1984        if (strlen(argv[1]))1985            sentinel.announce_ip = sdsnew(argv[1]);1986    } else if (!strcasecmp(argv[0],"announce-port") && argc == 2) {1987        /* announce-port <port> */1988        sentinel.announce_port = atoi(argv[1]);1989    } else if (!strcasecmp(argv[0],"deny-scripts-reconfig") && argc == 2) {1990        /* deny-scripts-reconfig <yes|no> */1991        if ((sentinel.deny_scripts_reconfig = yesnotoi(argv[1])) == -1) {1992            return "Please specify yes or no for the "1993                   "deny-scripts-reconfig options.";1994        }1995    } else if (!strcasecmp(argv[0],"sentinel-user") && argc == 2) {1996        /* sentinel-user <user-name> */1997        if (strlen(argv[1]))1998            sentinel.sentinel_auth_user = sdsnew(argv[1]);1999    } else if (!strcasecmp(argv[0],"sentinel-pass") && argc == 2) {2000        /* sentinel-pass <password> */

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.