@Test
  public void testRemoveNode() throws Exception {
    waitForFullMesh(2000);
    IStoreClient<String, String> client0 =
        syncManagers[0].getStoreClient("global", String.class, String.class);
    IStoreClient<String, String> client1 =
        syncManagers[1].getStoreClient("global", String.class, String.class);
    IStoreClient<String, String> client2 =
        syncManagers[2].getStoreClient("global", String.class, String.class);

    client0.put("key", "value");
    waitForValue(client1, "key", "value", 2000, "client1");

    nodes.remove(0);
    nodeString = mapper.writeValueAsString(nodes);

    SyncManager oldNode = syncManagers[0];
    syncManagers = Arrays.copyOfRange(syncManagers, 1, 4);
    moduleContexts = Arrays.copyOfRange(moduleContexts, 1, 4);

    try {
      for (int i = 0; i < syncManagers.length; i++) {
        moduleContexts[i].addConfigParam(syncManagers[i], "nodes", nodeString);
        syncManagers[i].doUpdateConfiguration();
        waitForConnection(syncManagers[i], (short) 1, false, 2000);
      }
    } finally {
      oldNode.shutdown();
    }
    waitForFullMesh(2000);

    client1.put("newkey", "newvalue");
    waitForValue(client2, "key", "value", 2000, "client4");
    waitForValue(client2, "newkey", "newvalue", 2000, "client0");
  }
  @Test
  public void testLeaderElection() throws Exception {
    // Check we end up with master nodes
    HashMap<Short, Short> leaderMap = checkElectionState(syncManagers, 5000);

    // Kill the masters and verify failover
    ArrayList<SyncManager> live = new ArrayList<>();
    for (SyncManager m : syncManagers) {
      ClusterNode n = m.getClusterConfig().getNode();
      if (leaderMap.get(n.getDomainId()).equals(n.getNodeId())) m.shutdown();
      else live.add(m);
    }
    checkElectionState(live.toArray(new SyncManager[0]), 5000);
  }
  private HashMap<Short, Short> checkElectionState(SyncManager[] syncManagers, int maxTime)
      throws Exception {
    long then = System.currentTimeMillis();

    HashSet<Short> domainSet = new HashSet<>();
    HashSet<Short> nodeSet = new HashSet<>();
    HashMap<Short, Short> leaderMap = new HashMap<>();
    while (true) {
      leaderMap.clear();
      domainSet.clear();
      nodeSet.clear();

      boolean converged = true;
      for (SyncManager s : syncManagers) {
        ClusterNode n = s.getClusterConfig().getNode();
        Short domainId = n.getDomainId();
        domainSet.add(domainId);
        nodeSet.add(n.getNodeId());
        Short l = s.clusterMgr.getDomainLeader();
        if (l != null) {
          if (leaderMap.containsKey(domainId)) {
            assertEquals(l, leaderMap.get(domainId));
          }
          leaderMap.put(domainId, l);
        } else {
          converged = false;
        }
      }

      for (Short l : leaderMap.values()) {
        if (!nodeSet.contains(l)) {
          converged = false;
          break;
        }
      }
      if (converged) break;

      Thread.sleep(100);
      assertTrue(then + maxTime > System.currentTimeMillis());
    }
    for (Short did : domainSet) {
      assertTrue(leaderMap.containsKey(did));
      assertTrue(nodeSet.contains(leaderMap.get(did)));
    }
    return leaderMap;
  }
  protected void setupSyncManager(
      FloodlightModuleContext fmc, SyncManager syncManager, ClusterNode thisNode) throws Exception {
    fmc.addService(IThreadPoolService.class, tp);
    fmc.addService(IDebugCounterService.class, new NullDebugCounter());
    fmc.addConfigParam(syncManager, "configProviders", PropertyCCProvider.class.getName());
    fmc.addConfigParam(syncManager, "nodes", nodeString);
    fmc.addConfigParam(syncManager, "thisNode", "" + thisNode.getNodeId());
    fmc.addConfigParam(syncManager, "persistenceEnabled", "false");
    fmc.addConfigParam(syncManager, "authScheme", "CHALLENGE_RESPONSE");
    fmc.addConfigParam(syncManager, "keyStorePath", keyStoreFile.getAbsolutePath());
    fmc.addConfigParam(syncManager, "keyStorePassword", keyStorePassword);
    tp.init(fmc);
    syncManager.init(fmc);

    tp.startUp(fmc);
    syncManager.startUp(fmc);

    syncManager.registerStore("global", Scope.GLOBAL);
    syncManager.registerStore("local", Scope.LOCAL);
  }