Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.github.dekobon.CloudApi Maven / Gradle / Ivy
package com.github.dekobon;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.dekobon.config.ConfigContext;
import com.github.dekobon.domain.Instance;
import com.github.dekobon.domain.InstanceFilter;
import com.github.dekobon.exceptions.InstanceGoneMissingException;
import com.github.dekobon.http.CloudApiConnectionFactory;
import com.github.dekobon.http.CloudApiResponseHandler;
import com.github.dekobon.http.HttpCollectionResponse;
import com.github.dekobon.http.JsonEntity;
import com.github.dekobon.json.CloudApiObjectMapper;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.*;
import static com.github.dekobon.CloudApiHttpHeaders.X_QUERY_LIMIT;
import static com.github.dekobon.CloudApiHttpHeaders.X_RESOURCE_COUNT;
import static com.github.dekobon.CloudApiUtils.firstNonNull;
import static org.apache.http.HttpStatus.*;
/**
* @author Elijah Zupancic
* @since 1.0.0
*/
public class CloudApi {
private final ConfigContext config;
private final Logger logger = LoggerFactory.getLogger(getClass());
private final CloudApiConnectionFactory connectionFactory;
private final ObjectMapper mapper = new CloudApiObjectMapper();
private final CloudApiResponseHandler> headerInstanceHandler;
private final CloudApiResponseHandler> listInstanceHandler;
private final CloudApiResponseHandler createInstanceHandler;
private final CloudApiResponseHandler deleteInstanceHandler;
private final CloudApiResponseHandler findInstanceHandler;
private final CloudApiResponseHandler> tagsHandler;
public CloudApi(ConfigContext config) {
this.config = config;
this.connectionFactory = new CloudApiConnectionFactory(config);
this.headerInstanceHandler = new CloudApiResponseHandler<>(
"get headers", mapper, new TypeReference>() { }, SC_OK, true
);
this.listInstanceHandler = new CloudApiResponseHandler<>(
"list instances", mapper, new TypeReference>() {}, SC_OK, false
);
this.createInstanceHandler = new CloudApiResponseHandler<>(
"create instance", mapper, new TypeReference() {}, SC_CREATED, false
);
this.deleteInstanceHandler = new CloudApiResponseHandler<>(
"delete instance", mapper, new TypeReference() {}, SC_NO_CONTENT, false
);
this.findInstanceHandler = new CloudApiResponseHandler<>(
"find instance", mapper, new TypeReference() {},
new int[] { SC_OK, SC_GONE }, true
);
this.tagsHandler = new CloudApiResponseHandler<>(
"tag instance", mapper, new TypeReference>() {}, SC_OK, false
);
}
public Iterator listInstances() throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return listInstances(context);
}
}
public Iterator listInstances(final CloudApiConnectionContext context) throws IOException {
return listInstances(context, new InstanceFilter());
}
public Iterator listInstances(final CloudApiConnectionContext context,
final InstanceFilter filter) throws IOException {
Objects.requireNonNull(context, "Context object must be present");
Objects.requireNonNull(context, "Filter object must be present");
final List filterParams = FilterEncoders.urlParamsFromFilter(filter);
final String path = String.format("/%s/machines", config.getUser());
final HttpClient client = context.getHttpClient();
final HttpHead head = connectionFactory.head(path, filterParams);
/* We first perform a head request because we can use it to determine if any results are going
* to be returned. If there are no results, we can just return an empty collection and give up. */
final Map headHeaders = client.execute(head, headerInstanceHandler, context.getHttpContext());
final int headResourceCount = resourceCount(headHeaders);
// -1 indicates error, 1+ indicates values present
if (headResourceCount == 0) {
return Collections.emptyIterator();
}
final HttpGet get = connectionFactory.get(path, filterParams);
@SuppressWarnings("unchecked")
final HttpCollectionResponse result =
(HttpCollectionResponse)client.execute(
get, listInstanceHandler, context.getHttpContext());
final HttpResponse response = result.getResponse();
final String resourceCountVal = response.getFirstHeader(X_RESOURCE_COUNT).getValue();
@SuppressWarnings("ConstantConditions")
final int resourceCount = Integer.parseInt(firstNonNull(resourceCountVal, "0"));
final String queryLimitVal = response.getFirstHeader(X_QUERY_LIMIT).getValue();
@SuppressWarnings("ConstantConditions")
final int queryLimit = Integer.parseInt(firstNonNull(queryLimitVal, "1000"));
if (resourceCount < queryLimit) {
logger.info("Total instances: {}", result.size());
return result.getWrapped().iterator();
}
// TODO: Add code for handling more instances than the query limit
return result.iterator();
}
private int resourceCount(final Map headers) {
final int UNAVAILABLE = -1;
if (headers == null) {
return UNAVAILABLE;
}
final Header header = headers.get(X_RESOURCE_COUNT);
if (header != null) {
final String headResourceCountVal = header.getValue();
@SuppressWarnings("ConstantConditions")
// We default to -1 in order to indicate an error
final int headResourceCount = Integer.parseInt(
firstNonNull(headResourceCountVal, String.valueOf(UNAVAILABLE)));
return headResourceCount;
}
return UNAVAILABLE;
}
public CloudApiConnectionContext createConnectionContext() {
return new CloudApiConnectionContext(connectionFactory);
}
public Instance create(final Instance instance) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return create(context, instance);
}
}
public Instance create(final CloudApiConnectionContext context,
final Instance instance) throws IOException {
Objects.requireNonNull(context, "Context object must be present");
Objects.requireNonNull(instance, "Instance must be present");
Objects.requireNonNull(instance.getPackageId(), "Package id must be present");
Objects.requireNonNull(instance.getImage(), "Image id must be present");
final String path = String.format("/%s/machines", config.getUser());
final HttpPost post = connectionFactory.post(path);
HttpEntity entity = new JsonEntity(mapper, instance);
post.setEntity(entity);
final HttpClient client = context.getHttpClient();
final Instance result = client.execute(post,
createInstanceHandler, context.getHttpContext());
logger.info("Created new instance: {}", result.getId());
return result;
}
public void delete(final Instance instance) throws IOException {
Objects.requireNonNull(instance, "Instance must be present");
delete(instance.getId());
}
public void delete(final UUID instanceId) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
delete(context, instanceId);
}
}
public void delete(final CloudApiConnectionContext context,
final Instance instance) throws IOException {
Objects.requireNonNull(instance, "Instance must be present");
delete(context, instance.getId());
}
public void delete(final CloudApiConnectionContext context,
final UUID instanceId) throws IOException {
Objects.requireNonNull(context, "Context object must be present");
Objects.requireNonNull(instanceId, "Instance id to be deleted must be present");
final String path = String.format("/%s/machines/%s",
config.getUser(), instanceId);
final HttpDelete delete = connectionFactory.delete(path);
final HttpClient client = context.getHttpClient();
client.execute(delete, deleteInstanceHandler, context.getHttpContext());
logger.info("Deleted instance: {}", instanceId);
}
public Instance waitForStateChange(final UUID instanceId,
final String initialState,
final long maxWaitTimeMs,
final long waitIntervalMs) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return waitForStateChange(context, instanceId, initialState, maxWaitTimeMs, waitIntervalMs);
}
}
public Instance waitForStateChange(final CloudApiConnectionContext context,
final UUID instanceId,
final String initialState,
final long maxWaitTimeMs,
final long waitIntervalMs) throws IOException {
Objects.requireNonNull(instanceId, "Instance id must be present");
Objects.requireNonNull(initialState, "Initial state value must be present");
if (maxWaitTimeMs < 1) {
throw new IllegalArgumentException("Maximum wait time must be 1 millisecond "
+ "or greater");
}
Instance lastPoll = findMachineById(context, instanceId);
if (lastPoll == null) {
return null;
}
// Don't even bother sleeping if the state was never set
if (!lastPoll.getState().equals(initialState)) {
logger.debug("State changed from [{}] to [{}]- no longer waiting",
initialState, lastPoll.getState());
return lastPoll;
}
long waited = 0;
while (true) {
try {
Thread.sleep(waitIntervalMs);
waited += waitIntervalMs;
} catch (InterruptedException e) {
return null;
}
lastPoll = findMachineById(context, instanceId);
if (lastPoll == null) {
final String msg = String.format("The instance [%s] was successfully polled "
+ "previously, but is no longer available. Maybe it was deleted?",
instanceId);
throw new InstanceGoneMissingException(msg);
}
if (!lastPoll.getState().equals(initialState)) {
logger.debug("State changed from [{}] to [{}] - no longer waiting",
initialState, lastPoll.getState());
break;
}
if (waited > maxWaitTimeMs) {
logger.debug("Exceeded maximum wait time [{} ms] for state change - "
+ "no longer waiting", maxWaitTimeMs);
break;
}
}
return lastPoll;
}
public Instance findMachineById(final UUID instanceId) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return findMachineById(context, instanceId);
}
}
public Instance findMachineById(final CloudApiConnectionContext context,
final UUID instanceId) throws IOException {
final HttpClient client = context.getHttpClient();
final String path = String.format("/%s/machines/%s",
config.getUser(), instanceId);
final HttpGet get = connectionFactory.get(path);
return client.execute(get, findInstanceHandler, context.getHttpContext());
}
public Map addTags(final UUID instanceId,
final Map tags) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return addTags(context, instanceId, tags);
}
}
public Map addTags(final CloudApiConnectionContext context,
final UUID instanceId,
final Map tags) throws IOException {
Objects.requireNonNull(context, "Context object must be present");
Objects.requireNonNull(instanceId, "Instance id must be present");
Objects.requireNonNull(tags, "Tags to add must be present");
if (tags.isEmpty()) {
return Collections.emptyMap();
}
final String path = String.format("/%s/machines/%s/tags", config.getUser(), instanceId);
final HttpPost post = connectionFactory.post(path);
final HttpEntity entity = new JsonEntity(mapper, tags);
post.setEntity(entity);
final HttpClient client = context.getHttpClient();
final Map result = client.execute(post,
tagsHandler, context.getHttpContext());
if (logger.isInfoEnabled()) {
logger.info("Add/updated [%d] tags to instance [{}]", result.size());
}
return result;
}
public Map replaceTags(final UUID instanceId,
final Map tags) throws IOException {
try (CloudApiConnectionContext context = createConnectionContext()) {
return replaceTags(context, instanceId, tags);
}
}
public Map replaceTags(final CloudApiConnectionContext context,
final UUID instanceId,
final Map tags) throws IOException {
Objects.requireNonNull(context, "Context object must be present");
Objects.requireNonNull(instanceId, "Instance id must be present");
Objects.requireNonNull(tags, "Tags to replace must be present");
final String path = String.format("/%s/machines/%s/tags", config.getUser(), instanceId);
final HttpPut put = connectionFactory.put(path);
final HttpEntity entity = new JsonEntity(mapper, tags);
put.setEntity(entity);
final HttpClient client = context.getHttpClient();
final Map result = client.execute(put,
tagsHandler, context.getHttpContext());
if (logger.isInfoEnabled()) {
logger.info("Replaced all tags on instance [{}] with [%d] tags", result.size());
}
return result;
}
public CloudApiConnectionFactory getConnectionFactory() {
return connectionFactory;
}
}