summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorJon Marius Venstad <jvenstad@yahoo-inc.com>2019-01-07 16:46:47 +0100
committerJon Marius Venstad <jvenstad@yahoo-inc.com>2019-01-07 16:46:47 +0100
commit956336a5a1022957946a79375fbefa08a02eff3b (patch)
tree77bc77546bcc75e9f7992acb4b5954dede7f380c /controller-server
parente4648829edc0594f41c589585be63c77c3c1985c (diff)
Serve 302 badge URLs through application API
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java1
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java63
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java26
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java66
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java4
6 files changed, 184 insertions, 2 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index eb86f0c2919..56b7ee44a66 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -354,6 +354,7 @@ public class ApplicationController {
// Update application with information from application package
if ( ! preferOldestVersion && ! application.get().deploymentJobs().deployedInternally())
+ // TODO jvenstad: Store only on submissions (not on deployments to dev!!)
application = storeWithUpdatedConfig(application, applicationPackage);
// Assign global rotation
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java
new file mode 100644
index 00000000000..8737220efc4
--- /dev/null
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java
@@ -0,0 +1,63 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.yahoo.config.provision.ApplicationId;
+
+import java.net.URI;
+import java.util.List;
+
+/**
+ * URLs for deployment job badges using <a href="https://github.com/yahoo/badge-up">badge-up</a>.
+ *
+ * @author jonmv
+ */
+class Badges {
+
+ static final String dark = "555555",
+ blue = "4477DD",
+ red = "DD4444",
+ purple = "AA11CC",
+ yellow = "DDAA11",
+ white = "FFFFFF";
+
+ private final URI badgeApi;
+
+ Badges(URI badgeApi) {
+ this.badgeApi = badgeApi;
+ }
+
+ /** Returns a URI which gives a history badge for the given runs. */
+ URI historic(ApplicationId id, List<Run> runs) {
+ StringBuilder path = new StringBuilder(id + ";" + dark);
+
+ if ( ! runs.isEmpty()) {
+ Run lastCompleted = runs.get(runs.size() - 1);
+ if (runs.size() > 1 && !lastCompleted.hasEnded())
+ lastCompleted = runs.get(runs.size() - 2);
+
+ path.append("/").append(lastCompleted.id().type().jobName()).append(";").append(colorOf(lastCompleted));
+ for (Run run : runs)
+ path.append("/%20;").append(colorOf(run)).append(";s%7B").append(white).append("%7D");
+ }
+
+ return badgeApi.resolve(path.toString());
+ }
+
+ /** Returns a URI which gives an overview badge for the given runs. */
+ URI overview(ApplicationId id, List<Run> runs) {
+ StringBuilder path = new StringBuilder(id + ";" + dark);
+ for (Run run : runs)
+ path.append("/").append(run.id().type().jobName()).append(";").append(colorOf(run));
+
+ return badgeApi.resolve(path.toString());
+ }
+
+ private static String colorOf(Run run) {
+ switch (run.status()) {
+ case success: return blue;
+ case running: return purple;
+ case aborted: return yellow;
+ default: return red;
+ }
+ }
+
+}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 9659022e258..1a2d7de8185 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -44,6 +44,7 @@ import java.util.stream.Stream;
import static com.google.common.collect.ImmutableList.copyOf;
import static com.yahoo.vespa.hosted.controller.deployment.Step.deactivateTester;
import static com.yahoo.vespa.hosted.controller.deployment.Step.endTests;
+import static java.util.stream.Collectors.toList;
/**
* A singleton owned by the controller, which contains the state and methods for controlling deployment jobs.
@@ -66,12 +67,14 @@ public class JobController {
private final CuratorDb curator;
private final BufferedLogStore logs;
private final TesterCloud cloud;
+ private final Badges badges;
public JobController(Controller controller, RunDataStore runDataStore, TesterCloud testerCloud) {
this.controller = controller;
this.curator = controller.curator();
this.logs = new BufferedLogStore(curator, runDataStore);
this.cloud = testerCloud;
+ this.badges = new Badges(controller.zoneRegistry().badgeUrl());
}
public TesterCloud cloud() { return cloud; }
@@ -110,7 +113,7 @@ public class JobController {
locked(id, __ -> {
List<LogEntry> entries = messages.stream()
.map(message -> new LogEntry(0, controller.clock().millis(), LogEntry.typeOf(level), message))
- .collect(Collectors.toList());
+ .collect(toList());
logs.append(id.application(), id.type(), step, entries);
return __;
});
@@ -157,7 +160,7 @@ public class JobController {
/** Returns an immutable map of all known runs for the given application and job type. */
public Map<RunId, Run> runs(ApplicationId id, JobType type) {
- Map<RunId, Run> runs = curator.readHistoricRuns(id, type);
+ SortedMap<RunId, Run> runs = curator.readHistoricRuns(id, type);
last(id, type).ifPresent(run -> runs.putIfAbsent(run.id(), run));
return ImmutableMap.copyOf(runs);
}
@@ -314,6 +317,25 @@ public class JobController {
}
}
+ /** Returns a URI which points at a badge showing historic status of given length for the given job type for the given application. */
+ public URI historicBadge(ApplicationId id, JobType type, int historyLength) {
+ Map<RunId, Run> runs = runs(id, type);
+ return badges.historic(id,
+ runs.values().stream()
+ .skip(runs.size() - historyLength)
+ .collect(toList()));
+ }
+
+ /** Returns a URI which points at a badge showing current status for all jobs for the given application. */
+ public URI overviewBadge(ApplicationId id) {
+ DeploymentSteps steps = new DeploymentSteps(controller.applications().require(id).deploymentSpec(), controller::system);
+ return badges.overview(id,
+ steps.jobs().stream()
+ .map(type -> last(id, type))
+ .filter(Optional::isPresent).map(Optional::get)
+ .collect(toList()));
+ }
+
/** Returns a URI of the tester endpoint retrieved from the routing generator, provided it matches an expected form. */
Optional<URI> testerEndpoint(RunId id) {
ApplicationId tester = id.tester().id();
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index cfd36a25de8..b76e41971cf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -14,6 +14,7 @@ import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.container.jdisc.HttpResponse;
import com.yahoo.container.jdisc.LoggingRequestHandler;
import com.yahoo.io.IOUtils;
+import com.yahoo.jdisc.Response;
import com.yahoo.log.LogLevel;
import com.yahoo.restapi.Path;
import com.yahoo.slime.Cursor;
@@ -83,6 +84,7 @@ import javax.ws.rs.InternalServerErrorException;
import javax.ws.rs.NotAuthorizedException;
import java.io.IOException;
import java.io.InputStream;
+import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.Principal;
@@ -172,6 +174,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application")) return applications(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return application(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.getUri().getQuery());
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/badge")) return badge(path.get("tenant"), path.get("application"), path.get("instance"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/badge/{badgetype}")) return badge(path.get("tenant"), path.get("application"), path.get("instance"), path.get("job"), request.getProperty("historyLength"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/run/{number}")) return JobControllerApiHandlerHelper.runDetailsResponse(controller.jobController(), runIdFromPath(path), request.getProperty("after"));
@@ -948,6 +952,28 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
}
+ /** Returns a URI which points to an overview badge for the given application. */
+ private HttpResponse badge(String tenant, String application, String instance) {
+ URI location = controller.jobController().overviewBadge(ApplicationId.from(tenant, application, instance));
+ return redirect(location);
+ }
+
+ /** Returns a URI which points to a history badge for the given application and job type. */
+ private HttpResponse badge(String tenant, String application, String instance, String jobName, String historyLength) {
+ URI location = controller.jobController().historicBadge(ApplicationId.from(tenant, application, instance),
+ JobType.fromJobName(jobName),
+ historyLength == null ? 5 : Integer.parseInt(historyLength));
+ return redirect(location);
+ }
+
+ private static HttpResponse redirect(URI location) {
+ HttpResponse httpResponse = new HttpResponse(Response.Status.FOUND) {
+ @Override public void render(OutputStream outputStream) { }
+ };
+ httpResponse.headers().add("Location", location.toString());
+ return httpResponse;
+ }
+
private static DeploymentJobs.JobReport toJobReport(String tenantName, String applicationName, Inspector report) {
Optional<DeploymentJobs.JobError> jobError = Optional.empty();
if (report.field("jobError").valid()) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java
new file mode 100644
index 00000000000..2c89dec61a8
--- /dev/null
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java
@@ -0,0 +1,66 @@
+package com.yahoo.vespa.hosted.controller.deployment;
+
+import com.google.common.collect.ImmutableMap;
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
+import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.Optional;
+
+import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.stagingTest;
+import static com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType.systemTest;
+import static com.yahoo.vespa.hosted.controller.deployment.Step.report;
+import static java.time.Instant.now;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author jonmv
+ */
+public class BadgesTest {
+
+ private static final ApplicationId id = ApplicationId.from("tenant", "application", "default");
+ private static final Run success = new Run(new RunId(id, systemTest, 3), ImmutableMap.of(report, Step.Status.succeeded),
+ null, null, Optional.of(now()), RunStatus.success, 0);
+
+ private static final Run running = new Run(new RunId(id, systemTest, 4), ImmutableMap.of(report, Step.Status.succeeded),
+ null, null, Optional.empty(), RunStatus.running, 0);
+
+ private static final Run failure = new Run(new RunId(id, JobType.stagingTest, 2), ImmutableMap.of(report, Step.Status.succeeded),
+ null, null, Optional.of(now()), RunStatus.testFailure, 0);
+
+ @Test
+ public void test() {
+ Badges badges = new Badges(URI.create("https://badges.tld/api/"));
+
+ assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark),
+ badges.historic(id, Collections.emptyList()));
+
+ assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark +
+ "/" + systemTest.jobName() + ";" + Badges.purple +
+ "/%20;" + Badges.purple + ";s%7B" + Badges.white + "%7D"),
+ badges.historic(id, Collections.singletonList(running)));
+
+ assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark +
+ "/" + systemTest.jobName() + ";" + Badges.blue +
+ "/%20;" + Badges.blue + ";s%7B" + Badges.white + "%7D" +
+ "/%20;" + Badges.purple + ";s%7B" + Badges.white + "%7D"),
+ badges.historic(id, Arrays.asList(success, running)));
+
+ assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark +
+ "/" + systemTest.jobName() + ";" + Badges.purple +
+ "/" + stagingTest.jobName() + ";" + Badges.red),
+ badges.overview(id, Arrays.asList(running, failure)));
+ }
+
+}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 22ef61839be..adc3594c05f 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -457,6 +457,10 @@ public class ApplicationApiTest extends ControllerContainerTest {
.data(createApplicationSubmissionData(packageWithService)),
"{\"version\":\"1.0.43-d00d\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/default/badge", GET)
+ .userIdentity(USER_ID),
+ "", 302);
+
ApplicationId app1 = ApplicationId.from("tenant1", "application1", "default");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/jobreport", POST)
.screwdriverIdentity(SCREWDRIVER_ID)