com.spotify.helios.client.HeliosClient Maven / Gradle / Ivy
/*
* Copyright (c) 2014 Spotify AB.
*
* 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.spotify.helios.client;
import com.google.common.base.Function;
import com.google.common.base.Optional;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.escape.Escaper;
import com.google.common.net.UrlEscapers;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.FutureFallback;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningScheduledExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.spotify.helios.common.HeliosException;
import com.spotify.helios.common.Json;
import com.spotify.helios.common.Resolver;
import com.spotify.helios.common.Version;
import com.spotify.helios.common.VersionCompatibility;
import com.spotify.helios.common.descriptors.Deployment;
import com.spotify.helios.common.descriptors.DeploymentGroup;
import com.spotify.helios.common.descriptors.HostStatus;
import com.spotify.helios.common.descriptors.Job;
import com.spotify.helios.common.descriptors.JobId;
import com.spotify.helios.common.descriptors.JobStatus;
import com.spotify.helios.common.descriptors.RolloutOptions;
import com.spotify.helios.common.protocol.CreateDeploymentGroupResponse;
import com.spotify.helios.common.protocol.CreateJobResponse;
import com.spotify.helios.common.protocol.DeploymentGroupStatusResponse;
import com.spotify.helios.common.protocol.HostDeregisterResponse;
import com.spotify.helios.common.protocol.JobDeleteResponse;
import com.spotify.helios.common.protocol.JobDeployResponse;
import com.spotify.helios.common.protocol.JobUndeployResponse;
import com.spotify.helios.common.protocol.RemoveDeploymentGroupResponse;
import com.spotify.helios.common.protocol.RollingUpdateRequest;
import com.spotify.helios.common.protocol.RollingUpdateResponse;
import com.spotify.helios.common.protocol.SetGoalResponse;
import com.spotify.helios.common.protocol.TaskStatusEvents;
import com.spotify.helios.common.protocol.VersionResponse;
import com.spotify.sshagentproxy.AgentProxies;
import com.spotify.sshagentproxy.AgentProxy;
import org.apache.http.client.utils.URIBuilder;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Strings.isNullOrEmpty;
import static com.google.common.util.concurrent.Futures.immediateFuture;
import static com.google.common.util.concurrent.Futures.transform;
import static com.google.common.util.concurrent.Futures.withFallback;
import static com.spotify.helios.common.VersionCompatibility.HELIOS_SERVER_VERSION_HEADER;
import static com.spotify.helios.common.VersionCompatibility.HELIOS_VERSION_STATUS_HEADER;
import static java.lang.String.format;
import static java.net.HttpURLConnection.HTTP_BAD_METHOD;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;
import static java.net.HttpURLConnection.HTTP_FORBIDDEN;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.util.Arrays.asList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.Executors.newScheduledThreadPool;
import static java.util.concurrent.TimeUnit.SECONDS;
public class HeliosClient implements Closeable {
private static final Logger log = LoggerFactory.getLogger(HeliosClient.class);
private final String user;
private final RequestDispatcher dispatcher;
private final AtomicBoolean versionWarningLogged = new AtomicBoolean();
public HeliosClient(final String user, final RequestDispatcher dispatcher) {
this.user = checkNotNull(user);
this.dispatcher = checkNotNull(dispatcher);
}
@Override
public void close() throws IOException {
dispatcher.close();
}
private URI uri(final String path) {
return uri(path, Collections.emptyMap());
}
private URI uri(final String path, final Map query) {
checkArgument(path.startsWith("/"));
final URIBuilder builder = new URIBuilder()
.setScheme("http")
.setHost("helios")
.setPath(path);
for (final Map.Entry q : query.entrySet()) {
builder.addParameter(q.getKey(), q.getValue());
}
builder.addParameter("user", user);
try {
return builder.build();
} catch (URISyntaxException e) {
throw Throwables.propagate(e);
}
}
private String path(final String resource, final Object... params) {
final String path;
final Escaper escaper = UrlEscapers.urlPathSegmentEscaper();
if (params.length == 0) {
path = resource;
} else {
final List encodedParams = Lists.newArrayList();
for (final Object param : params) {
encodedParams.add(escaper.escape(param.toString()));
}
path = format(resource, encodedParams.toArray());
}
return path;
}
private ListenableFuture request(final URI uri, final String method) {
return request(uri, method, null);
}
private ListenableFuture request(final URI uri, final String method,
final Object entity) {
final Map> headers = Maps.newHashMap();
final byte[] entityBytes;
headers.put(VersionCompatibility.HELIOS_VERSION_HEADER,
Collections.singletonList(Version.POM_VERSION));
if (entity != null) {
headers.put("Content-Type", singletonList("application/json"));
headers.put("Charset", singletonList("utf-8"));
entityBytes = Json.asBytesUnchecked(entity);
} else {
entityBytes = new byte[]{};
}
final ListenableFuture f = dispatcher.request(uri, method, entityBytes, headers);
return transform(f, new Function() {
@Override
public Response apply(final Response response) {
checkProtocolVersionStatus(response);
return response;
}
});
}
private void checkProtocolVersionStatus(final Response response) {
final VersionCompatibility.Status versionStatus = getVersionStatus(response);
if (versionStatus == null) {
log.debug("Server didn't return a version header!");
return; // shouldn't happen really
}
final String serverVersion = response.header(HELIOS_SERVER_VERSION_HEADER);
if ((versionStatus == VersionCompatibility.Status.MAYBE) &&
(versionWarningLogged.compareAndSet(false, true))) {
log.warn("Your Helios client version [{}] is ahead of the server [{}]. This will"
+ " probably work ok but there is the potential for weird things. If in doubt,"
+ " contact the Helios team if you think the cluster you're connecting to is out"
+ " of date and should be upgraded.", Version.POM_VERSION, serverVersion);
}
}
private VersionCompatibility.Status getVersionStatus(final Response response) {
final String status = response.header(HELIOS_VERSION_STATUS_HEADER);
if (status != null) {
return VersionCompatibility.Status.valueOf(status);
}
return null;
}
private ListenableFuture get(final URI uri, final TypeReference typeReference) {
return get(uri, Json.type(typeReference));
}
private ListenableFuture get(final URI uri, final Class clazz) {
return get(uri, Json.type(clazz));
}
private ListenableFuture get(final URI uri, final JavaType javaType) {
return transform(request(uri, "GET"), new ConvertResponseToPojo(javaType));
}
private ListenableFuture put(final URI uri) {
return status(request(uri, "PUT"));
}
public ListenableFuture deploy(final Deployment job, final String host) {
return deploy(job, host, "");
}
public ListenableFuture deploy(final Deployment job, final String host,
final String token) {
final Set deserializeReturnCodes = ImmutableSet.of(HTTP_OK, HTTP_NOT_FOUND,
HTTP_BAD_METHOD,
HTTP_BAD_REQUEST,
HTTP_FORBIDDEN);
return transform(request(uri(path("/hosts/%s/jobs/%s", host, job.getJobId()),
ImmutableMap.of("token", token)),
"PUT", job),
ConvertResponseToPojo.create(JobDeployResponse.class, deserializeReturnCodes));
}
public ListenableFuture setGoal(final Deployment job, final String host) {
return setGoal(job, host, "");
}
public ListenableFuture setGoal(final Deployment job, final String host,
final String token) {
return transform(request(uri(path("/hosts/%s/jobs/%s", host, job.getJobId()),
ImmutableMap.of("token", token)),
"PATCH", job),
ConvertResponseToPojo.create(SetGoalResponse.class,
ImmutableSet.of(HTTP_OK, HTTP_NOT_FOUND,
HTTP_FORBIDDEN)));
}
private ListenableFuture status(final ListenableFuture req) {
return transform(req,
new Function() {
@Override
public Integer apply(final Response reply) {
return reply.status();
}
});
}
public ListenableFuture deployment(final String host, final JobId job) {
return get(uri(path("/hosts/%s/jobs/%s", host, job)), Deployment.class);
}
public ListenableFuture hostStatus(final String host) {
return hostStatus(host, Collections.emptyMap());
}
public ListenableFuture
hostStatus(final String host, final Map queryParams) {
return get(uri(path("/hosts/%s/status", host), queryParams), HostStatus.class);
}
public ListenableFuture
© 2015 - 2025 Weber Informatics LLC | Privacy Policy