/*******************************************************************************
* Copyright (c) 2012-2015 Codenvy, S.A.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Codenvy, S.A. - initial API and implementation
*******************************************************************************/
package org.eclipse.che.api.runner;
import org.eclipse.che.api.builder.dto.BuildTaskDescriptor;
import org.eclipse.che.api.core.ApiException;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.rest.OutputProvider;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.util.Cancellable;
import org.eclipse.che.api.core.util.ValueHolder;
import org.eclipse.che.api.runner.dto.RunnerMetric;
import org.eclipse.che.api.runner.internal.Constants;
import org.eclipse.che.api.runner.dto.ApplicationProcessDescriptor;
import org.eclipse.che.api.runner.dto.RunRequest;
import org.eclipse.che.dto.server.DtoFactory;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.UriBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Wraps RemoteRunnerProcess.
*
* @author andrew00x
*/
public class RunQueueTask implements Cancellable {
private final Long id;
private final RunRequest request;
private final Future<RemoteRunnerProcess> future;
private final ValueHolder<BuildTaskDescriptor> buildTaskHolder;
private final long created;
private final long waitingTimeout;
private final AtomicBoolean stopped = new AtomicBoolean(false);
private Long stopTime;
/* NOTE: don't use directly! Always use getter that makes copy of this UriBuilder. */
private final UriBuilder uriBuilder;
private UriBuilder getUriBuilder() {
return uriBuilder.clone();
}
/* ~~~~ */
private RemoteRunnerProcess myRemoteProcess;
RunQueueTask(Long id,
RunRequest request,
long waitingTimeout,
Future<RemoteRunnerProcess> future,
ValueHolder<BuildTaskDescriptor> buildTaskHolder,
UriBuilder uriBuilder) {
this.id = id;
this.future = future;
this.request = request;
this.waitingTimeout = waitingTimeout;
this.buildTaskHolder = buildTaskHolder;
this.uriBuilder = uriBuilder;
created = System.currentTimeMillis();
}
public Long getId() {
return id;
}
public RunRequest getRequest() {
return DtoFactory.getInstance().clone(request);
}
public long getCreationTime() {
return created;
}
public ApplicationProcessDescriptor getDescriptor() throws RunnerException, NotFoundException {
final DtoFactory dtoFactory = DtoFactory.getInstance();
ApplicationProcessDescriptor descriptor;
if (isStopped()) {
final List<RunnerMetric> runStats = new ArrayList<>(1);
if (stopTime != null) {
runStats.add(dtoFactory.createDto(RunnerMetric.class).withName(RunnerMetric.STOP_TIME)
.withValue(Long.toString(stopTime))
.withDescription("Time when application was stopped"));
}
descriptor = dtoFactory.createDto(ApplicationProcessDescriptor.class)
.withProcessId(id)
.withCreationTime(created)
.withStatus(ApplicationStatus.STOPPED)
.withRunStats(runStats);
} else if (future.isCancelled()) {
descriptor = dtoFactory.createDto(ApplicationProcessDescriptor.class)
.withProcessId(id)
.withCreationTime(created)
.withStatus(ApplicationStatus.CANCELLED);
} else {
final RemoteRunnerProcess remoteProcess = getRemoteProcess();
if (remoteProcess == null) {
final List<Link> links = new ArrayList<>(2);
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_GET_STATUS)
.withHref(getUriBuilder().path(RunnerService.class, "getStatus")
.build(request.getWorkspace(), id).toString()).withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_STOP)
.withHref(getUriBuilder().path(RunnerService.class, "stop")
.build(request.getWorkspace(), id).toString())
.withMethod("POST")
.withProduces(MediaType.APPLICATION_JSON));
final List<RunnerMetric> runStats = new ArrayList<>(2);
runStats.add(dtoFactory.createDto(RunnerMetric.class).withName(RunnerMetric.WAITING_TIME_LIMIT)
.withValue(Long.toString(created + waitingTimeout))
.withDescription("Waiting for start limit (ms)"));
final long lifetime = request.getLifetime();
runStats.add(dtoFactory.createDto(RunnerMetric.class).withName(RunnerMetric.LIFETIME)
.withValue(lifetime >= Integer.MAX_VALUE ? RunnerMetric.ALWAYS_ON
: Long.toString(TimeUnit.SECONDS.toMillis(lifetime)))
.withDescription("Application lifetime (ms)"));
descriptor = dtoFactory.createDto(ApplicationProcessDescriptor.class)
.withProcessId(id)
.withCreationTime(created)
.withStatus(ApplicationStatus.NEW)
.withRunStats(runStats)
.withLinks(links)
.withWorkspace(request.getWorkspace())
.withProject(request.getProject())
.withUserId(request.getUserId())
.withMemorySize(request.getMemorySize());
} else {
final ApplicationProcessDescriptor remoteDescriptor = remoteProcess.getApplicationProcessDescriptor();
// re-write some parameters, we are working as revers-proxy
descriptor = dtoFactory.clone(remoteDescriptor)
.withProcessId(id)
.withCreationTime(created)
.withMemorySize(request.getMemorySize())
.withLinks(rewriteKnownLinks(remoteDescriptor.getLinks()));
final long started = descriptor.getStartTime();
final long waitingTimeMillis = started > 0 ? started - created : System.currentTimeMillis() - created;
final List<RunnerMetric> runStats = descriptor.getRunStats();
runStats.add(dtoFactory.createDto(RunnerMetric.class).withName(RunnerMetric.WAITING_TIME)
.withValue(Long.toString(waitingTimeMillis))
.withDescription("Waiting for start duration (ms)"));
final long lifetime = request.getLifetime();
runStats.add(dtoFactory.createDto(RunnerMetric.class).withName(RunnerMetric.LIFETIME)
.withValue(lifetime >= Integer.MAX_VALUE ? RunnerMetric.ALWAYS_ON
: Long.toString(TimeUnit.SECONDS.toMillis(lifetime)))
.withDescription("Application lifetime (ms)"));
}
final BuildTaskDescriptor buildTaskDescriptor = buildTaskHolder.get();
if (buildTaskDescriptor != null) {
descriptor.setBuildStats(buildTaskDescriptor.getBuildStats());
}
}
return descriptor;
}
private List<Link> rewriteKnownLinks(List<Link> links) {
final List<Link> rewritten = new ArrayList<>();
for (Link link : links) {
if (Constants.LINK_REL_GET_STATUS.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(RunnerService.class, "getStatus").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_STOP.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(RunnerService.class, "stop").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_VIEW_LOG.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(RunnerService.class, "getLogs").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else if (Constants.LINK_REL_RUNNER_RECIPE.equals(link.getRel())) {
final Link copy = DtoFactory.getInstance().clone(link);
copy.setHref(getUriBuilder().path(RunnerService.class, "getRecipeFile").build(request.getWorkspace(), id).toString());
rewritten.add(copy);
} else {
rewritten.add(DtoFactory.getInstance().clone(link));
}
}
return rewritten;
}
@Override
public void cancel() throws Exception {
if (future.isCancelled()) {
return;
}
stopTime = System.currentTimeMillis();
doStop(getRemoteProcess());
}
public void stop() throws Exception {
if (stopped.compareAndSet(false, true)) {
cancel();
}
}
public boolean isStopped() throws RunnerException {
return stopped.get() || future.isCancelled();
}
public boolean isWaiting() {
return !future.isDone();
}
boolean isCancelled() {
return future.isCancelled();
}
private void doStop(RemoteRunnerProcess remoteProcess) throws RunnerException, NotFoundException {
if (remoteProcess != null) {
remoteProcess.stop();
} else {
future.cancel(true);
}
}
public void readLogs(OutputProvider output) throws IOException, RunnerException, NotFoundException {
final RemoteRunnerProcess remoteProcess = getRemoteProcess();
if (remoteProcess == null) {
throw new RunnerException("Application isn't started yet, logs aren't available");
}
remoteProcess.readLogs(output);
}
public void readRecipeFile(OutputProvider output) throws RunnerException, IOException, NotFoundException {
final RemoteRunnerProcess remoteProcess = getRemoteProcess();
if (remoteProcess == null) {
throw new RunnerException("Application isn't started yet, recipe file isn't available");
}
remoteProcess.readRecipeFile(output);
}
RemoteRunnerProcess getRemoteProcess() throws RunnerException, NotFoundException {
if (!future.isDone() || future.isCancelled()) {
return null;
}
if (myRemoteProcess == null) {
try {
myRemoteProcess = future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
final Throwable cause = e.getCause();
if (cause instanceof Error) {
throw (Error)cause; // lets caller to get Error as is
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else if (cause instanceof RunnerException) {
throw (RunnerException)cause;
} else if (cause instanceof NotFoundException) {
throw (NotFoundException)cause;
} else if (cause instanceof ApiException) {
throw new RunnerException(((ApiException)cause).getServiceError());
} else {
throw new RunnerException(cause.getMessage(), cause);
}
}
}
return myRemoteProcess;
}
@Override
public String toString() {
return "RunQueueTask{" +
"id=" + id +
", request=" + request +
'}';
}
}