/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2008-2010 Sun Microsystems, Inc.
 *      Portions Copyright 2013-2015 ForgeRock AS.
 */
package org.opends.server.replication.service;

import java.util.*;
import java.util.Map.Entry;

import org.assertj.core.api.Assertions;
import org.assertj.core.data.MapEntry;
import org.forgerock.i18n.LocalizableMessage;
import org.forgerock.i18n.slf4j.LocalizedLogger;
import org.opends.server.replication.ReplicationTestCase;
import org.opends.server.replication.common.CSN;
import org.opends.server.replication.common.RSInfo;
import org.opends.server.replication.common.ServerState;
import org.opends.server.replication.protocol.ReplServerStartMsg;
import org.opends.server.replication.server.ReplicationServer;
import org.opends.server.replication.service.ReplicationBroker.RSEvaluations;
import org.opends.server.replication.service.ReplicationBroker.ReplicationServerInfo;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import static java.util.Collections.*;

import static org.assertj.core.data.MapEntry.*;
import static org.opends.messages.ReplicationMessages.*;
import static org.opends.server.replication.service.ReplicationBroker.*;
import static org.opends.server.util.CollectionUtils.*;
import static org.testng.Assert.*;

/**
 * Test the algorithm for finding the best replication server among the
 * configured ones.
 */
@SuppressWarnings("javadoc")
public class ComputeBestServerTest extends ReplicationTestCase
{

  /** The tracer object for the debug logger. */
  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();

  // definitions for server ids
  private static final int myId1 = 1;
  private static final int myId2 = 2;
  private static final int myId3 = 3;

  // definitions for server names
  private static final String WINNER = "winner:389";
  private static final String LOOSER1 = "looser1:389";
  private static final String LOOSER2 = "looser2:389";

  private void debugInfo(String s)
  {
    logger.error(LocalizableMessage.raw(s));
    if (logger.isTraceEnabled())
    {
      logger.trace("** TEST **" + s);
    }
  }

  private ServerState newServerState(CSN... csns)
  {
    ServerState result = new ServerState();
    for (CSN csn : csns)
    {
      result.update(csn);
    }
    return result;
  }

  /**
   * Test with one replication server, nobody has a CSN (simulates) very first
   * connection.
   */
  @Test
  public void testNullCSNBoth() throws Exception
  {
    String testCase = "testNullCSNBoth";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState();
    ServerState aState = newServerState();

    Map<Integer, ReplicationServerInfo> rsInfos =
        newRSInfos(newRSInfo(11, WINNER, aState, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(), entry(11, NOTE_BEST_RS.ordinal()));
  }

  private Map<Integer, ReplicationServerInfo> newRSInfos(
      ReplicationServerInfo... rsInfos)
  {
    Map<Integer, ReplicationServerInfo> results = new HashMap<>();
    for (ReplicationServerInfo rsInfo : rsInfos)
    {
      results.put(rsInfo.getServerId(), rsInfo);
    }
    return results;
  }

  private ReplicationServerInfo newRSInfo(int serverId, String serverURL,
      ServerState state, long genId, int groupId)
  {
    return ReplicationServerInfo.newInstance(new ReplServerStartMsg(serverId,
        serverURL, null, 0, state, genId, false, (byte) groupId, 0));
  }

  /**
   * Test with one replication server, only replication server has a non null
   * CSN for ds server id.
   */
  @Test
  public void testNullCSNDS() throws Exception
  {
    String testCase = "testNullCSNDS";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState();
    ServerState aState = newServerState(new CSN(0, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos =
        newRSInfos(newRSInfo(11, WINNER, aState, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(), entry(11, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with one replication server, only ds server has a non null CSN for ds
   * server id but rs has a null one.
   */
  @Test
  public void testNullCSNRS() throws Exception
  {
    String testCase = "testNullCSNRS";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState = newServerState();

    Map<Integer, ReplicationServerInfo> rsInfos =
        newRSInfos(newRSInfo(11, WINNER, aState, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(), entry(11, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with one replication server, up to date.
   */
  @Test
  public void test1ServerUp() throws Exception
  {
    String testCase = "test1ServerUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState = newServerState(new CSN(1, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos =
        newRSInfos(newRSInfo(11, WINNER, aState, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(), entry(11, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with 2 replication servers, up to date.
   */
  @Test
  public void test2ServersUp() throws Exception
  {
    String testCase = "test2ServersUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(2, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 1),
        newRSInfo(12, WINNER, aState2, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(12, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with 2 replication servers, up to date, but 2 different group ids.
   */
  @Test
  public void testDiffGroup2ServersUp() throws Exception
  {
    String testCase = "testDiffGroup2ServersUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(2, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        // This server has less changes than the other one but it has the same
        // group id as us so he should be the winner
        newRSInfo(11, WINNER, aState1, 0, 1),
        newRSInfo(12, LOOSER1, aState2, 0, 2));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_BEST_RS.ordinal()),
        entry(12, NOTE_RS_HAS_DIFFERENT_GROUP_ID_THAN_DS.ordinal()));
  }

  private void containsOnly(final Map<Integer, LocalizableMessage> evaluations, MapEntry... entries)
  {
    final List<MapEntry> notFound = newArrayList(entries);
    for (Iterator<MapEntry> iter = notFound.iterator(); iter.hasNext();)
    {
      final MapEntry entry = iter.next();
      final LocalizableMessage reason = evaluations.get(entry.key);
      if (reason != null && reason.ordinal()==(Integer)entry.value)
      {
        iter.remove();
      }
    }
    if (!notFound.isEmpty())
    {
      final StringBuilder sb = new StringBuilder("expecting ordinals:\n");
      sb.append("  <").append(getOrdinal(evaluations)).append(">\n");
      sb.append("   to contain:\n");
      sb.append("  <").append(Arrays.asList(entries)).append(">\n");
      sb.append("   but could not find:\n");
      sb.append("  <").append(notFound).append(">");
      throw new AssertionError(sb.toString());
    }

    Assertions.assertThat(evaluations).hasSize(entries.length);
  }

  /** Contains ordinal for each message. */
  private Map<Integer, Integer> getOrdinal(Map<Integer, LocalizableMessage> evaluations)
  {
    final Map<Integer, Integer> result = new LinkedHashMap<>();
    for (Entry<Integer, LocalizableMessage> entry : evaluations.entrySet())
    {
      result.put(entry.getKey(), entry.getValue().ordinal());
    }
    return result;
  }

  /**
   * Test with 2 replication servers, none of them from our group id.
   */
  @Test
  public void testNotOurGroup() throws Exception
  {
    String testCase = "testNotOurGroup";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(2, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 2),
        newRSInfo(12, WINNER, aState2, 0, 2));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(12, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with 3 replication servers, up to date.
   */
  @Test
  public void test3ServersUp() throws Exception
  {
    String testCase = "test3ServersUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(2, 0, myId1));
    ServerState aState3 = newServerState(new CSN(3, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 1),
        newRSInfo(12, LOOSER2, aState2, 0, 1),
        newRSInfo(13, WINNER, aState3, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(12, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(13, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with 3 replication servers: 2 are up to date with the directory
   * server, 1 is more up to date than the directory server.
   */
  @Test
  public void test2ServersUpToDateAnd1EvenMoreUpToDate() throws Exception
  {
    String testCase = "test3ServersUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(1, 0, myId1));
    ServerState aState3 = newServerState(new CSN(2, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 1),
        newRSInfo(12, LOOSER2, aState2, 0, 1),
        newRSInfo(13, WINNER, aState3, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(12, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(13, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with 3 replication servers, up to date, but 2 different group ids.
   */
  @Test
  public void testDiffGroup3ServersUp() throws Exception
  {
    String testCase = "testDiffGroup3ServersUp";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState1 = newServerState(new CSN(1, 0, myId1));
    ServerState aState2 = newServerState(new CSN(2, 0, myId1));
    ServerState aState3 = newServerState(new CSN(3, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 1),
        newRSInfo(12, LOOSER2, aState2, 0, 2),
        // This server has less changes than looser2 but it has the same
        // group id as us so he should be the winner
        newRSInfo(13, WINNER, aState3, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte)1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal()),
        entry(12, NOTE_RS_HAS_DIFFERENT_GROUP_ID_THAN_DS.ordinal()),
        entry(13, NOTE_BEST_RS.ordinal()));
  }

  /**
   * Test with one replication server, late.
   */
  @Test
  public void test1ServerLate() throws Exception
  {
    String testCase = "test1ServerLate";
    debugInfo("Starting " + testCase);

    ServerState mySt = newServerState(new CSN(1, 0, myId1));
    ServerState aState = newServerState(new CSN(0, 0, myId1));

    Map<Integer, ReplicationServerInfo> rsInfos =
        newRSInfos(newRSInfo(11, WINNER, aState, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte) 1, 0);

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(), entry(11, NOTE_BEST_RS.ordinal()));
  }

  @DataProvider(name = "create3ServersData")
  public Object[][] create3ServersData() {
    return new Object[][] {
        // first RS is up to date, the others are late none is local
        { 4, 2, 3, false,
          1, 2, 3, false,
          2, 3, 4, false},

        // test that the local RS  is chosen first when all up to date
        { 4, 2, 3, true,
          4, 2, 3, false,
          4, 2, 3, false},

        // test that the local ServerID is more important than the others
        { 4, 0, 0, false,
          2, 100, 100, false,
          1, 100, 100, false},

        // test that a remote RS is chosen first when up to date when the local
        // one is late
        { 4, 1, 1, false,
          3, 1, 1, true,
          3, 1, 1, false},

        // test that the local RS is not chosen first when it is missing
        // local changes
        { 4, 1, 1, false,
          3, 2, 3, false,
          1, 1, 1, true},

        // test that a RS which is more up to date than the DS is chosen
        { 5, 1, 1, false,
          2, 0, 0, false,
          1, 1, 1, false},

        // test that a RS which is more up to date than the DS is chosen even
        // is some RS with the same last change from the DS
        { 5, 1, 1, false,
          4, 0, 0, false,
          4, 1, 1, false},

        // test that the local RS is chosen first when it is missing
        // the same local changes as the other RSs
        { 3, 1, 1, true,
          2, 1, 1, false,
          3, 1, 1, false},
        };
  }

  /**
   * Test with 3 replication servers (see data provider).
   */
  @Test(dataProvider =  "create3ServersData")
  public void test3Servers(
      long winnerT1, long winnerT2, long winnerT3, boolean winnerIsLocal,
      long looser1T1, long looser1T2, long looser1T3, boolean looser1IsLocal,
      long looser2T1, long looser2T2, long looser2T3, boolean looser2IsLocal)
      throws Exception
  {
    String testCase = "test3ServersLate";
    debugInfo("Starting " + testCase);

    // definitions for server names
    final String WINNER  = "localhost:123";
    final String LOOSER1 = "localhost:456";
    final String LOOSER2 = "localhost:789";

    // Create my state
    ServerState mySt = newServerState(
        new CSN(4, 0, myId1),
        new CSN(2, 0, myId2),  // myId2 should not be used inside algo (same for the other ServerStates)
        new CSN(3, 0, myId3)); // myId3 should not be used inside algo (same for the other ServerStates)

    // State for server 1
    ServerState aState1 = newServerState(
        new CSN(looser1T1, 0, myId1),
        new CSN(looser1T2, 0, myId2),
        new CSN(looser1T3, 0, myId3));
    if (looser1IsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(LOOSER1);
    }

    // State for server 2
    ServerState aState2 = newServerState(
        new CSN(winnerT1, 0, myId1),
        new CSN(winnerT2, 0, myId2),
        new CSN(winnerT3, 0, myId3));
    if (winnerIsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(WINNER);
    }

    // State for server 3
    ServerState aState3 = newServerState(
        new CSN(looser2T1, 0, myId1),
        new CSN(looser2T2, 0, myId2),
        new CSN(looser2T3, 0, myId3));
    if (looser2IsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(LOOSER2);
    }

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, 0, 1),
        newRSInfo(12, WINNER, aState2, 0, 1),
        newRSInfo(13, LOOSER2, aState3, 0, 1));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte) 1, 0);

    ReplicationServer.onlyForTestsClearLocalReplicationServerList();

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    final boolean winnerIsLatestRS = winnerT1 > 4 && looser1T1 == 4 && looser2T1 == 4;
    containsOnly(evals.getEvaluations(),
        entry(11, getEval1(winnerIsLocal, looser1IsLocal, winnerIsLatestRS)),
        entry(12, NOTE_BEST_RS.ordinal()),
        entry(13, getEval1(winnerIsLocal, looser2IsLocal, winnerIsLatestRS)));
  }

  private Integer getEval1(boolean winnerIsLocal, boolean looserIsLocal, boolean winnerIsLatestRS)
  {
    if (winnerIsLocal && !looserIsLocal)
    {
      return NOTE_RS_ON_DIFFERENT_VM_THAN_DS.ordinal();
    }
    else if (winnerIsLatestRS)
    {
      return NOTE_RS_LATER_THAN_ANOTHER_RS_MORE_UP_TO_DATE_THAN_LOCAL_DS.ordinal();
    }
    return NOTE_RS_LATER_THAN_LOCAL_DS.ordinal();
  }

  @DataProvider(name = "test3ServersMoreCriteria")
  public Object[][] create3ServersMoreCriteriaData() {
    return new Object[][] {
        // Test that a RS is chosen if its group is ok whereas the other parameters
        // are not ok
        { 1L, 1L, 1, false,
          4L, 0L, 2, false,
          4L, 0L, 3, false},

        // Test that a RS is chosen if its genid is ok (all RS with same group)
        // and state is not ok
        { 1L, 0L, 1, false,
          4L, 1L, 1, false,
          4L, 2L, 1, false},

        // Test that a RS is chosen if all servers have wrong genid and group id
        // but it is local
        { 1L, 1L, 2, true,
          4L, 2L, 3, false,
          5L, 3L, 4, false}
        };
  }

  /**
   * Test with 3 replication servers (see data provider).
   */
  @Test(dataProvider =  "test3ServersMoreCriteria")
  public void test3ServersMoreCriteria(
      long winnerT1, long winnerGenId, int winnerGroupId, boolean winnerIsLocal,
      long looser1T1, long looser1GenId, int looser1GroupId, boolean looser1IsLocal,
      long looser2T1, long looser2GenId, int looser2GroupId, boolean looser2IsLocal)
      throws Exception
  {
    String testCase = "test3ServersMoreCriteria";
    debugInfo("Starting " + testCase);

    // definitions for server names
    final String WINNER  = "localhost:123";
    final String LOOSER1 = "localhost:456";
    final String LOOSER2 = "localhost:789";

    // Create my state
    ServerState mySt = newServerState(new CSN(4, 0, myId1));

    // State for server 1
    ServerState aState1 = newServerState(new CSN(looser1T1, 0, myId1));
    if (looser1IsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(LOOSER1);
    }

    // State for server 2
    ServerState aState2 = newServerState(new CSN(winnerT1, 0, myId1));
    if (winnerIsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(WINNER);
    }

    // State for server 3
    ServerState aState3 = newServerState(new CSN(looser2T1, 0, myId1));
    if (looser2IsLocal)
    {
      ReplicationServer.onlyForTestsAddlocalReplicationServer(LOOSER2);
    }

    Map<Integer, ReplicationServerInfo> rsInfos = newRSInfos(
        newRSInfo(11, LOOSER1, aState1, looser1GenId, looser1GroupId),
        newRSInfo(12, WINNER, aState2, winnerGenId, winnerGroupId),
        newRSInfo(13, LOOSER2, aState3, looser2GenId, looser2GroupId));
    RSEvaluations evals =
      computeBestReplicationServer(true, -1, mySt, rsInfos, myId1, (byte) 1, 0);

    ReplicationServer.onlyForTestsClearLocalReplicationServerList();

    assertEquals(evals.getBestRS().getServerURL(), WINNER,
        "Wrong best replication server.");
    containsOnly(evals.getEvaluations(),
        entry(11, getEval2(winnerGroupId == looser1GroupId, winnerIsLocal, looser1IsLocal)),
        entry(12, NOTE_BEST_RS.ordinal()),
        entry(13, getEval2(winnerGroupId == looser2GroupId, winnerIsLocal, looser2IsLocal)));
  }

  private Integer getEval2(boolean sameGroupId, boolean winnerIsLocal, boolean looserIsLocal)
  {
    if (winnerIsLocal && !looserIsLocal)
    {
      return NOTE_RS_ON_DIFFERENT_VM_THAN_DS.ordinal();
    }
    else if (!sameGroupId)
    {
      return NOTE_RS_HAS_DIFFERENT_GROUP_ID_THAN_DS.ordinal();
    }
    return NOTE_RS_HAS_DIFFERENT_GENERATION_ID_THAN_DS.ordinal();
  }

  @SuppressWarnings("unchecked")
  @DataProvider(name = "testComputeBestServerForWeightProvider")
  public Object[][] testComputeBestServerForWeightProvider() {

    Object[][] testData = new Object[24][];
    int idx = 0;

    Map<Integer, ReplicationServerInfo> rsInfos = null;

    /************************
     * First connection tests
     ************************/

    /**
     * 1 RS, no connected DSs
     * Expected winner: the RS
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "AwinnerHost:123", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "AwinnerHost:123", // winner url
    };

    /**
     * 2 RSs with TL=0.5, no connected DSs
     * Excepted winner: first in the list
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "BwinnerHost:123", 0L, (byte)1, 1),
        EMPTY_SET);
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "BwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=1.0 - DS=1 ; RS2: TL=0.5 - CL=0 - DS=0
     * Excepted winner: R2 (still no connected DS)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "CwinnerHost:456", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "CwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=0.5 - DS=1 ; RS2: TL=0.5 - CL=0.5 - DS=1
     * Excepted winner: first in the list as both RSs reached TL
     * and have same weight
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "DwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(101));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "DwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=2/3 - DS=2 ; RS2: TL=0.5 - CL=1/3 - DS=1
     * Excepted winner: RS2 -> 2 DSs on each RS
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "EwinnerHost:456", 0L, (byte)1, 1),
        newHashSet(101));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "EwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=0.5 - DS=1 ; RS2: TL=2/3 - CL=0.5 - DS=1
     * Excepted winner: RS2 -> go to perfect load balance
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "FwinnerHost:456", 0L, (byte)1, 2),
        newHashSet(101));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "FwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=1/3 - DS=1 ; RS2: TL=2/3 - CL=2/3 - DS=2
     * Excepted winner: RS2 -> already load balanced so choose server with the
     * highest weight
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "GwinnerHost:456", 0L, (byte)1, 2),
        newHashSet(101, 102));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "GwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=1/3 - DS=2 ; RS2: TL=2/3 - CL=2/3 - DS=4
     * Excepted winner: RS2 -> already load balanced so choose server with the
     * highest weight
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "HwinnerHost:456", 0L, (byte)1, 2),
        newHashSet(101, 102, 103, 104));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "HwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/6 - CL=1/6 - DS=1 ; RS2: TL=2/6 - CL=2/6 - DS=2 ; RS3: TL=3/6 - CL=3/6 - DS=3
     * Excepted winner: RS3 -> already load balanced so choose server with the
     * highest weight
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 2),
        newHashSet(101, 102));
    put(rsInfos,
        new RSInfo(13, "IwinnerHost:789", 0L, (byte)1, 3),
        newHashSet(201, 202, 203));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "IwinnerHost:789", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=5/10 - CL=3/9 - DS=3 ; RS2: TL=3/10 - CL=5/9 - DS=5 ; RS3: TL=2/10 - CL=1/9 - DS=1
     * Excepted winner: RS1 -> misses more DSs than RS3
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "JwinnerHost:123", 0L, (byte)1, 5),
        newHashSet(1, 2, 3));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 3),
        newHashSet(101, 102, 103, 104, 105));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 2),
        newHashSet(201));

    testData[idx++] = new Object[] {
      rsInfos,
      -1, // current RS id
      -1, // local DS id
      "JwinnerHost:123", // winner url
    };

    /*************************
     * Already connected tests
     *************************/

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=0.5 - DS=1 ; RS2: TL=0.5 - CL=0.5 - DS=1
     * Excepted winner: RS2 (stay connected to it as load correctly spread)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1));
    put(rsInfos,
        new RSInfo(12, "KwinnerHost:456", 0L, (byte)1, 1),
        newHashSet(101));

    testData[idx++] = new Object[] {
      rsInfos,
      12, // current RS id
      101, // local DS id
      "KwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=1.0 - DS=2 ; RS2: TL=0.5 - CL=0.0 - DS=0
     * Excepted winner: RS2 (one must disconnect from RS1)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "LwinnerHost:456", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      1, // local DS id
      null, // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=1.0 - DS=2 ; RS2: TL=0.5 - CL=0.0 - DS=0
     * Excepted winner: RS1 (one server must disconnect from RS1 but it is the
     * one with the lowest id so not DS with server id 2)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "MwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      2, // local DS id
      "MwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.3 - CL=0.3 - DS=6 ; RS2: TL=0.4 - CL=0.4 - DS=8 ;
     * RS3: TL=0.1 - CL=0.1 - DS=2 ; RS4: TL=0.2 - CL=0.2 - DS=4
     * Excepted winner: RS2 no change as load correctly spread
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 3),
        newHashSet(1, 2, 3, 4, 5, 6));
    put(rsInfos,
        new RSInfo(12, "NwinnerHost:456", 0L, (byte)1, 4),
        newHashSet(101, 102, 103, 104, 105, 106, 107, 108));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(201, 202));
    put(rsInfos,
        new RSInfo(14, "looserHost:1011", 0L, (byte)1, 2),
        newHashSet(301, 302, 303, 304));

    testData[idx++] = new Object[] {
      rsInfos,
      12, // current RS id
      101, // local DS id
      "NwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.3 - CL=0.2 - DS=4 ; RS2: TL=0.4 - CL=0.4 - DS=8 ;
     * RS3: TL=0.1 - CL=0.1 - DS=2 ; RS4: TL=0.2 - CL=0.3 - DS=6
     * Excepted winner: RS2: no change load ok on current server and there is the
     * possibility to arrange load for other servers with disconnection from
     * 2 DSs from RS4 and reconnect them to RS1 (we moved these 2 servers from
     * previous test where the loads were ok)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 3),
        newHashSet(1, 2, 3, 4));
    put(rsInfos,
        new RSInfo(12, "OwinnerHost:456", 0L, (byte)1, 4),
        newHashSet(101, 102, 103, 104, 105, 106, 107, 108));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(201, 202));
    put(rsInfos,
        new RSInfo(14, "looserHost:1011", 0L, (byte)1, 2),
        newHashSet(301, 302, 303, 304, 305, 306));

    testData[idx++] = new Object[] {
      rsInfos,
      12, // current RS id
      101, // local DS id
      "OwinnerHost:456", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.3 - CL=0.2 - DS=4 ; RS2: TL=0.4 - CL=0.4 - DS=8 ;
     * RS3: TL=0.1 - CL=0.1 - DS=2 ; RS4: TL=0.2 - CL=0.3 - DS=6
     * Excepted winner: RS4 : 2 DSs should go away from RS4 and server id 302
     * is one of the two lowest ids connected to RS4
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "PwinnerHost:123", 0L, (byte)1, 3),
        newHashSet(1, 2, 3, 4));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 4),
        newHashSet(101, 102, 103, 104, 105, 106, 107, 108));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(201, 202));
    put(rsInfos,
        new RSInfo(14, "looserHost:1011", 0L, (byte)1, 2),
        newHashSet(306, 305, 304, 303, 302, 301));

    testData[idx++] = new Object[] {
      rsInfos,
      14, // current RS id
      302, // local DS id
      null, // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.3 - CL=0.2 - DS=4 ; RS2: TL=0.4 - CL=0.4 - DS=8 ;
     * RS3: TL=0.1 - CL=0.1 - DS=2 ; RS4: TL=0.2 - CL=0.3 - DS=6
     * Excepted winner: RS1 : 2 DSs should go away from RS4 but server id 303
     * is not one of the two lowest ids connected to RS4
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 3),
        newHashSet(1, 2, 3, 4));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 4),
        newHashSet(101, 102, 103, 104, 105, 106, 107, 108));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(201, 202));
    put(rsInfos,
        new RSInfo(14, "QwinnerHost:1011", 0L, (byte)1, 2),
        newHashSet(306, 305, 304, 303, 302, 301));

    testData[idx++] = new Object[] {
      rsInfos,
      14, // current RS id
      303, // local DS id
      "QwinnerHost:1011", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.3 - CL=0.2 - DS=4 ; RS2: TL=0.4 - CL=0.65 - DS=13 ;
     * RS3: TL=0.1 - CL=0.1 - DS=2 ; RS4: TL=0.2 - CL=0.05 - DS=1
     * Excepted winner: RS2: no change load ok on current server and there is the
     * possibility to arrange load for other servers with disconnection from
     * 2 DSs from RS4 and reconnect them to RS1 (we moved these 2 servers from
     * previous test where the loads were ok)
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte) 1, 3),
        newHashSet(1, 2, 3, 4));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 4),
        newHashSet(113, 112, 111, 110, 109, 108, 107, 106, 105, 104, 103, 102, 101));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(201, 202));
    put(rsInfos,
        new RSInfo(14, "looserHost:1011", 0L, (byte)1, 2),
        newHashSet(301));

    testData[idx++] = new Object[] {
      rsInfos,
      12, // current RS id
      105, // local DS id
      null, // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=2/3 - DS=2 ; RS2: TL=0.5 - CL=1/3 - DS=1
     * Excepted winner: RS1. Local server should stay connected to current one
     * as the balance cannot be done. We already have the nearest possible
     * balance to the load goals: disconnection would cause a yoyo effect and
     * the local server would not stop going and coming back to/from the other
     * RS.
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "RwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(3));

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      1, // local DS id
      "RwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=0.5 - CL=2/3 - DS=2 ; RS2: TL=0.5 - CL=1/3 - DS=1
     * Excepted winner: RS1. Local server should stay connected to current one
     * as the balance cannot be done. We already have the nearest possible
     * balance to the load goals: disconnection would cause a yoyo effect and
     * the local server would not stop going and coming back to/from the other
     * RS.
     * Note: Same test as before, but not with the lowest local DS server id
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "SwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(3));

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      2, // local DS id
      "SwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=2/4 - DS=2 ; RS2: TL=1/3 - CL=1/4 - DS=1 ; RS3: TL=1/3 - CL=1/4 - DS=1
     * Excepted winner: RS1. Local server should stay connected to current one
     * as the balance cannot be done. We already have the nearest possible
     * balance to the load goals: disconnection would cause a yoyo effect and
     * the local server would not stop going and coming back between RSs.
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "TwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(3));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(4));

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      1, // local DS id
      "TwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=3/7 - DS=3 ; RS2: TL=1/3 - CL=2/7 - DS=2 ; RS3: TL=1/3 - CL=2/7 - DS=2
     * Excepted winner: RS1. Local server should stay connected to current one
     * as the balance cannot be done. We already have the nearest possible
     * balance to the load goals: disconnection would cause a yoyo effect and
     * the local server would not stop going and coming back between RSs.
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "UwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2, 3));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(4, 5));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        newHashSet(6, 7));

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      1, // local DS id
      "UwinnerHost:123", // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=2/3 - DS=2 ; RS2: TL=1/3 - CL=1/3 - DS=1 ; RS3: TL=1/3 - CL=0 - DS=0
     * Excepted winner: RS3. Local server should disconnect for reconnection to
     * RS3
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "looserHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(3));
    put(rsInfos,
        new RSInfo(13, "VwinnerHost:789", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      1, // local DS id
      null, // winner url
    };

    /**
     * TL = target load
     * CL = current load
     * DS = connected DSs number
     * RS1: TL=1/3 - CL=2/3 - DS=2 ; RS2: TL=1/3 - CL=1/3 - DS=1 ; RS3: TL=1/3 - CL=0 - DS=0
     * Excepted winner: RS3. Local server (2) should stay connected while
     * DS server id 1 should disconnect for reconnection to RS3
     */

    rsInfos = new HashMap<>();
    put(rsInfos,
        new RSInfo(11, "WwinnerHost:123", 0L, (byte)1, 1),
        newHashSet(1, 2));
    put(rsInfos,
        new RSInfo(12, "looserHost:456", 0L, (byte)1, 1),
        newHashSet(3));
    put(rsInfos,
        new RSInfo(13, "looserHost:789", 0L, (byte)1, 1),
        EMPTY_SET);

    testData[idx++] = new Object[] {
      rsInfos,
      11, // current RS id
      2, // local DS id
      "WwinnerHost:123", // winner url
    };

    return testData;
  }

  private void put(Map<Integer, ReplicationServerInfo> rsInfos, RSInfo rsInfo,
      Set<Integer> connectedDSs)
  {
    ReplicationServerInfo info = new ReplicationServerInfo(rsInfo, connectedDSs);
    rsInfos.put(info.getServerId(), info);
  }

  /**
   * Test the method that chooses the best RS using the RS weights.
   */
  @Test(dataProvider =  "testComputeBestServerForWeightProvider")
  public void testComputeBestServerForWeight(
      Map<Integer, ReplicationServerInfo> servers, int currentRsServerId,
      int localServerId, String winnerUrl)
      throws Exception
  {
    String testCase = "testComputeBestServerForWeight";
    debugInfo("Starting " + testCase);

    final RSEvaluations evals = new RSEvaluations(localServerId, servers);
    computeBestServerForWeight(evals, currentRsServerId, localServerId);
    final ReplicationServerInfo bestServer = evals.getBestRS();

    if (winnerUrl == null)
    {
      // We expect null
      String url = null;
      if (bestServer != null)
      {
        url = bestServer.getServerURL();
      }
      assertNull(bestServer, "The best server should be null but is: " + url);
    }
    else
    {
      assertNotNull(bestServer, "The best server should not be null");
      assertEquals(bestServer.getServerURL(),
        winnerUrl, "Wrong best replication server: " + bestServer.getServerURL());
    }
  }
}
