package com.hubspot.baragon.client;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.inject.Provider;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.hubspot.baragon.models.BaragonAgentMetadata;
import com.hubspot.baragon.models.BaragonGroup;
import com.hubspot.baragon.models.BaragonRequest;
import com.hubspot.baragon.models.BaragonResponse;
import com.hubspot.baragon.models.BaragonService;
import com.hubspot.baragon.models.BaragonServiceState;
import com.hubspot.baragon.models.BaragonServiceStatus;
import com.hubspot.baragon.models.QueuedRequestId;
import com.hubspot.horizon.HttpClient;
import com.hubspot.horizon.HttpRequest;
import com.hubspot.horizon.HttpRequest.Method;
import com.hubspot.horizon.HttpResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static com.google.common.base.Preconditions.checkNotNull;
public class BaragonServiceClient {
private static final Logger LOG = LoggerFactory.getLogger(BaragonServiceClient.class);
private static final String WORKERS_FORMAT = "http://%s/%s/workers";
private static final String LOAD_BALANCER_FORMAT = "http://%s/%s/load-balancer";
private static final String LOAD_BALANCER_BASE_PATH_FORMAT = LOAD_BALANCER_FORMAT + "/%s/base-path";
private static final String LOAD_BALANCER_ALL_BASE_PATHS_FORMAT = LOAD_BALANCER_BASE_PATH_FORMAT + "/all";
private static final String LOAD_BALANCER_AGENTS_FORMAT = LOAD_BALANCER_FORMAT + "/%s/agents";
private static final String LOAD_BALANCER_KNOWN_AGENTS_FORMAT = LOAD_BALANCER_FORMAT + "/%s/known-agents";
private static final String LOAD_BALANCER_DELETE_KNOWN_AGENT_FORMAT = LOAD_BALANCER_KNOWN_AGENTS_FORMAT + "/%s";
private static final String LOAD_BALANCER_GROUP_FORMAT = LOAD_BALANCER_FORMAT + "/%s";
private static final String ALL_LOAD_BALANCER_GROUPS_FORMAT = LOAD_BALANCER_FORMAT + "/all";
private static final String LOAD_BALANCER_TRAFFIC_SOURCE_FORMAT = LOAD_BALANCER_GROUP_FORMAT + "/sources";
private static final String REQUEST_FORMAT = "http://%s/%s/request";
private static final String REQUEST_ID_FORMAT = REQUEST_FORMAT + "/%s";
private static final String STATE_FORMAT = "http://%s/%s/state";
private static final String STATE_SERVICE_ID_FORMAT = STATE_FORMAT + "/%s";
private static final String STATE_RELOAD_FORMAT = STATE_SERVICE_ID_FORMAT + "/reload";
private static final String STATUS_FORMAT = "http://%s/%s/status";
private static final TypeReference<Collection<String>> STRING_COLLECTION = new TypeReference<Collection<String>>() {};
private static final TypeReference<Collection<BaragonGroup>> BARAGON_GROUP_COLLECTION = new TypeReference<Collection<BaragonGroup>>() {};
private static final TypeReference<Collection<BaragonAgentMetadata>> BARAGON_AGENTS_COLLECTION = new TypeReference<Collection<BaragonAgentMetadata>>() {};
private static final TypeReference<Collection<QueuedRequestId>> QUEUED_REQUEST_COLLECTION = new TypeReference<Collection<QueuedRequestId>>() {};
private static final TypeReference<Collection<BaragonServiceState>> BARAGON_SERVICE_STATE_COLLECTION = new TypeReference<Collection<BaragonServiceState>>() {};
private final Random random;
private final Provider<List<String>> hostsProvider;
private final String contextPath;
private final Provider<Optional<String>> authkeyProvider;
private final HttpClient httpClient;
public BaragonServiceClient(String contextPath, HttpClient httpClient, List<String> hosts, Optional<String> authkey) {
this(contextPath, httpClient, ProviderUtils.<List<String>>of(ImmutableList.copyOf(hosts)), ProviderUtils.of(authkey));
}
public BaragonServiceClient(String contextPath, HttpClient httpClient, Provider<List<String>> hostsProvider, Provider<Optional<String>> authkeyProvider) {
this.httpClient = httpClient;
this.contextPath = contextPath;
this.hostsProvider = hostsProvider;
this.authkeyProvider = authkeyProvider;
this.random = new Random();
}
private String getHost() {
final List<String> hosts = hostsProvider.get();
return hosts.get(random.nextInt(hosts.size()));
}
private HttpRequest.Builder buildRequest(String uri) {
return buildRequest(uri, null);
}
private HttpRequest.Builder buildRequest(String uri, Map<String, String> queryParams) {
final HttpRequest.Builder builder = HttpRequest.newBuilder().setUrl(uri);
final Optional<String> maybeAuthkey = authkeyProvider.get();
if (maybeAuthkey.isPresent()) {
builder.setQueryParam("authkey").to(maybeAuthkey.get());
}
if ((queryParams != null) && (!queryParams.isEmpty())) {
for (Map.Entry<String, String> entry : queryParams.entrySet()) {
builder.setQueryParam(entry.getKey()).to(entry.getValue());
}
}
return builder;
}
private void checkResponse(String type, HttpResponse response) {
if (response.isError()) {
throw fail(type, response);
}
}
private BaragonClientException fail(String type, HttpResponse response) {
String body = "";
try {
body = response.getAsString();
} catch (Exception e) {
LOG.warn("Unable to read body", e);
}
String uri = "";
try {
uri = response.getRequest().getUrl().toString();
} catch (Exception e) {
LOG.warn("Unable to read uri", e);
}
throw new BaragonClientException(String.format("Failed '%s' action on Baragon (%s) - code: %s, %s", type, uri, response.getStatusCode(), body), response.getStatusCode());
}
private <T> Optional<T> getSingle(String uri, String type, String id, Class<T> clazz) {
return getSingle(uri, type, id, clazz, null);
}
private <T> Optional<T> getSingle(String uri, String type, String id, Class<T> clazz, Map<String, String> queryParams) {
checkNotNull(id, String.format("Provide a %s id", type));
LOG.debug("Getting {} {} from {}", type, id, uri);
final long start = System.currentTimeMillis();
HttpResponse response = httpClient.execute(buildRequest(uri, queryParams).build());
if (response.getStatusCode() == 404) {
return Optional.absent();
}
checkResponse(type, response);
LOG.debug("Got {} {} in {}ms", type, id, System.currentTimeMillis() - start);
return Optional.fromNullable(response.getAs(clazz));
}
private <T> Collection<T> getCollection(String uri, String type, TypeReference<Collection<T>> typeReference) {
LOG.debug("Getting all {} from {}", type, uri);
final long start = System.currentTimeMillis();
HttpResponse response = httpClient.execute(buildRequest(uri).build());
if (response.getStatusCode() == 404) {
return ImmutableList.of();
}
checkResponse(type, response);
LOG.debug("Got {} in {}ms", type, System.currentTimeMillis() - start);
return response.getAs(typeReference);
}
private <T> void delete(String uri, String type, String id, Map<String, String> queryParams) {
delete(uri, type, id, queryParams, Optional.<Class<T>>absent());
}
private <T> Optional<T> delete(String uri, String type, String id, Map<String, String> queryParams, Optional<Class<T>> clazz) {
LOG.debug("Deleting {} {} from {}", type, id, uri);
final long start = System.currentTimeMillis();
HttpRequest.Builder request = buildRequest(uri, queryParams).setMethod(Method.DELETE);
HttpResponse response = httpClient.execute(request.build());
if (response.getStatusCode() == 404) {
LOG.debug("{} ({}) was not found", type, id);
return Optional.absent();
}
checkResponse(type, response);
LOG.debug("Deleted {} ({}) from Baragon in %sms", type, id, System.currentTimeMillis() - start);
if (clazz.isPresent()) {
return Optional.of(response.getAs(clazz.get()));
}
return Optional.absent();
}
private <T> Optional<T> post(String uri, String type, Optional<?> body, Optional<Class<T>> clazz) {
return post(uri, type, body, clazz, Collections.<String, String>emptyMap());
}
private <T> Optional<T> post(String uri, String type, Optional<?> body, Optional<Class<T>> clazz, Map<String, String> queryParams) {
try {
HttpResponse response = post(uri, type, body, queryParams);
if (clazz.isPresent()) {
return Optional.of(response.getAs(clazz.get()));
}
} catch (Exception e) {
LOG.warn("Http post failed", e);
}
return Optional.absent();
}
private HttpResponse post(String uri, String type, Optional<?> body, Map<String, String> params) {
LOG.debug("Posting {} to {}", type, uri);
final long start = System.currentTimeMillis();
HttpRequest.Builder request = buildRequest(uri, params).setMethod(Method.POST);
if (body.isPresent()) {
request.setBody(body.get());
}
HttpResponse response = httpClient.execute(request.build());
checkResponse(type, response);
LOG.debug("Successfully posted {} in {}ms", type, System.currentTimeMillis() - start);
return response;
}
// BaragonService overall status
public Optional<BaragonServiceStatus> getBaragonServiceStatus(String hostname) {
final String uri = String.format(STATUS_FORMAT, hostname, contextPath);
return getSingle(uri, "status", "", BaragonServiceStatus.class);
}
public Optional<BaragonServiceStatus> getAnyBaragonServiceStatus() {
return getBaragonServiceStatus(getHost());
}
// BaragonService service states
public Collection<BaragonServiceState> getGlobalState() {
final String uri = String.format(STATE_FORMAT, getHost(), contextPath);
return getCollection(uri, "global state", BARAGON_SERVICE_STATE_COLLECTION);
}
public Optional<BaragonServiceState> getServiceState(String serviceId) {
final String uri = String.format(STATE_SERVICE_ID_FORMAT, getHost(), contextPath, serviceId);
return getSingle(uri, "service state", serviceId, BaragonServiceState.class);
}
public Optional<BaragonResponse> deleteService(String serviceId) {
final String uri = String.format(STATE_SERVICE_ID_FORMAT, getHost(), contextPath, serviceId);
return delete(uri, "service state", serviceId, Collections.<String, String>emptyMap(), Optional.of(BaragonResponse.class));
}
public Optional<BaragonResponse> reloadServiceConfigs(String serviceId){
final String uri = String.format(STATE_RELOAD_FORMAT, getHost(), contextPath, serviceId);
return post(uri, "service reload",Optional.absent(), Optional.of(BaragonResponse.class));
}
// BaragonService Workers
public Collection<String> getBaragonServiceWorkers() {
final String requestUri = String.format(WORKERS_FORMAT, getHost(), contextPath);
return getCollection(requestUri, "baragon service workers", STRING_COLLECTION);
}
// BaragonService load balancer group actions
public Collection<String> getLoadBalancerGroups() {
final String requestUri = String.format(LOAD_BALANCER_FORMAT, getHost(), contextPath);
return getCollection(requestUri, "load balancer groups", STRING_COLLECTION);
}
public Collection<BaragonGroup> getAllLoadBalancerGroups() {
final String requestUri = String.format(ALL_LOAD_BALANCER_GROUPS_FORMAT, getHost(), contextPath);
return getCollection(requestUri, "load balancer groups", BARAGON_GROUP_COLLECTION);
}
public Collection<BaragonAgentMetadata> getLoadBalancerGroupAgentMetadata(String loadBalancerGroupName) {
final String requestUri = String.format(LOAD_BALANCER_AGENTS_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return getCollection(requestUri, "load balancer agent metadata", BARAGON_AGENTS_COLLECTION);
}
public Collection<BaragonAgentMetadata> getLoadBalancerGroupKnownAgentMetadata(String loadBalancerGroupName) {
final String requestUri = String.format(LOAD_BALANCER_KNOWN_AGENTS_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return getCollection(requestUri, "load balancer known agent metadata", BARAGON_AGENTS_COLLECTION);
}
public void deleteLoadBalancerGroupKnownAgent(String loadBalancerGroupName, String agentId) {
final String requestUri = String.format(LOAD_BALANCER_DELETE_KNOWN_AGENT_FORMAT, getHost(), contextPath, loadBalancerGroupName, agentId);
delete(requestUri, "known agent", agentId, Collections.<String, String>emptyMap());
}
public BaragonGroup addTrafficSource(String loadBalancerGroupName, String source) {
final String requestUri = String.format(LOAD_BALANCER_TRAFFIC_SOURCE_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return post(requestUri, "add source", Optional.absent(), Optional.of(BaragonGroup.class), ImmutableMap.of("source", source)).get();
}
public Optional<BaragonGroup> removeTrafficSource(String loadBalancerGroupName, String source) {
final String requestUri = String.format(LOAD_BALANCER_TRAFFIC_SOURCE_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return delete(requestUri, "remove source", source, ImmutableMap.of("source", source), Optional.of(BaragonGroup.class));
}
public Optional<BaragonGroup> getGroupDetail(String loadBalancerGroupName) {
final String requestUri = String.format(LOAD_BALANCER_GROUP_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return getSingle(requestUri, "group detail", loadBalancerGroupName, BaragonGroup.class);
}
// BaragonService base path actions
public Collection<String> getOccupiedBasePaths(String loadBalancerGroupName) {
final String requestUri = String.format(LOAD_BALANCER_ALL_BASE_PATHS_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return getCollection(requestUri, "occupied base paths", STRING_COLLECTION);
}
public Optional<BaragonService> getServiceForBasePath(String loadBalancerGroupName, String basePath) {
final String requestUri = String.format(LOAD_BALANCER_BASE_PATH_FORMAT, getHost(), contextPath, loadBalancerGroupName);
return getSingle(requestUri, "service for base path", "", BaragonService.class, ImmutableMap.of("basePath", basePath));
}
public void clearBasePath(String loadBalancerGroupName, String basePath) {
final String requestUri = String.format(LOAD_BALANCER_BASE_PATH_FORMAT, getHost(), contextPath, loadBalancerGroupName);
delete(requestUri, "base path", "", ImmutableMap.of("basePath", basePath));
}
// BaragonService request actions
public Optional<BaragonResponse> getRequest(String requestId) {
final String uri = String.format(REQUEST_ID_FORMAT, getHost(), contextPath, requestId);
return getSingle(uri, "request", requestId, BaragonResponse.class);
}
public Optional<BaragonResponse> enqueueRequest(BaragonRequest request) {
final String uri = String.format(REQUEST_FORMAT, getHost(), contextPath);
return post(uri, "request", Optional.of(request), Optional.of(BaragonResponse.class));
}
public Optional<BaragonResponse> cancelRequest(String requestId) {
final String uri = String.format(REQUEST_ID_FORMAT, getHost(), contextPath, requestId);
return delete(uri, "request", requestId, Collections.<String, String>emptyMap(), Optional.of(BaragonResponse.class));
}
// BaragonService queued request actions
public Collection<QueuedRequestId> getQueuedRequests() {
final String uri = String.format(REQUEST_FORMAT, getHost(), contextPath);
return getCollection(uri, "queued requests", QUEUED_REQUEST_COLLECTION);
}
}