MockClusterTopology.java

package redis.clients.jedis.util.server;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

import redis.clients.jedis.HostAndPort;
import redis.clients.jedis.Protocol;

/**
 * The slot table a set of mock cluster nodes agree on: who owns each of the 16384 slots. Shared by
 * every {@link ClusterSlotsResponder} of one mock cluster so that {@code CLUSTER SLOTS} replies and
 * {@code MOVED} redirects stay consistent. Tests mutate it to simulate a migration that completed
 * on the server side, then announce it with an SMIGRATED push.
 */
public final class MockClusterTopology {

  private final HostAndPort[] owners = new HostAndPort[Protocol.CLUSTER_HASHSLOTS];

  /** Assigns the inclusive slot range to {@code master}, replacing any previous owner. */
  public synchronized MockClusterTopology assign(int from, int to, HostAndPort master) {
    for (int slot = from; slot <= to; slot++) {
      owners[slot] = master;
    }
    return this;
  }

  public synchronized HostAndPort ownerOf(int slot) {
    return owners[slot];
  }

  /**
   * The {@code CLUSTER SLOTS} reply for the current table: one {@code [from, to, [host, port, id]]}
   * entry per maximal run of slots with the same owner, unassigned runs omitted.
   */
  public synchronized List<Object> clusterSlotsReply() {
    List<Object> ranges = new ArrayList<>();
    int start = 0;
    for (int slot = 1; slot <= owners.length; slot++) {
      if (slot == owners.length || !Objects.equals(owners[slot], owners[start])) {
        if (owners[start] != null) {
          ranges.add(range(start, slot - 1, owners[start]));
        }
        start = slot;
      }
    }
    return ranges;
  }

  private static List<Object> range(int from, int to, HostAndPort master) {
    List<Object> node = Arrays.<Object> asList(master.getHost(), (long) master.getPort(),
      nodeId(master));
    return Arrays.<Object> asList((long) from, (long) to, node);
  }

  /** A stable, unique node id derived from the address (Jedis only reads host and port). */
  public static String nodeId(HostAndPort node) {
    return "mock-node-" + node.getHost().replace('.', '-') + "-" + node.getPort();
  }
}