]> git.saurik.com Git - redis.git/blobdiff - src/cluster.c
Variadic ZADD
[redis.git] / src / cluster.c
index b3548755370536e084d274e4e9ed4b0ece99c7db..3e65af7899e05f071585437ed5ac42ba5995193b 100644 (file)
@@ -24,12 +24,10 @@ void clusterGetRandomName(char *p) {
     char *charset = "0123456789abcdef";
     int j;
 
-    if (!fp) {
-        redisLog(REDIS_WARNING,
-            "Unrecovarable error: can't open /dev/urandom:%s" ,strerror(errno));
-        exit(1);
+    if (fp == NULL || fread(p,REDIS_CLUSTER_NAMELEN,1,fp) == 0) {
+        for (j = 0; j < REDIS_CLUSTER_NAMELEN; j++)
+            p[j] = rand();
     }
-    fread(p,REDIS_CLUSTER_NAMELEN,1,fp);
     for (j = 0; j < REDIS_CLUSTER_NAMELEN; j++)
         p[j] = charset[p[j] & 0x0F];
     fclose(fp);
@@ -115,7 +113,30 @@ int clusterLoadConfig(char *filename) {
         for (j = 7; j < argc; j++) {
             int start, stop;
 
-            if ((p = strchr(argv[j],'-')) != NULL) {
+            if (argv[j][0] == '[') {
+                /* Here we handle migrating / importing slots */
+                int slot;
+                char direction;
+                clusterNode *cn;
+
+                p = strchr(argv[j],'-');
+                redisAssert(p != NULL);
+                *p = '\0';
+                direction = p[1]; /* Either '>' or '<' */
+                slot = atoi(argv[j]+1);
+                p += 3;
+                cn = clusterLookupNode(p);
+                if (!cn) {
+                    cn = createClusterNode(p,0);
+                    clusterAddNode(cn);
+                }
+                if (direction == '>') {
+                    server.cluster.migrating_slots_to[slot] = cn;
+                } else {
+                    server.cluster.importing_slots_from[slot] = cn;
+                }
+                continue;
+            } else if ((p = strchr(argv[j],'-')) != NULL) {
                 *p = '\0';
                 start = atoi(argv[j]);
                 stop = atoi(p+1);
@@ -993,11 +1014,24 @@ int clusterNodeGetSlotBit(clusterNode *n, int slot) {
  * If the slot is already assigned to another instance this is considered
  * an error and REDIS_ERR is returned. */
 int clusterAddSlot(clusterNode *n, int slot) {
-    redisAssert(clusterNodeSetSlotBit(n,slot) == 0);
+    if (clusterNodeSetSlotBit(n,slot) != 0)
+        return REDIS_ERR;
     server.cluster.slots[slot] = n;
     return REDIS_OK;
 }
 
+/* Delete the specified slot marking it as unassigned.
+ * Returns REDIS_OK if the slot was assigned, otherwise if the slot was
+ * already unassigned REDIS_ERR is returned. */
+int clusterDelSlot(int slot) {
+    clusterNode *n = server.cluster.slots[slot];
+
+    if (!n) return REDIS_ERR;
+    redisAssert(clusterNodeClearSlotBit(n,slot) == 1);
+    server.cluster.slots[slot] = NULL;
+    return REDIS_OK;
+}
+
 /* -----------------------------------------------------------------------------
  * Cluster state evaluation function
  * -------------------------------------------------------------------------- */
@@ -1086,12 +1120,39 @@ sds clusterGenNodesDescription(void) {
                 start = -1;
             }
         }
+
+        /* Just for MYSELF node we also dump info about slots that
+         * we are migrating to other instances or importing from other
+         * instances. */
+        if (node->flags & REDIS_NODE_MYSELF) {
+            for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {
+                if (server.cluster.migrating_slots_to[j]) {
+                    ci = sdscatprintf(ci," [%d->-%.40s]",j,
+                        server.cluster.migrating_slots_to[j]->name);
+                } else if (server.cluster.importing_slots_from[j]) {
+                    ci = sdscatprintf(ci," [%d-<-%.40s]",j,
+                        server.cluster.importing_slots_from[j]->name);
+                }
+            }
+        }
         ci = sdscatlen(ci,"\n",1);
     }
     dictReleaseIterator(di);
     return ci;
 }
 
+int getSlotOrReply(redisClient *c, robj *o) {
+    long long slot;
+
+    if (getLongLongFromObject(o,&slot) != REDIS_OK ||
+        slot < 0 || slot > REDIS_CLUSTER_SLOTS)
+    {
+        addReplyError(c,"Invalid or out of range slot");
+        return -1;
+    }
+    return (int) slot;
+}
+
 void clusterCommand(redisClient *c) {
     if (server.cluster_enabled == 0) {
         addReplyError(c,"This instance has cluster support disabled");
@@ -1129,24 +1190,26 @@ void clusterCommand(redisClient *c) {
         o = createObject(REDIS_STRING,ci);
         addReplyBulk(c,o);
         decrRefCount(o);
-    } else if (!strcasecmp(c->argv[1]->ptr,"addslots") && c->argc >= 3) {
-        int j;
-        long long slot;
+    } else if ((!strcasecmp(c->argv[1]->ptr,"addslots") ||
+               !strcasecmp(c->argv[1]->ptr,"delslots")) && c->argc >= 3) {
+        int j, slot;
         unsigned char *slots = zmalloc(REDIS_CLUSTER_SLOTS);
+        int del = !strcasecmp(c->argv[1]->ptr,"delslots");
 
         memset(slots,0,REDIS_CLUSTER_SLOTS);
         /* Check that all the arguments are parsable and that all the
          * slots are not already busy. */
         for (j = 2; j < c->argc; j++) {
-            if (getLongLongFromObject(c->argv[j],&slot) != REDIS_OK ||
-                slot < 0 || slot > REDIS_CLUSTER_SLOTS)
-            {
-                addReplyError(c,"Invalid or out of range slot index");
+            if ((slot = getSlotOrReply(c,c->argv[j])) == -1) {
                 zfree(slots);
                 return;
             }
-            if (server.cluster.slots[slot]) {
-                addReplyErrorFormat(c,"Slot %lld is already busy", slot);
+            if (del && server.cluster.slots[slot] == NULL) {
+                addReplyErrorFormat(c,"Slot %d is already unassigned", slot);
+                zfree(slots);
+                return;
+            } else if (!del && server.cluster.slots[slot]) {
+                addReplyErrorFormat(c,"Slot %d is already busy", slot);
                 zfree(slots);
                 return;
             }
@@ -1159,8 +1222,15 @@ void clusterCommand(redisClient *c) {
         }
         for (j = 0; j < REDIS_CLUSTER_SLOTS; j++) {
             if (slots[j]) {
-                int retval = clusterAddSlot(server.cluster.myself,j);
-                
+                int retval;
+
+                /* If this slot was set as importing we can clear this 
+                 * state as now we are the real owner of the slot. */
+                if (server.cluster.importing_slots_from[j])
+                    server.cluster.importing_slots_from[j] = NULL;
+
+                retval = del ? clusterDelSlot(j) :
+                               clusterAddSlot(server.cluster.myself,j);
                 redisAssert(retval == REDIS_OK);
             }
         }
@@ -1168,6 +1238,79 @@ void clusterCommand(redisClient *c) {
         clusterUpdateState();
         clusterSaveConfigOrDie();
         addReply(c,shared.ok);
+    } else if (!strcasecmp(c->argv[1]->ptr,"setslot") && c->argc >= 4) {
+        /* SETSLOT 10 MIGRATING <instance ID> */
+        /* SETSLOT 10 IMPORTING <instance ID> */
+        /* SETSLOT 10 STABLE */
+        int slot;
+        clusterNode *n;
+
+        if ((slot = getSlotOrReply(c,c->argv[2])) == -1) return;
+
+        if (!strcasecmp(c->argv[3]->ptr,"migrating") && c->argc == 5) {
+            if (server.cluster.slots[slot] != server.cluster.myself) {
+                addReplyErrorFormat(c,"I'm not the owner of hash slot %u",slot);
+                return;
+            }
+            if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
+                addReplyErrorFormat(c,"I don't know about node %s",
+                    (char*)c->argv[4]->ptr);
+                return;
+            }
+            server.cluster.migrating_slots_to[slot] = n;
+        } else if (!strcasecmp(c->argv[3]->ptr,"importing") && c->argc == 5) {
+            if (server.cluster.slots[slot] == server.cluster.myself) {
+                addReplyErrorFormat(c,
+                    "I'm already the owner of hash slot %u",slot);
+                return;
+            }
+            if ((n = clusterLookupNode(c->argv[4]->ptr)) == NULL) {
+                addReplyErrorFormat(c,"I don't know about node %s",
+                    (char*)c->argv[3]->ptr);
+                return;
+            }
+            server.cluster.importing_slots_from[slot] = n;
+        } else if (!strcasecmp(c->argv[3]->ptr,"stable") && c->argc == 4) {
+            /* CLUSTER SETSLOT <SLOT> STABLE */
+            server.cluster.importing_slots_from[slot] = NULL;
+            server.cluster.migrating_slots_to[slot] = NULL;
+        } else if (!strcasecmp(c->argv[3]->ptr,"node") && c->argc == 4) {
+            /* CLUSTER SETSLOT <SLOT> NODE <NODE ID> */
+            clusterNode *n = clusterLookupNode(c->argv[4]->ptr);
+
+            if (!n) addReplyErrorFormat(c,"Unknown node %s",
+                (char*)c->argv[4]->ptr);
+            /* If this hash slot was served by 'myself' before to switch
+             * make sure there are no longer local keys for this hash slot. */
+            if (server.cluster.slots[slot] == server.cluster.myself &&
+                n != server.cluster.myself)
+            {
+                int numkeys;
+                robj **keys;
+
+                keys = zmalloc(sizeof(robj*)*1);
+                numkeys = GetKeysInSlot(slot, keys, 1);
+                zfree(keys);
+                if (numkeys == 0) {
+                    addReplyErrorFormat(c, "Can't assign hashslot %d to a different node while I still hold keys for this hash slot.", slot);
+                    return;
+                }
+            }
+            /* If this node was the slot owner and the slot was marked as
+             * migrating, assigning the slot to another node will clear
+             * the migratig status. */
+            if (server.cluster.slots[slot] == server.cluster.myself &&
+                server.cluster.migrating_slots_to[slot])
+                server.cluster.migrating_slots_to[slot] = NULL;
+
+            clusterDelSlot(slot);
+            clusterAddSlot(n,slot);
+        } else {
+            addReplyError(c,"Invalid CLUSTER SETSLOT action or number of arguments");
+            return;
+        }
+        clusterSaveConfigOrDie();
+        addReply(c,shared.ok);
     } else if (!strcasecmp(c->argv[1]->ptr,"info") && c->argc == 2) {
         char *statestr[] = {"ok","fail","needhelp"};
         int slots_assigned = 0, slots_ok = 0, slots_pfail = 0, slots_fail = 0;
@@ -1205,6 +1348,30 @@ void clusterCommand(redisClient *c) {
             (unsigned long)sdslen(info)));
         addReplySds(c,info);
         addReply(c,shared.crlf);
+    } else if (!strcasecmp(c->argv[1]->ptr,"keyslot") && c->argc == 3) {
+        sds key = c->argv[2]->ptr;
+
+        addReplyLongLong(c,keyHashSlot(key,sdslen(key)));
+    } else if (!strcasecmp(c->argv[1]->ptr,"getkeysinslot") && c->argc == 4) {
+        long long maxkeys, slot;
+        unsigned int numkeys, j;
+        robj **keys;
+
+        if (getLongLongFromObjectOrReply(c,c->argv[2],&slot,NULL) != REDIS_OK)
+            return;
+        if (getLongLongFromObjectOrReply(c,c->argv[3],&maxkeys,NULL) != REDIS_OK)
+            return;
+        if (slot < 0 || slot >= REDIS_CLUSTER_SLOTS || maxkeys < 0 ||
+            maxkeys > 1024*1024) {
+            addReplyError(c,"Invalid slot or number of keys");
+            return;
+        }
+
+        keys = zmalloc(sizeof(robj*)*maxkeys);
+        numkeys = GetKeysInSlot(slot, keys, maxkeys);
+        addReplyMultiBulkLen(c,numkeys);
+        for (j = 0; j < numkeys; j++) addReplyBulk(c,keys[j]);
+        zfree(keys);
     } else {
         addReplyError(c,"Wrong CLUSTER subcommand or number of arguments");
     }
@@ -1505,12 +1672,19 @@ file_rd_err:
 /* Return the pointer to the cluster node that is able to serve the query
  * as all the keys belong to hash slots for which the node is in charge.
  *
- * If keys in query spawn multiple nodes NULL is returned. */
-clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot) {
+ * If the returned node should be used only for this request, the *ask
+ * integer is set to '1', otherwise to '0'. This is used in order to
+ * let the caller know if we should reply with -MOVED or with -ASK.
+ *
+ * If the request contains more than a single key NULL is returned,
+ * however a request with more then a key argument where the key is always
+ * the same is valid, like in: RPOPLPUSH mylist mylist.*/
+clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **argv, int argc, int *hashslot, int *ask) {
     clusterNode *n = NULL;
+    robj *firstkey = NULL;
     multiState *ms, _ms;
     multiCmd mc;
-    int i;
+    int i, slot = 0;
 
     /* We handle all the cases as if they were EXEC commands, so we have
      * a common code path for everything */
@@ -1520,7 +1694,9 @@ clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **arg
         if (!(c->flags & REDIS_MULTI)) return server.cluster.myself;
         ms = &c->mstate;
     } else {
-        /* Create a fake Multi State structure, with just one command */
+        /* In order to have a single codepath create a fake Multi State
+         * structure if the client is not in MULTI/EXEC state, this way
+         * we have a single codepath below. */
         ms = &_ms;
         _ms.commands = &mc;
         _ms.count = 1;
@@ -1529,6 +1705,8 @@ clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **arg
         mc.cmd = cmd;
     }
 
+    /* Check that all the keys are the same key, and get the slot and
+     * node for this key. */
     for (i = 0; i < ms->count; i++) {
         struct redisCommand *mcmd;
         robj **margv;
@@ -1539,26 +1717,50 @@ clusterNode *getNodeByQuery(redisClient *c, struct redisCommand *cmd, robj **arg
         margv = ms->commands[i].argv;
 
         keyindex = getKeysFromCommand(mcmd,margv,margc,&numkeys,
-                                      REDIS_GETKEYS_PRELOAD);
+                                      REDIS_GETKEYS_ALL);
         for (j = 0; j < numkeys; j++) {
-            int slot = keyHashSlot((char*)margv[keyindex[j]]->ptr,
-                sdslen(margv[keyindex[j]]->ptr));
-            struct clusterNode *slotnode;
-
-            slotnode = server.cluster.slots[slot];
-            if (hashslot) *hashslot = slot;
-            /* Node not assigned? (Should never happen actually
-             * if we reached this function).
-             * Different node than the previous one?
-             * Return NULL, the cluster can't serve multi-node requests */
-            if (slotnode == NULL || (n && slotnode != n)) {
-                getKeysFreeResult(keyindex);
-                return NULL;
+            if (firstkey == NULL) {
+                /* This is the first key we see. Check what is the slot
+                 * and node. */
+                firstkey = margv[keyindex[j]];
+
+                slot = keyHashSlot((char*)firstkey->ptr, sdslen(firstkey->ptr));
+                n = server.cluster.slots[slot];
+                redisAssert(n != NULL);
             } else {
-                n = slotnode;
+                /* If it is not the first key, make sure it is exactly
+                 * the same key as the first we saw. */
+                if (!equalStringObjects(firstkey,margv[keyindex[j]])) {
+                    decrRefCount(firstkey);
+                    getKeysFreeResult(keyindex);
+                    return NULL;
+                }
             }
         }
         getKeysFreeResult(keyindex);
     }
-    return (n == NULL) ? server.cluster.myself : n;
+    if (ask) *ask = 0; /* This is the default. Set to 1 if needed later. */
+    /* No key at all in command? then we can serve the request
+     * without redirections. */
+    if (n == NULL) return server.cluster.myself;
+    if (hashslot) *hashslot = slot;
+    /* This request is about a slot we are migrating into another instance?
+     * Then we need to check if we have the key. If we have it we can reply.
+     * If instead is a new key, we pass the request to the node that is
+     * receiving the slot. */
+    if (n == server.cluster.myself &&
+        server.cluster.migrating_slots_to[slot] != NULL)
+    {
+        if (lookupKeyRead(&server.db[0],firstkey) == NULL) {
+            if (ask) *ask = 1;
+            return server.cluster.migrating_slots_to[slot];
+        }
+    }
+    /* Handle the case in which we are receiving this hash slot from
+     * another instance, so we'll accept the query even if in the table
+     * it is assigned to a different node. */
+    if (server.cluster.importing_slots_from[slot] != NULL)
+        return server.cluster.myself;
+    /* It's not a -ASK case. Base case: just return the right node. */
+    return n;
 }