diff options
author | Martin Polden <mpolden@mpolden.no> | 2017-10-03 16:03:56 +0200 |
---|---|---|
committer | Martin Polden <mpolden@mpolden.no> | 2017-10-03 16:06:01 +0200 |
commit | f888cdc66f190a4decbcaac4e9288855585c3e6e (patch) | |
tree | 8bda332ad0199afb4c0e908126b6c1940f6596ab /controller-server | |
parent | 6e134d121f14dd940f84b25b5bc27ad22d80af2f (diff) |
Add trigger API
Diffstat (limited to 'controller-server')
2 files changed, 122 insertions, 20 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java index a02bb6b373e..7ea82881014 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java @@ -12,12 +12,14 @@ import com.yahoo.slime.Cursor; import com.yahoo.slime.Inspector; import com.yahoo.slime.Slime; import com.yahoo.vespa.config.SlimeUtils; +import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.BuildService.BuildJob; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobReport; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType; import com.yahoo.vespa.hosted.controller.restapi.ErrorResponse; +import com.yahoo.vespa.hosted.controller.restapi.Path; import com.yahoo.vespa.hosted.controller.restapi.SlimeJsonResponse; import com.yahoo.vespa.hosted.controller.restapi.StringResponse; import com.yahoo.vespa.hosted.controller.versions.VespaVersion; @@ -27,6 +29,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.List; import java.util.Optional; +import java.util.Scanner; import java.util.concurrent.Executor; import java.util.logging.Level; import java.util.logging.Logger; @@ -36,13 +39,14 @@ import java.util.logging.Logger; * on completion. * * @author bratseth + * @author mpolden */ +@SuppressWarnings("unused") // Handler public class ScrewdriverApiHandler extends LoggingRequestHandler { private final static Logger log = Logger.getLogger(ScrewdriverApiHandler.class.getName()); private final Controller controller; - // TODO: Remember to distinguish between PR jobs and component ones, by adding reports to the right jobs? public ScrewdriverApiHandler(Executor executor, AccessLog accessLog, Controller controller) { super(executor, accessLog); @@ -51,24 +55,13 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { @Override public HttpResponse handle(HttpRequest request) { + Method method = request.getMethod(); try { - Method method = request.getMethod(); - String path = request.getUri().getPath(); switch (method) { - case GET: switch (path) { - case "/screwdriver/v1/release/vespa": return vespaVersion(); - case "/screwdriver/v1/jobsToRun": return buildJobResponse(controller.applications().deploymentTrigger().buildSystem().jobs()); - default: return ErrorResponse.notFoundError(String.format( "No '%s' handler at '%s'", method, path)); - } - case POST: switch (path) { - case "/screwdriver/v1/jobreport": return handleJobReportPost(request); - default: return ErrorResponse.notFoundError(String.format( "No '%s' handler at '%s'", method, path)); - } - case DELETE: switch (path) { - case "/screwdriver/v1/jobsToRun": return buildJobResponse(controller.applications().deploymentTrigger().buildSystem().takeJobsToRun()); - default: return ErrorResponse.notFoundError(String.format( "No '%s' handler at '%s'", method, path)); - } - default: return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is not supported"); + case GET: return get(request); + case POST: return post(request); + case DELETE: return delete(request); + default: return ErrorResponse.methodNotAllowed("Method '" + method + "' is unsupported"); } } catch (IllegalArgumentException|IllegalStateException e) { return ErrorResponse.badRequest(Exceptions.toMessageString(e)); @@ -77,7 +70,57 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); } } - + + private HttpResponse get(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/screwdriver/v1/release/vespa")) { + return vespaVersion(); + } + if (path.matches("/screwdriver/v1/jobsToRun")) { + return buildJobs(controller.applications().deploymentTrigger().buildSystem().jobs()); + } + return notFound(request); + } + + private HttpResponse post(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/screwdriver/v1/jobreport")) { + return notifyJobCompletion(request); + } + if (path.matches("/screwdriver/v1/trigger/tenant/{tenant}/application/{application}")) { + return trigger(request, path.get("tenant"), path.get("application")); + } + return notFound(request); + } + + private HttpResponse delete(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/screwdriver/v1/jobsToRun")) { + return buildJobs(controller.applications().deploymentTrigger().buildSystem().takeJobsToRun()); + } + return notFound(request); + } + + private HttpResponse trigger(HttpRequest request, String tenantName, String applicationName) { + ApplicationId applicationId = ApplicationId.from(tenantName, applicationName, "default"); + Optional<Application> application = controller.applications().get(applicationId); + if (!application.isPresent()) { + return ErrorResponse.notFoundError("No such application '" + applicationId.toShortString() + "'"); + } + JobType jobType = Optional.of(asString(request.getData())) + .filter(s -> !s.isEmpty()) + .map(JobType::fromId) + .orElse(JobType.component); + // Since this is a manual operation we likely want it to trigger as soon as possible so we add it at to the + // front of the queue + controller.applications().deploymentTrigger().buildSystem().addJob(application.get().id(), jobType, true); + + Slime slime = new Slime(); + Cursor cursor = slime.setObject(); + cursor.setString("message", "Triggered " + jobType.id() + " for " + application.get().id()); + return new SlimeJsonResponse(slime); + } + private HttpResponse vespaVersion() { VespaVersion version = controller.versionStatus().version(controller.systemVersion()); if (version == null) @@ -92,7 +135,7 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { } - private HttpResponse buildJobResponse(List<BuildJob> buildJobs) { + private HttpResponse buildJobs(List<BuildJob> buildJobs) { Slime slime = new Slime(); Cursor buildJobArray = slime.setArray(); for (BuildJob buildJob : buildJobs) { @@ -103,7 +146,7 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { return new SlimeJsonResponse(slime); } - private HttpResponse handleJobReportPost(HttpRequest request) { + private HttpResponse notifyJobCompletion(HttpRequest request) { controller.applications().notifyJobCompletion(toJobReport(toSlime(request.getData()).get())); return new StringResponse("ok"); } @@ -134,4 +177,17 @@ public class ScrewdriverApiHandler extends LoggingRequestHandler { ); } + private static String asString(InputStream in) { + Scanner scanner = new Scanner(in).useDelimiter("\\A"); + if (scanner.hasNext()) { + return scanner.next(); + } + return ""; + } + + private static HttpResponse notFound(HttpRequest request) { + return ErrorResponse.notFoundError(String.format("No '%s' handler at '%s'", request.getMethod(), + request.getUri().getPath())); + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java index 5425b37d787..fcabaa28652 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java @@ -9,12 +9,14 @@ import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; import com.yahoo.config.provision.Zone; import com.yahoo.vespa.config.SlimeUtils; +import com.yahoo.vespa.curator.Lock; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobError; import com.yahoo.vespa.hosted.controller.application.DeploymentJobs.JobType; import com.yahoo.vespa.hosted.controller.application.JobStatus; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; +import com.yahoo.vespa.hosted.controller.deployment.BuildSystem; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; @@ -138,6 +140,50 @@ public class ScrewdriverApiTest extends ControllerContainerTest { assertFalse(jobStatus.isSuccess()); assertEquals(JobError.outOfCapacity, jobStatus.jobError().get()); } + + @Test + public void testTriggerJobForApplication() throws Exception { + ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); + BuildSystem buildSystem = tester.controller().applications().deploymentTrigger().buildSystem(); + tester.containerTester().updateSystemVersion(); + + Application app = tester.createApplication(); + try (Lock lock = tester.controller().applications().lock(app.id())) { + app = app.withProjectId(1); + tester.controller().applications().store(app, lock); + } + + // Unknown application + assertResponse(new Request("http://localhost:8080/screwdriver/v1/trigger/tenant/foo/application/bar", + new byte[0], Request.Method.POST), + 404, "{\"error-code\":\"NOT_FOUND\",\"message\":\"No such application 'foo.bar'\"}"); + + // Invalid job + assertResponse(new Request("http://localhost:8080/screwdriver/v1/trigger/tenant/" + + app.id().tenant().value() + "/application/" + app.id().application().value(), + "invalid".getBytes(StandardCharsets.UTF_8), Request.Method.POST), + 400, "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Unknown job id 'invalid'\"}"); + + // component is triggered if no job is specified in request body + assertResponse(new Request("http://localhost:8080/screwdriver/v1/trigger/tenant/" + + app.id().tenant().value() + "/application/" + app.id().application().value(), + new byte[0], Request.Method.POST), + 200, "{\"message\":\"Triggered component for tenant1.application1\"}"); + + assertFalse(buildSystem.jobs().isEmpty()); + assertEquals(JobType.component.id(), buildSystem.jobs().get(0).jobName()); + assertEquals(1L, buildSystem.jobs().get(0).projectId()); + buildSystem.takeJobsToRun(); + + // Triggers specific job when given + assertResponse(new Request("http://localhost:8080/screwdriver/v1/trigger/tenant/" + + app.id().tenant().value() + "/application/" + app.id().application().value(), + "staging-test".getBytes(StandardCharsets.UTF_8), Request.Method.POST), + 200, "{\"message\":\"Triggered staging-test for tenant1.application1\"}"); + assertFalse(buildSystem.jobs().isEmpty()); + assertEquals(JobType.stagingTest.id(), buildSystem.jobs().get(0).jobName()); + assertEquals(1L, buildSystem.jobs().get(0).projectId()); + } private void notifyCompletion(ApplicationId app, long projectId, JobType jobType, Optional<JobError> error) throws IOException { assertResponse(new Request("http://localhost:8080/screwdriver/v1/jobreport", |