package com.netflix.discovery.util;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.AmazonInfo.MetaDataKey;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.ActionType;
import com.netflix.appinfo.InstanceInfo.Builder;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.appinfo.InstanceInfo.PortType;
import com.netflix.appinfo.LeaseInfo;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import static com.netflix.discovery.util.EurekaEntityFunctions.mergeApplications;
import static com.netflix.discovery.util.EurekaEntityFunctions.toApplicationMap;
/**
* Test data generator.
*
* @author Tomasz Bak
*/
public class InstanceInfoGenerator {
public static final int RENEW_INTERVAL = 5;
private final int instanceCount;
private final String[] appNames;
private final String zone;
private final boolean taggedId;
private Iterator<InstanceInfo> currentIt;
private Applications allApplications = new Applications();
private final boolean withMetaData;
private final boolean includeAsg;
private final boolean useInstanceId;
InstanceInfoGenerator(InstanceInfoGeneratorBuilder builder) {
this.instanceCount = builder.instanceCount;
this.appNames = builder.appNames;
this.zone = builder.zone == null ? "us-east-1c" : builder.zone;
this.taggedId = builder.taggedId;
this.withMetaData = builder.includeMetaData;
this.includeAsg = builder.includeAsg;
this.useInstanceId = builder.useInstanceId;
}
public Applications takeDelta(int count) {
if (currentIt == null) {
currentIt = serviceIterator();
allApplications = new Applications();
}
List<InstanceInfo> instanceBatch = new ArrayList<InstanceInfo>();
for (int i = 0; i < count; i++) {
InstanceInfo next = currentIt.next();
next.setActionType(ActionType.ADDED);
instanceBatch.add(next);
}
Applications nextBatch = EurekaEntityFunctions.toApplications(toApplicationMap(instanceBatch));
allApplications = mergeApplications(allApplications, nextBatch);
nextBatch.setAppsHashCode(allApplications.getAppsHashCode());
return nextBatch;
}
public Iterator<InstanceInfo> serviceIterator() {
return new Iterator<InstanceInfo>() {
private int returned;
private final int[] appInstanceIds = new int[appNames.length];
private int currentApp;
@Override
public boolean hasNext() {
return returned < instanceCount;
}
@Override
public InstanceInfo next() {
if (!hasNext()) {
throw new NoSuchElementException("no more InstanceInfo elements");
}
InstanceInfo toReturn = generateInstanceInfo(currentApp, appInstanceIds[currentApp], useInstanceId);
appInstanceIds[currentApp]++;
currentApp = (currentApp + 1) % appNames.length;
returned++;
return toReturn;
}
@Override
public void remove() {
throw new IllegalStateException("method not supported");
}
};
}
public Applications toApplications() {
Map<String, Application> appsByName = new HashMap<>();
Iterator<InstanceInfo> it = serviceIterator();
while (it.hasNext()) {
InstanceInfo instanceInfo = it.next();
Application instanceApp = appsByName.get(instanceInfo.getAppName());
if (instanceApp == null) {
instanceApp = new Application(instanceInfo.getAppName());
appsByName.put(instanceInfo.getAppName(), instanceApp);
}
instanceApp.addInstance(instanceInfo);
}
// Do not pass application list to the constructor, as it does not initialize properly Applications
// data structure.
Applications applications = new Applications();
for (Application app : appsByName.values()) {
applications.addApplication(app);
}
applications.shuffleInstances(false);
applications.setAppsHashCode(applications.getReconcileHashCode());
applications.setVersion(1L);
return applications;
}
public List<InstanceInfo> toInstanceList() {
List<InstanceInfo> result = new ArrayList<>(instanceCount);
Iterator<InstanceInfo> it = serviceIterator();
while (it.hasNext()) {
InstanceInfo instanceInfo = it.next();
result.add(instanceInfo);
}
return result;
}
public InstanceInfo first() {
return take(0);
}
public InstanceInfo take(int idx) {
return toInstanceList().get(idx);
}
public static InstanceInfo takeOne() {
return newBuilder(1, 1).withMetaData(true).build().serviceIterator().next();
}
public static InstanceInfoGeneratorBuilder newBuilder(int instanceCount, int applicationCount) {
return new InstanceInfoGeneratorBuilder(instanceCount, applicationCount);
}
public static InstanceInfoGeneratorBuilder newBuilder(int instanceCount, String... appNames) {
return new InstanceInfoGeneratorBuilder(instanceCount, appNames);
}
// useInstanceId to false to generate older InstanceInfo types that does not use instanceId field for instance id.
private InstanceInfo generateInstanceInfo(int appIndex, int appInstanceId, boolean useInstanceId) {
String appName = appNames[appIndex];
String hostName = "instance" + appInstanceId + '.' + appName + ".com";
String privateHostname = "ip-10.0" + appIndex + "." + appInstanceId + ".compute.internal";
String publicIp = "20.0." + appIndex + '.' + appInstanceId;
String privateIp = "192.168." + appIndex + '.' + appInstanceId;
String instanceId = String.format("i-%04d%04d", appIndex, appInstanceId);
if (taggedId) {
instanceId = instanceId + '_' + appName;
}
AmazonInfo dataCenterInfo = AmazonInfo.Builder.newBuilder()
.addMetadata(MetaDataKey.accountId, "testAccountId")
.addMetadata(MetaDataKey.amiId, String.format("ami-%04d%04d", appIndex, appInstanceId))
.addMetadata(MetaDataKey.availabilityZone, zone)
.addMetadata(MetaDataKey.instanceId, instanceId)
.addMetadata(MetaDataKey.instanceType, "m2.xlarge")
.addMetadata(MetaDataKey.localHostname, privateHostname)
.addMetadata(MetaDataKey.localIpv4, privateIp)
.addMetadata(MetaDataKey.publicHostname, hostName)
.addMetadata(MetaDataKey.publicIpv4, publicIp)
.build();
String unsecureURL = "http://" + hostName + ":8080";
String secureURL = "https://" + hostName + ":8081";
long now = System.currentTimeMillis();
LeaseInfo leaseInfo = LeaseInfo.Builder.newBuilder()
.setDurationInSecs(3 * RENEW_INTERVAL)
.setRenewalIntervalInSecs(RENEW_INTERVAL)
.setServiceUpTimestamp(now - RENEW_INTERVAL)
.setRegistrationTimestamp(now)
.setEvictionTimestamp(now + 3 * RENEW_INTERVAL)
.setRenewalTimestamp(now + RENEW_INTERVAL)
.build();
Builder builder = useInstanceId
? InstanceInfo.Builder.newBuilder().setInstanceId(instanceId)
: InstanceInfo.Builder.newBuilder();
builder
.setActionType(ActionType.ADDED)
.setAppGroupName(appName + "Group")
.setAppName(appName)
.setHostName(hostName)
.setIPAddr(publicIp)
.setPort(8080)
.setSecurePort(8081)
.enablePort(PortType.SECURE, true)
.setHealthCheckUrls("/healthcheck", unsecureURL + "/healthcheck", secureURL + "/healthcheck")
.setHomePageUrl("/homepage", unsecureURL + "/homepage")
.setStatusPageUrl("/status", unsecureURL + "/status")
.setLeaseInfo(leaseInfo)
.setStatus(InstanceStatus.UP)
.setVIPAddress(appName + ":8080")
.setSecureVIPAddress(appName + ":8081")
.setDataCenterInfo(dataCenterInfo)
.setLastUpdatedTimestamp(System.currentTimeMillis() - 100)
.setLastDirtyTimestamp(System.currentTimeMillis() - 100)
.setIsCoordinatingDiscoveryServer(true)
.enablePort(PortType.UNSECURE, true);
if (includeAsg) {
builder.setASGName(appName + "ASG");
}
if (withMetaData) {
builder.add("appKey" + appIndex, Integer.toString(appInstanceId));
}
return builder.build();
}
public static class InstanceInfoGeneratorBuilder {
private final int instanceCount;
private String[] appNames;
private boolean includeMetaData;
private boolean includeAsg = true;
private String zone;
private boolean taggedId;
private boolean useInstanceId = true;
public InstanceInfoGeneratorBuilder(int instanceCount, int applicationCount) {
this.instanceCount = instanceCount;
String[] appNames = new String[applicationCount];
for (int i = 0; i < appNames.length; i++) {
appNames[i] = "application" + i;
}
this.appNames = appNames;
}
public InstanceInfoGeneratorBuilder(int instanceCount, String... appNames) {
this.instanceCount = instanceCount;
this.appNames = appNames;
}
public InstanceInfoGeneratorBuilder withZone(String zone) {
this.zone = zone;
return this;
}
public InstanceInfoGeneratorBuilder withTaggedId(boolean taggedId) {
this.taggedId = taggedId;
return this;
}
public InstanceInfoGeneratorBuilder withMetaData(boolean includeMetaData) {
this.includeMetaData = includeMetaData;
return this;
}
public InstanceInfoGeneratorBuilder withAsg(boolean includeAsg) {
this.includeAsg = includeAsg;
return this;
}
public InstanceInfoGeneratorBuilder withUseInstanceId(boolean useInstanceId) {
this.useInstanceId = useInstanceId;
return this;
}
public InstanceInfoGenerator build() {
return new InstanceInfoGenerator(this);
}
}
}