/*
* Copyright 2012 Netflix, 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 com.netflix.eureka.resources;
import javax.inject.Inject;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.eureka.EurekaServerContext;
import com.netflix.eureka.EurekaServerConfig;
import com.netflix.eureka.EurekaServerContextHolder;
import com.netflix.eureka.cluster.protocol.ReplicationInstance;
import com.netflix.eureka.cluster.protocol.ReplicationInstanceResponse;
import com.netflix.eureka.cluster.protocol.ReplicationInstanceResponse.Builder;
import com.netflix.eureka.cluster.protocol.ReplicationList;
import com.netflix.eureka.cluster.protocol.ReplicationListResponse;
import com.netflix.eureka.registry.PeerAwareInstanceRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A <em>jersey</em> resource that handles requests for replication purposes.
*
* @author Karthik Ranganathan
*
*/
@Path("/{version}/peerreplication")
@Produces({"application/xml", "application/json"})
public class PeerReplicationResource {
private static final Logger logger = LoggerFactory.getLogger(PeerReplicationResource.class);
private static final String REPLICATION = "true";
private final EurekaServerConfig serverConfig;
private final PeerAwareInstanceRegistry registry;
@Inject
PeerReplicationResource(EurekaServerContext server) {
this.serverConfig = server.getServerConfig();
this.registry = server.getRegistry();
}
public PeerReplicationResource() {
this(EurekaServerContextHolder.getInstance().getServerContext());
}
/**
* Process batched replication events from peer eureka nodes.
*
* <p>
* The batched events are delegated to underlying resources to generate a
* {@link ReplicationListResponse} containing the individual responses to the batched events
* </p>
*
* @param replicationList
* The List of replication events from peer eureka nodes
* @return A batched response containing the information about the responses of individual events
*/
@Path("batch")
@POST
public Response batchReplication(ReplicationList replicationList) {
try {
ReplicationListResponse batchResponse = new ReplicationListResponse();
for (ReplicationInstance instanceInfo : replicationList.getReplicationList()) {
try {
batchResponse.addResponse(dispatch(instanceInfo));
} catch (Exception e) {
batchResponse.addResponse(new ReplicationInstanceResponse(Status.INTERNAL_SERVER_ERROR.getStatusCode(), null));
logger.error(instanceInfo.getAction() + " request processing failed for batch item "
+ instanceInfo.getAppName() + '/' + instanceInfo.getId(), e);
}
}
return Response.ok(batchResponse).build();
} catch (Throwable e) {
logger.error("Cannot execute batch Request", e);
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
}
}
private ReplicationInstanceResponse dispatch(ReplicationInstance instanceInfo) {
ApplicationResource applicationResource = createApplicationResource(instanceInfo);
InstanceResource resource = createInstanceResource(instanceInfo, applicationResource);
String lastDirtyTimestamp = toString(instanceInfo.getLastDirtyTimestamp());
String overriddenStatus = toString(instanceInfo.getOverriddenStatus());
String instanceStatus = toString(instanceInfo.getStatus());
Builder singleResponseBuilder = new Builder();
switch (instanceInfo.getAction()) {
case Register:
singleResponseBuilder = handleRegister(instanceInfo, applicationResource);
break;
case Heartbeat:
singleResponseBuilder = handleHeartbeat(resource, lastDirtyTimestamp, overriddenStatus, instanceStatus);
break;
case Cancel:
singleResponseBuilder = handleCancel(resource);
break;
case StatusUpdate:
singleResponseBuilder = handleStatusUpdate(instanceInfo, resource);
break;
case DeleteStatusOverride:
singleResponseBuilder = handleDeleteStatusOverride(instanceInfo, resource);
break;
}
return singleResponseBuilder.build();
}
/* Visible for testing */ ApplicationResource createApplicationResource(ReplicationInstance instanceInfo) {
return new ApplicationResource(instanceInfo.getAppName(), serverConfig, registry);
}
/* Visible for testing */ InstanceResource createInstanceResource(ReplicationInstance instanceInfo,
ApplicationResource applicationResource) {
return new InstanceResource(applicationResource, instanceInfo.getId(), serverConfig, registry);
}
private static Builder handleRegister(ReplicationInstance instanceInfo, ApplicationResource applicationResource) {
applicationResource.addInstance(instanceInfo.getInstanceInfo(), REPLICATION);
return new Builder().setStatusCode(Status.OK.getStatusCode());
}
private static Builder handleCancel(InstanceResource resource) {
Response response = resource.cancelLease(REPLICATION);
return new Builder().setStatusCode(response.getStatus());
}
private static Builder handleHeartbeat(InstanceResource resource, String lastDirtyTimestamp, String overriddenStatus, String instanceStatus) {
Response response = resource.renewLease(REPLICATION, overriddenStatus, instanceStatus, lastDirtyTimestamp);
Builder responseBuilder = new Builder().setStatusCode(response.getStatus());
if (response.getStatus() == Status.OK.getStatusCode() && response.getEntity() != null) {
responseBuilder.setResponseEntity((InstanceInfo) response.getEntity());
}
return responseBuilder;
}
private static Builder handleStatusUpdate(ReplicationInstance instanceInfo, InstanceResource resource) {
Response response = resource.statusUpdate(instanceInfo.getStatus(), REPLICATION, toString(instanceInfo.getLastDirtyTimestamp()));
return new Builder().setStatusCode(response.getStatus());
}
private static Builder handleDeleteStatusOverride(ReplicationInstance instanceInfo, InstanceResource resource) {
Response response = resource.deleteStatusUpdate(REPLICATION, instanceInfo.getStatus(),
instanceInfo.getLastDirtyTimestamp().toString());
return new Builder().setStatusCode(response.getStatus());
}
private static <T> String toString(T value) {
if (value == null) {
return null;
}
return value.toString();
}
}