io.airlift.airship.coordinator.HttpRemoteAgent Maven / Gradle / Ivy
The newest version!
package io.airlift.airship.coordinator;
import com.google.common.base.Function;
import com.google.common.base.Preconditions;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.CheckedFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import io.airlift.airship.shared.AgentStatus;
import io.airlift.airship.shared.AgentStatusRepresentation;
import io.airlift.airship.shared.Installation;
import io.airlift.airship.shared.InstallationRepresentation;
import io.airlift.airship.shared.SlotLifecycleState;
import io.airlift.airship.shared.SlotStatus;
import io.airlift.airship.shared.SlotStatusRepresentation;
import io.airlift.discovery.client.ServiceDescriptor;
import io.airlift.discovery.client.ServiceDescriptorsRepresentation;
import io.airlift.http.client.AsyncHttpClient;
import io.airlift.http.client.Request;
import io.airlift.http.client.StatusResponseHandler;
import io.airlift.json.JsonCodec;
import io.airlift.log.Logger;
import javax.ws.rs.core.Response.Status;
import java.net.URI;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static com.google.common.net.HttpHeaders.CONTENT_TYPE;
import static io.airlift.airship.shared.AgentLifecycleState.OFFLINE;
import static io.airlift.airship.shared.AgentLifecycleState.ONLINE;
import static io.airlift.airship.shared.AgentLifecycleState.PROVISIONING;
import static io.airlift.airship.shared.HttpUriBuilder.uriBuilderFrom;
import static io.airlift.airship.shared.VersionsUtil.AIRSHIP_AGENT_VERSION_HEADER;
import static io.airlift.http.client.JsonBodyGenerator.jsonBodyGenerator;
import static io.airlift.http.client.JsonResponseHandler.createJsonResponseHandler;
import static io.airlift.http.client.StatusResponseHandler.StatusResponse;
import static io.airlift.http.client.StatusResponseHandler.createStatusResponseHandler;
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
public class HttpRemoteAgent implements RemoteAgent
{
private static final Logger log = Logger.get(HttpRemoteAgent.class);
private final JsonCodec installationCodec;
private final JsonCodec agentStatusCodec;
private final JsonCodec slotStatusCodec;
private final JsonCodec serviceDescriptorsCodec;
private AgentStatus agentStatus;
private final String environment;
private final AsyncHttpClient httpClient;
private final AtomicLong failureCount = new AtomicLong();
private final AtomicBoolean serviceInventoryUp = new AtomicBoolean(true);
public HttpRemoteAgent(AgentStatus agentStatus,
String environment,
AsyncHttpClient httpClient,
JsonCodec installationCodec,
JsonCodec agentStatusCodec,
JsonCodec slotStatusCodec,
JsonCodec serviceDescriptorsCodec)
{
Preconditions.checkNotNull(agentStatus, "agentStatus is null");
Preconditions.checkNotNull(environment, "environment is null");
Preconditions.checkNotNull(httpClient, "httpClient is null");
this.agentStatus = agentStatus;
this.environment = environment;
this.httpClient = httpClient;
this.installationCodec = installationCodec;
this.agentStatusCodec = agentStatusCodec;
this.slotStatusCodec = slotStatusCodec;
this.serviceDescriptorsCodec = serviceDescriptorsCodec;
}
@Override
public synchronized AgentStatus status()
{
return agentStatus;
}
@Override
public synchronized void setInternalUri(URI internalUri)
{
agentStatus = agentStatus.changeInternalUri(internalUri);
}
@Override
public synchronized List extends RemoteSlot> getSlots()
{
return ImmutableList.copyOf(Iterables.transform(agentStatus.getSlotStatuses(), new Function()
{
@Override
public HttpRemoteSlot apply(SlotStatus slotStatus)
{
return new HttpRemoteSlot(slotStatus, httpClient, HttpRemoteAgent.this);
}
}));
}
@Override
public void setServiceInventory(List serviceInventory)
{
AgentStatus agentStatus = status();
if (agentStatus.getState() == ONLINE) {
Preconditions.checkNotNull(serviceInventory, "serviceInventory is null");
final URI internalUri = agentStatus.getInternalUri();
Request request = Request.Builder.preparePut()
.setUri(uriBuilderFrom(internalUri).replacePath("/v1/serviceInventory").build())
.setHeader(CONTENT_TYPE, APPLICATION_JSON)
.setBodyGenerator(jsonBodyGenerator(serviceDescriptorsCodec, new ServiceDescriptorsRepresentation(environment, serviceInventory)))
.build();
Futures.addCallback(httpClient.executeAsync(request, createStatusResponseHandler()), new FutureCallback()
{
@Override
public void onSuccess(StatusResponse result)
{
if (serviceInventoryUp.compareAndSet(false, true)) {
log.info("Service inventory put succeeded for agent at %s", internalUri);
}
}
@Override
public void onFailure(Throwable t)
{
if (serviceInventoryUp.compareAndSet(true, false) && !log.isDebugEnabled()) {
log.error("Unable to post service inventory to agent at %s: %s", internalUri, t.getMessage());
}
log.debug(t, "Unable to post service inventory to agent at %s: %s", internalUri, t.getMessage());
}
});
}
}
@Override
public ListenableFuture> updateStatus()
{
final AgentStatus agentStatus = status();
URI internalUri = agentStatus.getInternalUri();
if (internalUri != null) {
Request request = Request.Builder.prepareGet()
.setUri(uriBuilderFrom(internalUri).replacePath("/v1/agent/").build())
.build();
CheckedFuture future = httpClient.executeAsync(request, createJsonResponseHandler(agentStatusCodec));
Futures.addCallback(future, new FutureCallback()
{
@Override
public void onSuccess(AgentStatusRepresentation result)
{
// todo deal with out of order responses
setStatus(result.toAgentStatus(agentStatus.getInstanceId(), agentStatus.getInstanceType()));
failureCount.set(0);
}
@Override
public void onFailure(Throwable t)
{
// error talking to agent -- mark agent offline
if (agentStatus.getState() != PROVISIONING && failureCount.incrementAndGet() > 5) {
setStatus(agentStatus.changeState(OFFLINE).changeAllSlotsState(SlotLifecycleState.UNKNOWN));
}
}
});
return future;
}
return Futures.immediateFuture(null);
}
public synchronized void setStatus(AgentStatus agentStatus)
{
Preconditions.checkNotNull(agentStatus, "agentStatus is null");
this.agentStatus = agentStatus;
}
public synchronized void setSlotStatus(SlotStatus slotStatus)
{
agentStatus = agentStatus.changeSlotStatus(slotStatus);
}
@Override
public SlotStatus install(Installation installation)
{
Preconditions.checkNotNull(installation, "installation is null");
AgentStatus agentStatus = status();
URI internalUri = agentStatus.getInternalUri();
Preconditions.checkState(internalUri != null, "agent is down");
try {
Request request = Request.Builder.preparePost()
.setUri(uriBuilderFrom(internalUri).replacePath("/v1/agent/slot/").build())
.setHeader(CONTENT_TYPE, APPLICATION_JSON)
.setHeader(AIRSHIP_AGENT_VERSION_HEADER, status().getVersion())
.setBodyGenerator(jsonBodyGenerator(installationCodec, InstallationRepresentation.from(installation)))
.build();
SlotStatusRepresentation slotStatusRepresentation = httpClient.execute(request, createJsonResponseHandler(slotStatusCodec, Status.CREATED.getStatusCode()));
SlotStatus slotStatus = slotStatusRepresentation.toSlotStatus(agentStatus.getInstanceId());
setStatus(agentStatus.changeSlotStatus(slotStatus));
return slotStatus;
}
catch (Exception e) {
throw Throwables.propagate(e);
}
}
}