package com.netflix.eureka.cluster;
import javax.ws.rs.core.MediaType;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.shared.Application;
import com.netflix.discovery.shared.Applications;
import com.netflix.discovery.shared.transport.EurekaHttpResponse;
import com.netflix.eureka.cluster.protocol.ReplicationInstanceResponse;
import com.netflix.eureka.cluster.protocol.ReplicationList;
import com.netflix.eureka.cluster.protocol.ReplicationListResponse;
import com.netflix.eureka.resources.ASGResource.ASGStatus;
import static com.netflix.discovery.shared.transport.EurekaHttpResponse.anEurekaHttpResponse;
/**
* This stub implementation is primarily useful for batch updates, and complex failure scenarios.
* Using mock would results in too convoluted code.
*
* @author Tomasz Bak
*/
public class TestableHttpReplicationClient implements HttpReplicationClient {
private int[] networkStatusCodes;
private InstanceInfo instanceInfoFromPeer;
private int networkFailuresRepeatCount;
private int batchStatusCode;
private final AtomicInteger callCounter = new AtomicInteger();
private final AtomicInteger networkFailureCounter = new AtomicInteger();
private long processingDelayMs;
private final BlockingQueue<HandledRequest> handledRequests = new LinkedBlockingQueue<>();
public void withNetworkStatusCode(int... networkStatusCodes) {
this.networkStatusCodes = networkStatusCodes;
}
public void withInstanceInfo(InstanceInfo instanceInfoFromPeer) {
this.instanceInfoFromPeer = instanceInfoFromPeer;
}
public void withBatchReply(int batchStatusCode) {
this.batchStatusCode = batchStatusCode;
}
public void withNetworkError(int networkFailuresRepeatCount) {
this.networkFailuresRepeatCount = networkFailuresRepeatCount;
}
public void withProcessingDelay(long processingDelay, TimeUnit timeUnit) {
this.processingDelayMs = timeUnit.toMillis(processingDelay);
}
public HandledRequest nextHandledRequest(long timeout, TimeUnit timeUnit) throws InterruptedException {
return handledRequests.poll(timeout, timeUnit);
}
@Override
public EurekaHttpResponse<Void> register(InstanceInfo info) {
handledRequests.add(new HandledRequest(RequestType.Register, info));
return EurekaHttpResponse.status(networkStatusCodes[callCounter.getAndIncrement()]);
}
@Override
public EurekaHttpResponse<Void> cancel(String appName, String id) {
handledRequests.add(new HandledRequest(RequestType.Cancel, id));
return EurekaHttpResponse.status(networkStatusCodes[callCounter.getAndIncrement()]);
}
@Override
public EurekaHttpResponse<InstanceInfo> sendHeartBeat(String appName, String id, InstanceInfo info, InstanceStatus overriddenStatus) {
handledRequests.add(new HandledRequest(RequestType.Heartbeat, instanceInfoFromPeer));
int statusCode = networkStatusCodes[callCounter.getAndIncrement()];
return anEurekaHttpResponse(statusCode, instanceInfoFromPeer).type(MediaType.APPLICATION_JSON_TYPE).build();
}
@Override
public EurekaHttpResponse<Void> statusUpdate(String asgName, ASGStatus newStatus) {
handledRequests.add(new HandledRequest(RequestType.AsgStatusUpdate, newStatus));
return EurekaHttpResponse.status(networkStatusCodes[callCounter.getAndIncrement()]);
}
@Override
public EurekaHttpResponse<Void> statusUpdate(String appName, String id, InstanceStatus newStatus, InstanceInfo info) {
handledRequests.add(new HandledRequest(RequestType.StatusUpdate, newStatus));
return EurekaHttpResponse.status(networkStatusCodes[callCounter.getAndIncrement()]);
}
@Override
public EurekaHttpResponse<Void> deleteStatusOverride(String appName, String id, InstanceInfo info) {
handledRequests.add(new HandledRequest(RequestType.DeleteStatusOverride, null));
return EurekaHttpResponse.status(networkStatusCodes[callCounter.getAndIncrement()]);
}
@Override
public EurekaHttpResponse<Applications> getApplications(String... regions) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<Applications> getDelta(String... regions) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<Applications> getVip(String vipAddress, String... regions) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<Applications> getSecureVip(String secureVipAddress, String... regions) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<Application> getApplication(String appName) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String id) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<InstanceInfo> getInstance(String appName, String id) {
throw new IllegalStateException("method not supported");
}
@Override
public EurekaHttpResponse<ReplicationListResponse> submitBatchUpdates(ReplicationList replicationList) {
if (networkFailureCounter.get() < networkFailuresRepeatCount) {
networkFailureCounter.incrementAndGet();
throw new RuntimeException(new IOException("simulated network failure"));
}
if (processingDelayMs > 0) {
try {
Thread.sleep(processingDelayMs);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
List<ReplicationInstanceResponse> responseList = new ArrayList<>();
responseList.add(new ReplicationInstanceResponse(batchStatusCode, instanceInfoFromPeer));
ReplicationListResponse replicationListResponse = new ReplicationListResponse(responseList);
handledRequests.add(new HandledRequest(RequestType.Batch, replicationList));
int statusCode = networkStatusCodes[callCounter.getAndIncrement()];
return anEurekaHttpResponse(statusCode, replicationListResponse).type(MediaType.APPLICATION_JSON_TYPE).build();
}
@Override
public void shutdown() {
}
public enum RequestType {Heartbeat, Register, Cancel, StatusUpdate, DeleteStatusOverride, AsgStatusUpdate, Batch}
public static class HandledRequest {
private final RequestType requestType;
private final Object data;
public HandledRequest(RequestType requestType, Object data) {
this.requestType = requestType;
this.data = data;
}
public RequestType getRequestType() {
return requestType;
}
public Object getData() {
return data;
}
}
}