summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMartin Polden <mpolden@mpolden.no>2017-10-03 16:03:56 +0200
committerMartin Polden <mpolden@mpolden.no>2017-10-03 16:06:01 +0200
commitf888cdc66f190a4decbcaac4e9288855585c3e6e (patch)
tree8bda332ad0199afb4c0e908126b6c1940f6596ab
parent6e134d121f14dd940f84b25b5bc27ad22d80af2f (diff)
Add trigger API
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiHandler.java96
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/screwdriver/ScrewdriverApiTest.java46
3 files changed, 124 insertions, 21 deletions
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
index bbd15707cde..7933a23c45f 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/BuildService.java
@@ -8,7 +8,8 @@ public interface BuildService {
/**
* Enqueue a job defined by "buildJob in an external build system, and return the outcome of the enqueue request.
- * This method should return @false only when a retry is in order, and @true otherwise, e.g., on succes, or for invalid jobs.
+ * This method should return @false only when a retry is in order, and @true otherwise, e.g., on success, or for
+ * invalid jobs.
*/
boolean trigger(BuildJob buildJob);
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",