/******************************************************************************* * 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.core.NotFoundException; import org.eclipse.che.api.core.rest.HttpJsonHelper; import org.eclipse.che.api.core.rest.HttpServletProxyResponse; import org.eclipse.che.api.core.rest.Service; 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.project.shared.EnvironmentId; import org.eclipse.che.api.project.shared.dto.RunnerEnvironment; import org.eclipse.che.api.project.shared.dto.RunnerEnvironmentLeaf; import org.eclipse.che.api.project.shared.dto.RunnerEnvironmentTree; import org.eclipse.che.api.runner.dto.ApplicationProcessDescriptor; import org.eclipse.che.api.runner.dto.ResourcesDescriptor; import org.eclipse.che.api.runner.dto.RunOptions; import org.eclipse.che.api.runner.dto.RunRequest; import org.eclipse.che.api.runner.dto.RunnerDescriptor; import org.eclipse.che.api.runner.internal.Constants; import org.eclipse.che.commons.env.EnvironmentContext; import org.eclipse.che.commons.lang.Pair; import org.eclipse.che.commons.user.User; import org.eclipse.che.dto.server.DtoFactory; import com.wordnik.swagger.annotations.Api; import com.wordnik.swagger.annotations.ApiOperation; import com.wordnik.swagger.annotations.ApiParam; import com.wordnik.swagger.annotations.ApiResponse; import com.wordnik.swagger.annotations.ApiResponses; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; 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 java.util.Iterator; import java.util.LinkedList; import java.util.List; /** * RESTful API for RunQueue. * * @author andrew00x */ @Api(value = "/runner", description = "Runner manager") @Path("/runner/{ws-id}") @Description("Runner REST API") public class RunnerService extends Service { private static final Logger LOG = LoggerFactory.getLogger(RunnerService.class); private static final String START = "{ \"recipe\":\""; private static final String END = "\" }"; @Inject private RunQueue runQueue; @ApiOperation(value = "Run project", notes = "Run selected project", response = ApplicationProcessDescriptor.class, position = 1) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GenerateLink(rel = Constants.LINK_REL_RUN) @Path("/run") @POST @Consumes(MediaType.APPLICATION_JSON) @Produces(MediaType.APPLICATION_JSON) public ApplicationProcessDescriptor run(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Project name", required = true) @Required @Description("project name") @QueryParam("project") String project, @ApiParam(value = "Run options") @Description("run options") RunOptions options) throws Exception { return runQueue.run(workspace, project, getServiceContext(), options).getDescriptor(); } @ApiOperation(value = "Get run status", notes = "Get status of a selected run process", response = ApplicationProcessDescriptor.class, position = 2) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/status/{id}") @Produces(MediaType.APPLICATION_JSON) public ApplicationProcessDescriptor getStatus(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Run ID", required = true) @PathParam("id") Long id) throws Exception { return runQueue.getTask(id).getDescriptor(); } @ApiOperation(value = "Get run processes", notes = "Get info on all running processes", response = ApplicationProcessDescriptor.class, responseContainer = "List", position = 3) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/processes") @Produces(MediaType.APPLICATION_JSON) public List<ApplicationProcessDescriptor> getRunningProcesses(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Project name", required = false) @Required @Description("project name") @QueryParam("project") String project) { if (project != null && !project.startsWith("/")) { project = '/' + project; } final List<ApplicationProcessDescriptor> processes = new LinkedList<>(); final User user = EnvironmentContext.getCurrent().getUser(); if (user != null) { final String userId = user.getId(); for (RunQueueTask task : runQueue.getTasks()) { final RunRequest request = task.getRequest(); if (request.getWorkspace().equals(workspace) && request.getProject().equals(project) && request.getUserId().equals(userId)) { try { processes.add(task.getDescriptor()); } catch (NotFoundException ignored) { // NotFoundException is possible and should not be treated as error in this case. Typically it occurs if slave // runner already cleaned up the task by its internal cleaner but RunQueue doesn't re-check yet slave runner and // doesn't have actual info about state of slave runner. } catch (RunnerException e) { // Decide ignore such error to be able show maximum available info. LOG.error(e.getMessage(), e); } } } } return processes; } @ApiOperation(value = "Stop run process", notes = "Stop running process", response = ApplicationProcessDescriptor.class, position = 4) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @POST @Path("/stop/{id}") @Produces(MediaType.APPLICATION_JSON) public ApplicationProcessDescriptor stop(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Run ID", required = true) @PathParam("id") Long id) throws Exception { final RunQueueTask task = runQueue.getTask(id); task.stop(); return task.getDescriptor(); } @ApiOperation(value = "Get logs", notes = "Get logs from a running application", position = 5) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/logs/{id}") public void getLogs(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Run ID", required = true) @PathParam("id") Long id, @Context HttpServletResponse httpServletResponse) throws Exception { // Response is written directly to the servlet request stream runQueue.getTask(id).readLogs(new HttpServletProxyResponse(httpServletResponse)); } @ApiOperation(value = "Get available RAM resources", notes = "Get RAM resources of a workspace: used and free RAM", response = ResourcesDescriptor.class, position = 6) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/resources") @Produces(MediaType.APPLICATION_JSON) public ResourcesDescriptor getResources(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace) throws Exception { return DtoFactory.getInstance().createDto(ResourcesDescriptor.class) .withTotalMemory(String.valueOf(runQueue.getTotalMemory(workspace, getServiceContext()))) .withUsedMemory(String.valueOf(runQueue.getUsedMemory(workspace))); } @ApiOperation(value = "Get available runner environments", notes = "Get available runner environments", response = RunnerEnvironmentTree.class, position = 7) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GenerateLink(rel = Constants.LINK_REL_AVAILABLE_RUNNERS) @GET @Path("/available") @Produces(MediaType.APPLICATION_JSON) public RunnerEnvironmentTree getRunnerEnvironments(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Project name") @Description("project name") @QueryParam("project") String project) { // Here merge environments from all know runner servers and represent them as tree. final DtoFactory dtoFactory = DtoFactory.getInstance(); final RunnerEnvironmentTree root = dtoFactory.createDto(RunnerEnvironmentTree.class).withDisplayName("system"); final List<RemoteRunnerServer> registerRunnerServers = runQueue.getRegisterRunnerServers(); for (Iterator<RemoteRunnerServer> itr = registerRunnerServers.iterator(); itr.hasNext(); ) { final RemoteRunnerServer runnerServer = itr.next(); if (!runnerServer.isAvailable()) { LOG.error("Runner server {} becomes unavailable", runnerServer.getBaseUrl()); itr.remove(); } } for (RemoteRunnerServer runnerServer : registerRunnerServers) { final String assignedWorkspace; final String assignedProject; try { assignedWorkspace = runnerServer.getAssignedWorkspace(); assignedProject = runnerServer.getAssignedProject(); } catch (RunnerException e) { LOG.error(e.getMessage(), e); continue; } if (((assignedWorkspace != null && assignedWorkspace.equals(workspace)) || assignedWorkspace == null) && ((assignedProject != null && assignedProject.equals(project)) || assignedProject == null)) { final List<RunnerDescriptor> runners; try { runners = runnerServer.getRunnerDescriptors(); } catch (RunnerException e) { LOG.error(e.getMessage(), e); continue; } for (RunnerDescriptor runnerDescriptor : runners) { for (RunnerEnvironment runnerEnvironment : runnerDescriptor.getEnvironments()) { RunnerEnvironmentTree node = root; for (String s : runnerDescriptor.getName().split("/")) { RunnerEnvironmentTree child = node.getNode(s); if (child == null) { child = dtoFactory.createDto(RunnerEnvironmentTree.class).withDisplayName(s); node.addNode(child); } node = child; } // Clone environment and use its id as display name and replace its id with new one in format scope:/runner/environment. // This is global id that shown scope of this environment, e.g. system , project, etc. final String unique = new EnvironmentId(EnvironmentId.Scope.system, runnerDescriptor.getName(), runnerEnvironment.getId()) .toString(); final String envId = runnerEnvironment.getId(); final RunnerEnvironmentLeaf environment = node.getEnvironment(envId); if (environment == null) { node.addLeaf(dtoFactory.createDto(RunnerEnvironmentLeaf.class) .withDisplayName(envId) .withEnvironment(dtoFactory.clone(runnerEnvironment).withId(unique))); } } } } } return root; } @ApiOperation(value = "Get runtime recipe", notes = "Get content of a Dockerfile used to 'cook' runtime environment", position = 8) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GET @Path("/recipe/{id}") public void getRecipeFile(@ApiParam(value = "Workspace ID", required = true) @PathParam("ws-id") String workspace, @ApiParam(value = "Run ID", required = true) @PathParam("id") Long id, @Context HttpServletResponse httpServletResponse) throws Exception { // Response write directly to the servlet request stream runQueue.getTask(id).readRecipeFile(new HttpServletProxyResponse(httpServletResponse)); } @ApiOperation(value = "Get recipe", notes = "Get content of a Dockerfile", position = 9) @ApiResponses(value = { @ApiResponse(code = 200, message = "OK"), @ApiResponse(code = 404, message = "Not found"), @ApiResponse(code = 500, message = "Internal Server Error")}) @GenerateLink(rel = Constants.LINK_REL_GET_RECIPE) @GET @Path("/recipe") @Produces(MediaType.TEXT_PLAIN) public String getRecipe(@QueryParam("id") String id) throws Exception { List<RemoteRunnerServer> servers = runQueue.getRegisterRunnerServers(); if (servers.isEmpty()) { throw new NotFoundException("Docker configuration wasn't found."); } RemoteRunnerServer server = servers.get(0); Link link = server.getLink(Constants.LINK_REL_GET_CURRENT_RECIPE); if (link == null) { throw new NotFoundException("Get recipe link wasn't found."); } // TODO needs to improve this code String json = HttpJsonHelper.requestString(link.getHref(), "GET", null, Pair.of("id", id)); json = json.substring(START.length()); json = json.substring(0, json.length() - END.length()); return json; } }