All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.blazar.resources.ModuleBuildResource Maven / Gradle / Ivy

The newest version!
package com.hubspot.blazar.resources;

import com.google.common.base.Optional;
import com.hubspot.blazar.base.LogChunk;
import com.hubspot.blazar.base.ModuleBuild;
import com.hubspot.blazar.base.ModuleBuild.State;
import com.hubspot.blazar.data.service.ModuleBuildService;
import com.hubspot.horizon.AsyncHttpClient;
import com.hubspot.horizon.HttpRequest;
import com.hubspot.horizon.HttpResponse;
import com.hubspot.mesos.json.MesosFileChunkObject;
import com.hubspot.singularity.SingularityS3Log;
import com.hubspot.singularity.SingularityTaskHistory;
import com.hubspot.singularity.SingularityTaskHistoryUpdate;
import com.hubspot.singularity.SingularityTaskHistoryUpdate.SimplifiedTaskState;
import com.hubspot.singularity.client.SingularityClient;
import com.sun.jersey.api.NotFoundException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@Path("/modules/builds")
@Produces(MediaType.APPLICATION_JSON)
public class ModuleBuildResource {
  private static final Logger LOG = LoggerFactory.getLogger(ModuleBuildResource.class);

  private final ModuleBuildService moduleBuildService;
  private final SingularityClient singularityClient;
  private final AsyncHttpClient asyncHttpClient;

  @Inject
  public ModuleBuildResource(ModuleBuildService moduleBuildService,
                             SingularityClient singularityClient,
                             AsyncHttpClient asyncHttpClient) {
    this.moduleBuildService = moduleBuildService;
    this.singularityClient = singularityClient;
    this.asyncHttpClient = asyncHttpClient;
  }

  @GET
  @Path("/{id}")
  public Optional get(@PathParam("id") long moduleBuildId) {
    return moduleBuildService.get(moduleBuildId);
  }

  @PUT
  @Path("/{id}/start")
  public ModuleBuild start(@PathParam("id") long moduleBuildId, @QueryParam("taskId") Optional taskId) {
    ModuleBuild build = getBuildWithExpectedState(moduleBuildId, State.LAUNCHING);

    if (!taskId.isPresent()) {
      throw new IllegalArgumentException("Task ID is required");
    }

    ModuleBuild inProgress = build.withState(State.IN_PROGRESS).withTaskId(taskId.get());
    moduleBuildService.update(inProgress);
    return inProgress;
  }

  @PUT
  @Path("/{id}/success")
  public ModuleBuild completeSuccess(@PathParam("id") long moduleBuildId) {
    ModuleBuild build = getBuildWithExpectedState(moduleBuildId, State.IN_PROGRESS);

    ModuleBuild succeeded = build.withState(State.SUCCEEDED).withEndTimestamp(System.currentTimeMillis());
    moduleBuildService.update(succeeded);
    return succeeded;
  }

  @PUT
  @Path("/{id}/failure")
  public ModuleBuild completeFailure(@PathParam("id") long moduleBuildId) {
    ModuleBuild build = getBuildWithExpectedState(moduleBuildId, State.IN_PROGRESS);

    ModuleBuild failed = build.withState(State.FAILED).withEndTimestamp(System.currentTimeMillis());
    moduleBuildService.update(failed);
    return failed;
  }

  @GET
  @Path("/{id}/log")
  public LogChunk getLog(@PathParam("id") long moduleBuildId,
                         @QueryParam("offset") @DefaultValue("0") long offset,
                         @QueryParam("length") @DefaultValue("65536") long length) throws Exception {
    ModuleBuild build = getBuildWithError(moduleBuildId);

    Optional taskId = build.getTaskId();
    if (!taskId.isPresent()) {
      throw new NotFoundException("No taskId found for build: " + moduleBuildId);
    }

    String path = taskId.get() + "/service.log";
    Optional grep = Optional.absent();

    Optional chunk = singularityClient.readSandBoxFile(taskId.get(), path, grep, Optional.of(offset), Optional.of(length));
    if (chunk.isPresent()) {
      if (chunk.get().getData().isEmpty() && logCompleted(build)) {
        return new LogChunk(chunk.get().getData(), chunk.get().getOffset(), -1);
      } else {
        return new LogChunk(chunk.get().getData(), chunk.get().getOffset());
      }
    } else {
      SingularityS3Log s3Log = findS3ServiceLog(taskId.get());
      if (offset >= s3Log.getSize()) {
        return new LogChunk("", s3Log.getSize(), -1);
      }

      return readS3LogChunk(s3Log.getGetUrl(), offset, length);
    }
  }

  @GET
  @Path("/{id}/log/size")
  public Object getLogSize(@PathParam("id") long moduleBuildId) {
    ModuleBuild build = getBuildWithError(moduleBuildId);

    Optional taskId = build.getTaskId();
    if (!taskId.isPresent()) {
      throw new NotFoundException("No taskId found for build: " + moduleBuildId);
    }

    String path = taskId.get() + "/service.log";
    Optional absent = Optional.absent();
    Optional grep = Optional.absent();

    Optional chunk = singularityClient.readSandBoxFile(taskId.get(), path, grep, absent, absent);
    final long size;
    if (chunk.isPresent()) {
      size = chunk.get().getOffset();
    } else {
      size = findS3ServiceLog(taskId.get()).getSize();
    }

    return new Object() {

      public long getSize() {
        return size;
      }
    };
  }

  private SingularityS3Log findS3ServiceLog(String taskId) {
    Collection s3Logs = singularityClient.getTaskLogs(taskId);
    List serviceLogs = new ArrayList<>();
    for (SingularityS3Log s3Log : s3Logs) {
      if (s3Log.getGetUrl().contains("service.log")) {
        serviceLogs.add(s3Log);
      }
    }

    if (serviceLogs.isEmpty()) {
      throw new NotFoundException("No S3 log found for task " + taskId);
    } else if (serviceLogs.size() > 1) {
      throw new NotFoundException("Multiple S3 logs found for task " + taskId);
    } else {
      return serviceLogs.get(0);
    }
  }

  private LogChunk readS3LogChunk(String url, long offset, long length) throws Exception {
    HttpRequest request = HttpRequest.newBuilder()
        .setUrl(url)
        .addHeader("Range", String.format("bytes=%d-%d", offset, offset + length - 1))
        .build();

    HttpResponse response = asyncHttpClient.execute(request).get();
    if (response.isSuccess()) {
      return new LogChunk(response.getAsBytes(), offset);
    } else {
      String message = String.format("Error reading S3 log, status code %d, response %s", response.getStatusCode(), response.getAsString());
      LOG.warn(message);
      throw new WebApplicationException(Response.serverError().entity(message).type(MediaType.TEXT_PLAIN_TYPE).build());
    }
  }

  private boolean logCompleted(ModuleBuild build) {
    return build.getState().isComplete() && taskComplete(build.getTaskId().get());
  }

  private boolean taskComplete(String taskId) {
    Optional taskHistory = singularityClient.getHistoryForTask(taskId);
    if (!taskHistory.isPresent()) {
      return true;
    }

    SimplifiedTaskState taskState = SingularityTaskHistoryUpdate.getCurrentState(taskHistory.get().getTaskUpdates());
    return taskState == SimplifiedTaskState.DONE;
  }

  private ModuleBuild getBuildWithError(long moduleBuildId) {
    Optional maybeBuild = get(moduleBuildId);
    if (maybeBuild.isPresent()) {
      return maybeBuild.get();
    } else {
      throw new NotFoundException("No build found with id: " + moduleBuildId);
    }
  }

  private ModuleBuild getBuildWithExpectedState(long moduleBuildId, State expected) {
    ModuleBuild build = getBuildWithError(moduleBuildId);
    if (build.getState() == expected) {
      return build;
    } else {
      throw new IllegalStateException(String.format("Build is in state %s, expected %s", build.getState(), expected));
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy