package com.liveramp.hank.ui;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import org.apache.log4j.Level;
import org.apache.thrift.TException;
import org.junit.Ignore;
import org.junit.Test;
import com.liveramp.hank.Hank;
import com.liveramp.hank.client.HankSmartClient;
import com.liveramp.hank.coordinator.Coordinator;
import com.liveramp.hank.coordinator.Domain;
import com.liveramp.hank.coordinator.DomainGroup;
import com.liveramp.hank.coordinator.DomainVersion;
import com.liveramp.hank.coordinator.Host;
import com.liveramp.hank.coordinator.HostDomain;
import com.liveramp.hank.coordinator.HostDomainPartition;
import com.liveramp.hank.coordinator.HostState;
import com.liveramp.hank.coordinator.Hosts;
import com.liveramp.hank.coordinator.Ring;
import com.liveramp.hank.coordinator.RingGroup;
import com.liveramp.hank.generated.ClientMetadata;
import com.liveramp.hank.generated.HankBulkResponse;
import com.liveramp.hank.generated.HankResponse;
import com.liveramp.hank.generated.SmartClient;
import com.liveramp.hank.partition_assigner.PartitionAssigner;
import com.liveramp.hank.partition_assigner.RendezVousPartitionAssigner;
import com.liveramp.hank.partition_server.DoublePopulationStatisticsAggregator;
import com.liveramp.hank.partition_server.FilesystemStatisticsAggregator;
import com.liveramp.hank.partition_server.RuntimeStatisticsAggregator;
import com.liveramp.hank.partitioner.Murmur64Partitioner;
import com.liveramp.hank.ring_group_conductor.RingGroupConductorMode;
import com.liveramp.hank.storage.CacheStatistics;
import com.liveramp.hank.storage.echo.Echo;
import com.liveramp.hank.test.ZkMockCoordinatorTestCase;
import com.liveramp.hank.util.LocalHostUtils;
@Ignore("Should only be run manually")
public class WebUiServerTester extends ZkMockCoordinatorTestCase {
private static final int NUM_RING_GROUPS = 1;
public static final String DOMAIN_ = "domain-";
public static final String DOMAIN_GROUP_ = "domain-group-";
public static final String RING_GROUP_ = "ring-group-";
private static final String HOST_ = "host-";
private static final int NUM_DOMAINS = 2;
private static final int NUM_RINGS = 2;
private static final int NUM_HOSTS = 10;
private static final int NUM_CLIENTS = 100;
private final Random random = new Random();
@Test
public void testIt() throws Exception {
org.apache.log4j.Logger.getLogger("com.liveramp.hank.zookeeper").setLevel(Level.INFO);
final SmartClient.Iface mockClient = new SmartClient.Iface() {
private final Map<String, ByteBuffer> values = new HashMap<String, ByteBuffer>() {
{
put("key1", ByteBuffer.wrap("value1".getBytes()));
put("key2", ByteBuffer.wrap("a really long value that you will just love!".getBytes()));
}
};
@Override
public HankResponse get(String domainName, ByteBuffer key) throws TException {
String sKey;
try {
sKey = new String(key.array(), key.position(), key.limit(), "UTF-8");
} catch (UnsupportedEncodingException e) {
throw new TException(e);
}
ByteBuffer v = values.get(sKey);
if (v != null) {
return HankResponse.value(v);
}
return HankResponse.not_found(true);
}
@Override
public HankBulkResponse getBulk(String domainName, List<ByteBuffer> keys) throws TException {
return null;
}
};
IClientCache clientCache = new IClientCache() {
@Override
public SmartClient.Iface getSmartClient(RingGroup rgc) throws IOException, TException {
return mockClient;
}
};
WebUiServer uiServer = new WebUiServer(getWebUiMockCoordinator(), clientCache, 12345);
uiServer.run();
}
private Coordinator getWebUiMockCoordinator() throws Exception {
Coordinator coordinator = getMockCoordinator();
for (int i = 0; i < NUM_RING_GROUPS; ++i) {
setUpRingGroup(i, coordinator);
}
return coordinator;
}
private void setUpRingGroup(int ringGroupId, Coordinator coordinator) throws IOException {
DomainGroup dg = setUpDomainGroup(ringGroupId, coordinator);
RingGroup rg = coordinator.addRingGroup(RING_GROUP_ + ringGroupId, dg.getName());
// Rings
int numRings = random.nextInt(NUM_RINGS) + 2;
for (int ringId = 0; ringId < numRings; ++ringId) {
setUpRing(rg, ringGroupId, ringId);
}
// Assign
PartitionAssigner partitionAssigner = new RendezVousPartitionAssigner();
for (Ring ring : rg.getRings()) {
partitionAssigner.prepare(ring, dg.getDomainVersions(), RingGroupConductorMode.ACTIVE);
for (Host host : ring.getHosts()) {
partitionAssigner.assign(host);
updateHost(ring, host);
}
}
rg.claimRingGroupConductor(RingGroupConductorMode.ACTIVE);
// Clients
int numClients = random.nextInt(NUM_CLIENTS) + 1;
for (int clientId = 0; clientId < numClients; ++clientId) {
rg.registerClient(new ClientMetadata(
LocalHostUtils.getHostName(),
System.currentTimeMillis(),
HankSmartClient.class.getName(),
Hank.getGitCommit()));
}
}
private void updateHost(Ring ring, Host host) throws IOException {
HostState state = host.getState();
// runtime
if (state == HostState.SERVING) {
Map<Domain, RuntimeStatisticsAggregator> runtimeStatistics = new HashMap<Domain, RuntimeStatisticsAggregator>();
for (HostDomain hd : host.getAssignedDomains()) {
int numRequests = randomNumRequests();
int numHits = (int)(randomNumHitsRatio() * numRequests);
int numHitsL1 = (int)(randomL1HitsRatio() * numHits);
int numHitsL2 = numHits - numHitsL1;
double requestMinimum = randomRequestMinimum();
double requestMaximum = randomRequestMaximum();
long requestValues = randomRequestValues();
long requestTotal = randomRequestsTotal();
double[] randomSample = randomRequestSample();
CacheStatistics randomCacheStatistics = randomCacheStatistics();
runtimeStatistics.put(hd.getDomain(),
new RuntimeStatisticsAggregator(
randomThroughput(),
randomResponseDataThroughput(),
numRequests,
numHits,
numHitsL1,
numHitsL2,
new DoublePopulationStatisticsAggregator(
requestMinimum,
requestMaximum,
requestValues,
requestTotal,
randomSample),
randomCacheStatistics
)
);
}
Hosts.setRuntimeStatistics(host, runtimeStatistics);
}
if (state == HostState.SERVING || state == HostState.IDLE) {
for (HostDomain hd : host.getAssignedDomains()) {
for (HostDomainPartition partition : hd.getPartitions()) {
partition.setCurrentDomainVersion(ring.getRingGroup().getDomainGroup().getDomainVersion(hd.getDomain()).getVersionNumber());
}
}
}
if (state == HostState.UPDATING) {
Hosts.setUpdateETA(host, randomETA());
for (HostDomain hd : host.getAssignedDomains()) {
for (HostDomainPartition partition : hd.getPartitions()) {
if (random.nextInt(3) == 0) {
partition.setCurrentDomainVersion(ring.getRingGroup().getDomainGroup().getDomainVersion(hd.getDomain()).getVersionNumber());
} else {
if (random.nextInt(3) != 0) {
partition.setCurrentDomainVersion(ring.getRingGroup().getDomainGroup().getDomainVersion(hd.getDomain()).getVersionNumber() - 1);
}
}
}
}
}
// filesystem
Map<String, FilesystemStatisticsAggregator> filesystemStatistics = new HashMap<String, FilesystemStatisticsAggregator>();
for (int i = 0; i < 10; ++i) {
long totalSpace = randomTotalSpace();
filesystemStatistics.put("/data-" + i, new FilesystemStatisticsAggregator(totalSpace, randomUsableSpace(totalSpace)));
}
Hosts.setFilesystemStatistics(host, filesystemStatistics);
}
private void setUpRing(RingGroup ringGroup, int ringGroupId, int ringId) throws IOException {
Ring ring = ringGroup.addRing(ringId);
HostState state = randomHostState();
int numHosts = random.nextInt(NUM_HOSTS) + 10;
for (int hostId = 0; hostId < numHosts; ++hostId) {
setUpHost(ringGroupId, ring, hostId, state);
}
}
private Host setUpHost(int ringGroupId, Ring ring, int hostId, HostState state) throws IOException {
Host host = ring.addHost(addy(HOST_ + ringGroupId + "-" + ring.getRingNumber() + "-" + hostId), Collections.<String>emptyList());
if (random.nextInt(20) == 0) {
state = randomOtherHostState();
}
host.setState(state);
return host;
}
private DomainGroup setUpDomainGroup(int domainGroupId, Coordinator coordinator) throws IOException {
int numDomains = random.nextInt(NUM_DOMAINS) + 3;
DomainGroup dg = coordinator.addDomainGroup(DOMAIN_GROUP_ + domainGroupId);
Map<Domain, Integer> versions = new HashMap<Domain, Integer>();
for (int domainId = 0; domainId < numDomains; ++domainId) {
Domain domain = setUpDomain(domainGroupId, domainId, coordinator);
versions.put(domain, 0);
}
dg.setDomainVersions(versions);
return dg;
}
private Domain setUpDomain(int domainGroupId, int domainId, Coordinator coordinator) throws IOException {
int numPartitions = random.nextInt(32) + 4;
final Domain domain = coordinator.addDomain(DOMAIN_ + domainGroupId + "-" + domainId, numPartitions, Echo.Factory.class.getName(), "", Murmur64Partitioner.class.getName(), Collections.<String>emptyList());
DomainVersion ver = domain.openNewVersion(null);
ver.close();
return domain;
}
private HostState randomHostState() {
switch (random.nextInt(5)) {
case 0:
return HostState.SERVING;
case 1:
return HostState.SERVING;
case 2:
return HostState.SERVING;
case 3:
return HostState.SERVING;
case 4:
return HostState.UPDATING;
default:
throw new IllegalStateException();
}
}
private HostState randomOtherHostState() {
switch (random.nextInt(2)) {
case 0:
return HostState.IDLE;
case 1:
return HostState.OFFLINE;
default:
throw new IllegalStateException();
}
}
private long randomETA() {
return random.nextInt(3600);
}
private long randomUsableSpace(long totalSpace) {
return Math.max(0, (totalSpace / (random.nextInt(3) + 3)) - random.nextInt(1 << 30));
}
private long randomTotalSpace() {
return (random.nextInt(500) + 500) * (long)Math.pow(1021, 3);
}
private long randomRequestsTotal() {
return 10000;
}
private long randomRequestValues() {
return 1000;
}
private double randomRequestMaximum() {
return random.nextInt(10) + 80;
}
private double randomRequestMinimum() {
return random.nextInt(10) * 0.001;
}
private double randomL1HitsRatio() {
return ((double)random.nextInt(100) / 100);
}
private double randomNumHitsRatio() {
return ((double)random.nextInt(25) + 75) / 100;
}
private int randomNumRequests() {
return 142;
}
private CacheStatistics randomCacheStatistics() {
int scale = random.nextInt(10) + 1;
return new CacheStatistics((1L * scale) << 18, (1L * scale) << 20, (100L * scale) << 20, (1L * scale) << 30);
}
private double[] randomRequestSample() {
int scale = random.nextInt(3) + 1;
double[] input = new double[]{0.01 * scale, 0.01 * scale, 0.1 * scale, 0.5 * scale, 0.8 * scale, 1 * scale, 1.5 * scale, 3 * scale, 8 * scale};
double[] result = new double[1000];
for (int i = 0; i < result.length; ++i) {
result[i] = input[random.nextInt(input.length)];
}
return result;
}
private double randomResponseDataThroughput() {
return random.nextInt(10) * (1 << 20);
}
private double randomThroughput() {
return random.nextInt(1000);
}
}