/*
* Copyright 2012-2013 LinkedIn, Inc
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package voldemort.server.gossip;
import static org.junit.Assert.assertEquals;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.log4j.Logger;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import voldemort.Attempt;
import voldemort.ServerTestUtils;
import voldemort.TestUtils;
import voldemort.client.ClientConfig;
import voldemort.client.protocol.admin.AdminClient;
import voldemort.client.protocol.admin.AdminClientConfig;
import voldemort.cluster.Cluster;
import voldemort.cluster.Node;
import voldemort.server.VoldemortServer;
import voldemort.store.metadata.MetadataStore;
import voldemort.store.socket.SocketStoreFactory;
import voldemort.store.socket.clientrequest.ClientRequestExecutorPool;
import voldemort.versioning.VectorClock;
import voldemort.versioning.Version;
import voldemort.versioning.Versioned;
/**
* Tests {@link voldemort.server.gossip.Gossiper}
*
* NOTE : DISABLED.. and will be removed from codebase eventually.
*
*
*/
@Ignore
@RunWith(Parameterized.class)
public class GossiperTest {
private static final Logger logger = Logger.getLogger(GossiperTest.class.getName());
private List<VoldemortServer> servers = new ArrayList<VoldemortServer>();
private Cluster cluster;
private static final int socketBufferSize = 4096;
private static final int adminSocketBufferSize = 8192;
private SocketStoreFactory socketStoreFactory = new ClientRequestExecutorPool(2,
10000,
100000,
socketBufferSize);
private static String storesXmlfile = "test/common/voldemort/config/stores.xml";
private final boolean useNio;
private CountDownLatch countDownLatch;
final private Properties props = new Properties();
public GossiperTest(boolean useNio) {
this.useNio = useNio;
}
@Parameters
public static Collection<Object[]> configs() {
return Arrays.asList(new Object[][] { { false }, { true } });
}
private void attemptParallelClusterStart(ExecutorService executorService) {
// Start all servers in parallel to avoid exceptions during gossip.
cluster = ServerTestUtils.getLocalCluster(3, new int[][] { { 0, 1, 2, 3 }, { 4, 5, 6, 7 },
{ 8, 9, 10, 11 } });
for(int i = 0; i < 3; i++) {
final int j = i;
executorService.submit(new Runnable() {
public void run() {
try {
servers.add(ServerTestUtils.startVoldemortServer(socketStoreFactory,
ServerTestUtils.createServerConfig(useNio,
j,
TestUtils.createTempDir()
.getAbsolutePath(),
null,
storesXmlfile,
props),
cluster));
} catch(IOException ioe) {
logger.error("Caught IOException during parallel server start: "
+ ioe.getMessage());
RuntimeException re = new RuntimeException();
re.initCause(ioe);
throw re;
} finally {
// Ensure setup progresses in face of errors
countDownLatch.countDown();
}
}
});
}
}
@Before
public void setUp() {
props.put("enable.gossip", "true");
props.put("gossip.interval.ms", "250");
props.put("socket.buffer.size", String.valueOf(socketBufferSize));
props.put("admin.streams.buffer.size", String.valueOf(adminSocketBufferSize));
ExecutorService executorService = Executors.newFixedThreadPool(3);
countDownLatch = new CountDownLatch(3);
boolean clusterStarted = false;
while(!clusterStarted) {
try {
attemptParallelClusterStart(executorService);
clusterStarted = true;
} catch(RuntimeException re) {
logger.info("Some server thread threw a RuntimeException. Will print out stacktrace and then try again. Assumption is that the RuntimeException is due to BindException that in turn is due to TOCTOU issue with getLocalCluster");
re.printStackTrace();
}
}
try {
countDownLatch.await();
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@After
public void tearDown() {
socketStoreFactory.close();
}
private AdminClient getAdminClient(Cluster newCluster) {
return new AdminClient(newCluster, new AdminClientConfig(), new ClientConfig());
}
private Cluster attemptStartAdditionalServer() throws IOException {
// Set up a new cluster that is one bigger than the original cluster
int originalSize = cluster.getNumberOfNodes();
int numOriginalPorts = originalSize * 3;
int ports[] = new int[numOriginalPorts + 3];
for(int i = 0, j = 0; i < originalSize; i++, j += 3) {
Node node = cluster.getNodeById(i);
System.arraycopy(new int[] { node.getHttpPort(), node.getSocketPort(),
node.getAdminPort() },
0,
ports,
j,
3);
}
System.arraycopy(ServerTestUtils.findFreePorts(3), 0, ports, numOriginalPorts, 3);
// Create a new partitioning scheme with room for a new server
final Cluster newCluster = ServerTestUtils.getLocalCluster(originalSize + 1,
ports,
new int[][] { { 0, 4, 8 },
{ 1, 5, 9 },
{ 2, 6, 10 },
{ 3, 7, 11 } });
// Create a new server
VoldemortServer newServer = ServerTestUtils.startVoldemortServer(socketStoreFactory,
ServerTestUtils.createServerConfig(useNio,
3,
TestUtils.createTempDir()
.getAbsolutePath(),
null,
storesXmlfile,
props),
newCluster);
// This step is only reached if startVoldemortServer does *not* throw a
// BindException due to TOCTOU problem with getLocalCluster
servers.add(newServer);
return newCluster;
}
// Protect against this test running forever until the root cause of running
// forever is found.
@Test(timeout = 1800)
public void testGossiper() throws Exception {
Cluster newCluster = null;
boolean startedAdditionalServer = false;
while(!startedAdditionalServer) {
try {
newCluster = attemptStartAdditionalServer();
startedAdditionalServer = true;
} catch(IOException ioe) {
logger.warn("Caught an IOException when attempting to start additional server. Will print stacktrace and then attempt to start additional server again.");
ioe.printStackTrace();
}
}
// Get the new cluster.xml
AdminClient localAdminClient = getAdminClient(newCluster);
Versioned<String> versionedClusterXML = localAdminClient.metadataMgmtOps.getRemoteMetadata(3,
MetadataStore.CLUSTER_KEY);
// Increment the version, let what would be the "donor node" know about
// it to seed the Gossip.
Version version = versionedClusterXML.getVersion();
((VectorClock) version).incrementVersion(3, ((VectorClock) version).getTimestamp() + 1);
((VectorClock) version).incrementVersion(0, ((VectorClock) version).getTimestamp() + 1);
localAdminClient.metadataMgmtOps.updateRemoteMetadata(0,
MetadataStore.CLUSTER_KEY,
versionedClusterXML);
localAdminClient.metadataMgmtOps.updateRemoteMetadata(3,
MetadataStore.CLUSTER_KEY,
versionedClusterXML);
try {
Thread.sleep(500);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
// Wait up to five seconds for Gossip to spread
final Cluster newFinalCluster = newCluster;
try {
TestUtils.assertWithBackoff(5000, new Attempt() {
public void checkCondition() {
int serversSeen = 0;
// Now verify that we have gossiped correctly
for(VoldemortServer server: servers) {
Cluster clusterAtServer = server.getMetadataStore().getCluster();
int nodeId = server.getMetadataStore().getNodeId();
assertEquals("server " + nodeId + " has heard "
+ " the gossip about number of nodes",
clusterAtServer.getNumberOfNodes(),
newFinalCluster.getNumberOfNodes());
assertEquals("server " + nodeId + " has heard "
+ " the gossip about partitions",
clusterAtServer.getNodeById(nodeId).getPartitionIds(),
newFinalCluster.getNodeById(nodeId).getPartitionIds());
serversSeen++;
}
assertEquals("saw all servers", serversSeen, servers.size());
}
});
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}