/*******************************************************************************
* 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.internal;
import org.eclipse.che.api.core.NotFoundException;
import org.eclipse.che.api.core.rest.Service;
import org.eclipse.che.api.core.rest.ServiceContext;
import org.eclipse.che.api.core.rest.annotations.Description;
import org.eclipse.che.api.core.rest.annotations.GenerateLink;
import org.eclipse.che.api.core.rest.annotations.Required;
import org.eclipse.che.api.core.rest.shared.dto.Link;
import org.eclipse.che.api.core.rest.shared.dto.ServiceDescriptor;
import org.eclipse.che.api.core.util.SystemInfo;
import org.eclipse.che.api.project.shared.dto.RunnerEnvironment;
import org.eclipse.che.api.runner.ApplicationStatus;
import org.eclipse.che.api.runner.RunnerException;
import org.eclipse.che.api.runner.dto.RunRequest;
import org.eclipse.che.api.runner.dto.RunnerDescriptor;
import org.eclipse.che.api.runner.dto.RunnerServerDescriptor;
import org.eclipse.che.api.runner.dto.ServerState;
import org.eclipse.che.api.runner.dto.ApplicationProcessDescriptor;
import org.eclipse.che.api.runner.dto.PortMapping;
import org.eclipse.che.api.runner.dto.RunnerState;
import org.eclipse.che.dto.server.DtoFactory;
import com.google.common.io.Files;
import javax.annotation.CheckForNull;
import javax.inject.Inject;
import javax.inject.Named;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import java.io.PrintWriter;
import java.nio.charset.Charset;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* RESTful API for slave-runners.
*
* @author andrew00x
*/
@Description("Internal Runner REST API")
@Path("internal/runner")
public class SlaveRunnerService extends Service {
private static final String DOCKERFILES_REPO = "runner.docker.dockerfiles_repo";
private static final String DOCKER_FILE_NAME = "/Dockerfile";
private static final String SYSTEM_PREFIX = "system:/";
@com.google.inject.Inject(optional = true)
@Named(Constants.RUNNER_ASSIGNED_TO_WORKSPACE)
private static String assignedWorkspace;
@com.google.inject.Inject(optional = true)
@Named(Constants.RUNNER_ASSIGNED_TO_PROJECT)
private static String assignedProject;
@Inject
private RunnerRegistry runners;
@Inject
private ResourceAllocators allocators;
@Inject
@Named(DOCKERFILES_REPO)
@CheckForNull
private String dockerfilesRepository;
@GenerateLink(rel = Constants.LINK_REL_RUN)
@Path("run")
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public ApplicationProcessDescriptor run(@Description("Parameters for run task in JSON format") RunRequest request) throws Exception {
final Runner myRunner = getRunner(request.getRunner());
final RunnerProcess process = myRunner.execute(request);
return getDescriptor(process, getServiceContext()).withRunStats(myRunner.getStats(process.getId()));
}
@GET
@Path("status/{runner:.*}/{id}")
@Produces(MediaType.APPLICATION_JSON)
public ApplicationProcessDescriptor getStatus(@PathParam("runner") String runner, @PathParam("id") Long id) throws Exception {
final Runner myRunner = getRunner(runner);
final RunnerProcess process = myRunner.getProcess(id);
return getDescriptor(process, getServiceContext()).withRunStats(myRunner.getStats(id));
}
@POST
@Path("stop/{runner:.*}/{id}")
@Produces(MediaType.APPLICATION_JSON)
public ApplicationProcessDescriptor stop(@PathParam("runner") String runner, @PathParam("id") Long id) throws Exception {
final Runner myRunner = getRunner(runner);
final RunnerProcess process = myRunner.getProcess(id);
process.stop();
return getDescriptor(process, getServiceContext()).withRunStats(myRunner.getStats(id));
}
@GET
@Path("logs/{runner:.*}/{id}")
public void getLogs(@PathParam("runner") String runner,
@PathParam("id") Long id,
@Context HttpServletResponse httpServletResponse) throws Exception {
final Runner myRunner = getRunner(runner);
final RunnerProcess process = myRunner.getProcess(id);
final Throwable error = process.getError();
if (error != null) {
final PrintWriter output = httpServletResponse.getWriter();
httpServletResponse.setContentType("text/plain");
if (error instanceof RunnerException) {
// expect ot have nice messages from our API
output.write(error.getMessage());
} else {
error.printStackTrace(output);
}
output.flush();
} else {
final ApplicationLogger logger = process.getLogger();
final PrintWriter output = httpServletResponse.getWriter();
httpServletResponse.setContentType(logger.getContentType());
logger.getLogs(output);
output.flush();
}
}
@GET
@Path("recipe/{runner:.*}/{id}")
public void getRecipeFile(@PathParam("runner") String runner,
@PathParam("id") Long id,
@Context HttpServletResponse httpServletResponse) throws Exception {
final Runner myRunner = getRunner(runner);
final RunnerProcess process = myRunner.getProcess(id);
final java.io.File recipeFile = process.getConfiguration().getRecipeFile();
if (recipeFile == null) {
throw new NotFoundException("Recipe file isn't available. ");
}
final PrintWriter output = httpServletResponse.getWriter();
httpServletResponse.setContentType("text/plain");
Files.copy(recipeFile, Charset.forName("UTF-8"), output);
output.flush();
}
@GenerateLink(rel = Constants.LINK_REL_SERVER_STATE)
@GET
@Path("server-state")
@Produces(MediaType.APPLICATION_JSON)
public ServerState getServerState() {
return DtoFactory.getInstance().createDto(ServerState.class)
.withCpuPercentUsage(SystemInfo.cpu())
.withTotalMemory(allocators.totalMemory())
.withFreeMemory(allocators.freeMemory());
}
@GenerateLink(rel = Constants.LINK_REL_AVAILABLE_RUNNERS)
@GET
@Path("available")
@Produces(MediaType.APPLICATION_JSON)
public List<RunnerDescriptor> getAvailableRunners() {
final Set<Runner> all = runners.getAll();
final List<RunnerDescriptor> list = new LinkedList<>();
final DtoFactory dtoFactory = DtoFactory.getInstance();
for (Runner runner : all) {
list.add(dtoFactory.createDto(RunnerDescriptor.class)
.withName(runner.getName())
.withDescription(runner.getDescription())
.withEnvironments(runner.getEnvironments()));
}
return list;
}
@GenerateLink(rel = Constants.LINK_REL_RUNNER_STATE)
@GET
@Path("state")
@Produces(MediaType.APPLICATION_JSON)
public RunnerState getRunnerState(@Required
@Description("Name of the runner")
@QueryParam("runner") String runner) throws Exception {
final Runner myRunner = getRunner(runner);
return DtoFactory.getInstance().createDto(RunnerState.class)
.withName(myRunner.getName())
.withStats(myRunner.getStats())
.withServerState(getServerState());
}
@GenerateLink(rel = Constants.LINK_REL_RUNNER_ENVIRONMENTS)
@GET
@Path("environments")
@Produces(MediaType.APPLICATION_JSON)
public List<RunnerEnvironment> getRunnerEnvironments(@Required
@Description("Name of the runner")
@QueryParam("runner") String runner) throws Exception {
return getRunner(runner).getEnvironments();
}
private Runner getRunner(String name) throws NotFoundException {
final Runner myRunner = runners.get(name);
if (myRunner == null) {
throw new NotFoundException(String.format("Unknown runner %s", name));
}
return myRunner;
}
private ApplicationProcessDescriptor getDescriptor(RunnerProcess process, ServiceContext restfulRequestContext) throws RunnerException {
final ApplicationStatus status = process.getError() == null ? (process.isCancelled() ? ApplicationStatus.CANCELLED
: (process.isStopped()
? ApplicationStatus.STOPPED
: (process.isStarted()
? ApplicationStatus.RUNNING
: ApplicationStatus.NEW)))
: ApplicationStatus.FAILED;
final List<Link> links = new LinkedList<>();
final UriBuilder servicePathBuilder = restfulRequestContext.getServiceUriBuilder();
final DtoFactory dtoFactory = DtoFactory.getInstance();
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_GET_STATUS)
.withHref(servicePathBuilder.clone().path(getClass(), "getStatus")
.build(process.getRunner(), process.getId()).toString())
.withMethod("GET")
.withProduces(MediaType.APPLICATION_JSON));
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_VIEW_LOG)
.withHref(servicePathBuilder.clone().path(getClass(), "getLogs")
.build(process.getRunner(), process.getId()).toString())
.withMethod("GET"));
switch (status) {
case NEW:
case RUNNING:
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_STOP)
.withHref(servicePathBuilder.clone().path(getClass(), "stop")
.build(process.getRunner(), process.getId()).toString())
.withMethod("POST")
.withProduces(MediaType.APPLICATION_JSON));
break;
}
final RunnerConfiguration configuration = process.getConfiguration();
final RunRequest request = configuration.getRequest();
final java.io.File recipeFile = configuration.getRecipeFile();
if (recipeFile != null) {
links.add(dtoFactory.createDto(Link.class)
.withRel(Constants.LINK_REL_RUNNER_RECIPE)
.withHref(servicePathBuilder.clone().path(getClass(), "getRecipeFile")
.build(process.getRunner(), process.getId()).toString())
.withMethod("GET")
.withProduces(MediaType.TEXT_PLAIN));
}
final List<Link> additionalLinks = new LinkedList<>();
PortMapping portMapping = null;
switch (status) {
case NEW:
case RUNNING:
for (Link link : configuration.getLinks()) {
additionalLinks.add(dtoFactory.clone(link));
}
final Map<String, String> ports = configuration.getPortMapping();
if (!ports.isEmpty()) {
portMapping = dtoFactory.createDto(PortMapping.class).withHost(configuration.getHost()).withPorts(new HashMap<>(ports));
}
break;
default:
for (Link link : configuration.getLinks()) {
if ("web url".equals(link.getRel()) || "shell url".equals(link.getRel())) {
// Hide web and shell links if application is not running.
continue;
}
additionalLinks.add(dtoFactory.clone(link));
}
break;
}
links.addAll(additionalLinks);
return dtoFactory.createDto(ApplicationProcessDescriptor.class)
.withProcessId(process.getId())
.withStatus(status)
.withStartTime(process.getStartTime())
.withStopTime(process.getStopTime())
.withLinks(links)
.withWorkspace(request.getWorkspace())
.withProject(request.getProject())
.withUserId(request.getUserId())
.withDebugHost(configuration.getDebugHost())
.withDebugPort(configuration.getDebugPort())
.withPortMapping(portMapping);
}
@Override
protected ServiceDescriptor createServiceDescriptor() {
return DtoFactory.getInstance().createDto(RunnerServerDescriptor.class).withAssignedWorkspace(assignedWorkspace)
.withAssignedProject(assignedProject);
}
@GenerateLink(rel = Constants.LINK_REL_GET_CURRENT_RECIPE)
@GET
@Path("/recipe")
@Produces(MediaType.APPLICATION_JSON)
public Response getRecipe(@QueryParam("id") String id) throws Exception {
java.nio.file.Path dockerParentPath = Paths.get(dockerfilesRepository);
if (dockerfilesRepository == null || !java.nio.file.Files.exists(dockerParentPath) ||
!java.nio.file.Files.isDirectory(dockerParentPath)) {
throw new NotFoundException(
"The configuration of docker repository wasn't found or some problem with configuration was found.");
}
final String path = id.replace(SYSTEM_PREFIX, "") + DOCKER_FILE_NAME;
java.nio.file.Path dockerFile = Paths.get(dockerParentPath.toAbsolutePath().toString(), path);
if (!java.nio.file.Files.exists(dockerFile)) {
throw new NotFoundException("The docker file with given id wasn't found.");
}
return Response.ok("{ \"recipe\":\"" +
new String(java.nio.file.Files.readAllBytes(dockerFile)) +
"\" }",
MediaType.APPLICATION_JSON_TYPE)
.build();
}
}