From 17d18f2ee5cdf49d78a6caf68467f24fd9d921c1 Mon Sep 17 00:00:00 2001 From: Jon Marius Venstad Date: Fri, 7 May 2021 14:49:44 +0200 Subject: Remove unused fields --- .../src/main/java/com/yahoo/search/query/Presentation.java | 2 +- container-search/src/main/java/com/yahoo/search/query/Select.java | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/container-search/src/main/java/com/yahoo/search/query/Presentation.java b/container-search/src/main/java/com/yahoo/search/query/Presentation.java index e147b14071a..b10e8442a5f 100644 --- a/container-search/src/main/java/com/yahoo/search/query/Presentation.java +++ b/container-search/src/main/java/com/yahoo/search/query/Presentation.java @@ -128,7 +128,7 @@ public class Presentation implements Cloneable { return clone; } catch (CloneNotSupportedException e) { - throw new RuntimeException("Someone inserted a noncloneable superclass",e); + throw new RuntimeException("Someone inserted a noncloneable superclass", e); } } diff --git a/container-search/src/main/java/com/yahoo/search/query/Select.java b/container-search/src/main/java/com/yahoo/search/query/Select.java index d90550084eb..a7e491f5269 100644 --- a/container-search/src/main/java/com/yahoo/search/query/Select.java +++ b/container-search/src/main/java/com/yahoo/search/query/Select.java @@ -1,7 +1,6 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.search.query; -import com.yahoo.processing.request.CompoundName; import com.yahoo.search.Query; import com.yahoo.search.grouping.GroupingRequest; import com.yahoo.search.query.parser.ParserEnvironment; @@ -12,13 +11,12 @@ import com.yahoo.search.yql.VespaGroupingStep; import java.util.ArrayList; import java.util.Collections; -import java.util.LinkedHashSet; import java.util.List; import java.util.Objects; /** - * The parameters defining the where-clause and groping of a query + * The parameters defining the where-clause and grouping of a query * * @author henrhoi */ @@ -26,7 +24,6 @@ public class Select implements Cloneable { /** The type representing the property arguments consumed by this */ private static final QueryProfileType argumentType; - private static final CompoundName argumentTypeName; public static final String SELECT = "select"; public static final String WHERE = "where"; @@ -46,7 +43,6 @@ public class Select implements Cloneable { argumentType.addField(new FieldDescription(WHERE, "string")); argumentType.addField(new FieldDescription(GROUPING, "string")); argumentType.freeze(); - argumentTypeName = new CompoundName(argumentType.getId().getName()); } public static QueryProfileType getArgumentType() { return argumentType; } -- cgit v1.2.3 From a8fb504aa689e76565e2e2243686b4ce41551580 Mon Sep 17 00:00:00 2001 From: Jon Marius Venstad Date: Fri, 7 May 2021 15:04:07 +0200 Subject: Remove old badge redirect implementation --- .../api/integration/zone/ZoneRegistry.java | 3 -- .../vespa/hosted/controller/deployment/Badges.java | 59 --------------------- .../controller/deployment/JobController.java | 26 --------- .../hosted/controller/deployment/BadgesTest.java | 61 ---------------------- .../controller/integration/ZoneRegistryMock.java | 5 -- 5 files changed, 154 deletions(-) delete mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java delete mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java index 76637d10a6e..7539ef3c63a 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/zone/ZoneRegistry.java @@ -86,9 +86,6 @@ public interface ZoneRegistry { /** Returns a URL used to request support from the Vespa team. */ URI supportUrl(); - /** Returns a URL used to generate flashy badges from strings. */ - URI badgeUrl(); - /** Returns a URL to the controller's api endpoint */ URI apiUrl(); 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 deleted file mode 100644 index f5369406f97..00000000000 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Badges.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package com.yahoo.vespa.hosted.controller.deployment; - -import com.yahoo.config.provision.ApplicationId; - -import java.net.URI; -import java.util.List; -import java.util.Optional; - -/** - * URLs for deployment job badges using badge-up. - * - * @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, Optional lastCompleted, List runs) { - StringBuilder path = new StringBuilder(id + ";" + dark); - - lastCompleted.ifPresent(last -> path.append("/").append(last.id().type().jobName()).append(";").append(colorOf(last))); - 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 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 3dc88d5d6d2..25bc21a0076 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 @@ -82,7 +82,6 @@ public class JobController { private final CuratorDb curator; private final BufferedLogStore logs; private final TesterCloud cloud; - private final Badges badges; private final JobMetrics metric; private final AtomicReference> runner = new AtomicReference<>(__ -> { }); @@ -92,7 +91,6 @@ public class JobController { this.curator = controller.curator(); this.logs = new BufferedLogStore(curator, controller.serviceRegistry().runDataStore()); this.cloud = controller.serviceRegistry().testerCloud(); - this.badges = new Badges(controller.zoneRegistry().badgeUrl()); this.metric = new JobMetrics(controller.metric(), controller::system); } @@ -539,30 +537,6 @@ 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) { - List runs = new ArrayList<>(runs(id, type).values()); - Run lastCompleted = null; - if (runs.size() > 0) - lastCompleted = runs.get(runs.size() - 1); - if (runs.size() > 1 && ! lastCompleted.hasEnded()) - lastCompleted = runs.get(runs.size() - 2); - - return badges.historic(id, Optional.ofNullable(lastCompleted), runs.subList(Math.max(0, runs.size() - historyLength), runs.size())); - } - - /** 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().requireApplication(TenantAndApplicationId.from(id)) - .deploymentSpec().requireInstance(id.instance()), - controller::system); - return badges.overview(id, - steps.jobs().stream() - .map(type -> last(id, type)) - .flatMap(Optional::stream) - .collect(toList())); - } - private void prunePackages(TenantAndApplicationId id) { controller.applications().lockApplicationIfPresent(id, application -> { application.get().productionDeployments().values().stream() 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 deleted file mode 100644 index 06d5a42f9c0..00000000000 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/BadgesTest.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -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.Test; - -import java.net.URI; -import java.util.Collections; -import java.util.List; -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.EPOCH; -import static java.time.Instant.now; -import static org.junit.Assert.assertEquals; - -/** - * @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, new StepInfo(report, Step.Status.succeeded, Optional.empty())), - null, null, Optional.of(now()), RunStatus.success, 0, EPOCH, Optional.empty(), Optional.empty(), Optional.empty()); - - private static final Run running = new Run(new RunId(id, systemTest, 4), ImmutableMap.of(report, new StepInfo(report, Step.Status.succeeded, Optional.empty())), - null, null, Optional.empty(), RunStatus.running, 0, EPOCH, Optional.empty(), Optional.empty(), Optional.empty()); - - private static final Run failure = new Run(new RunId(id, JobType.stagingTest, 2), ImmutableMap.of(report, new StepInfo(report, Step.Status.succeeded, Optional.empty())), - null, null, Optional.of(now()), RunStatus.testFailure, 0, EPOCH, Optional.empty(), Optional.empty(), Optional.empty()); - - @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, Optional.empty(), Collections.emptyList())); - - assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark + - "/" + systemTest.jobName() + ";" + Badges.blue + - "/%20;" + Badges.purple + ";s%7B" + Badges.white + "%7D"), - badges.historic(id, Optional.of(success), 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, Optional.of(success), List.of(success, running))); - - assertEquals(URI.create("https://badges.tld/api/tenant.application;" + Badges.dark + - "/" + systemTest.jobName() + ";" + Badges.purple + - "/" + stagingTest.jobName() + ";" + Badges.red), - badges.overview(id, List.of(running, failure))); - } - -} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java index 1d2c743ffba..f1e0b8125e1 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneRegistryMock.java @@ -205,11 +205,6 @@ public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry return URI.create("https://help.tld"); } - @Override - public URI badgeUrl() { - return URI.create("https://badges.tld"); - } - @Override public URI apiUrl() { return URI.create("https://api.tld:4443/"); -- cgit v1.2.3 From ca663df8b7af46fcd6b59631617e1f2fc725954c Mon Sep 17 00:00:00 2001 From: Jon Marius Venstad Date: Fri, 7 May 2021 15:04:37 +0200 Subject: Generate deployment badges in the controller --- .../restapi/deployment/BadgeApiHandler.java | 36 ++- .../controller/restapi/deployment/Badges.java | 293 +++++++++++++++++++++ .../restapi/deployment/BadgeApiTest.java | 42 ++- .../restapi/deployment/responses/history.svg | 176 +++++++++++++ .../restapi/deployment/responses/overview.svg | 125 +++++++++ 5 files changed, 654 insertions(+), 18 deletions(-) create mode 100644 controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java create mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg create mode 100644 controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java index befec42e84e..6fef422b811 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiHandler.java @@ -5,19 +5,23 @@ import com.yahoo.config.provision.ApplicationId; import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; -import com.yahoo.jdisc.Response; import com.yahoo.jdisc.http.HttpRequest.Method; import com.yahoo.restapi.Path; import com.yahoo.vespa.hosted.controller.Controller; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.restapi.ErrorResponse; +import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus; import com.yahoo.yolean.Exceptions; +import java.io.IOException; import java.io.OutputStream; -import java.net.URI; import java.util.logging.Level; import java.util.logging.Logger; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * This API serves redirects to a badge server. * @@ -62,24 +66,30 @@ public class BadgeApiHandler 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); + ApplicationId id = ApplicationId.from(tenant, application, instance); + DeploymentStatus status = controller.jobController().deploymentStatus(controller.applications().requireApplication(TenantAndApplicationId.from(id))); + return svgResponse(Badges.overviewBadge(id, + status.jobs().instance(id.instance()), + controller.system())); } /** 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 : Math.min(32, Math.max(0, Integer.parseInt(historyLength)))); - return redirect(location); + ApplicationId id = ApplicationId.from(tenant, application, instance); + return svgResponse(Badges.historyBadge(id, + controller.jobController().jobStatus(new JobId(id, JobType.fromJobName(jobName))), + historyLength == null ? 5 : Math.min(32, Math.max(0, Integer.parseInt(historyLength))))); } - private static HttpResponse redirect(URI location) { - HttpResponse httpResponse = new HttpResponse(Response.Status.FOUND) { - @Override public void render(OutputStream outputStream) { } + private static HttpResponse svgResponse(String svg) { + return new HttpResponse(200) { + @Override public void render(OutputStream outputStream) throws IOException { + outputStream.write(svg.getBytes(UTF_8)); + } + @Override public String getContentType() { + return "image/svg+xml; charset=UTF-8"; + } }; - httpResponse.headers().add("Location", location.toString()); - return httpResponse; } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java new file mode 100644 index 00000000000..006da9f4439 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/Badges.java @@ -0,0 +1,293 @@ +// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.deployment; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.SystemName; +import com.yahoo.slime.ArrayTraverser; +import com.yahoo.slime.SlimeUtils; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.deployment.JobList; +import com.yahoo.vespa.hosted.controller.deployment.JobStatus; +import com.yahoo.vespa.hosted.controller.deployment.Run; +import com.yahoo.vespa.hosted.controller.deployment.RunStatus; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static java.util.stream.Collectors.toList; + +public class Badges { + + // https://chrishewett.com/blog/calculating-text-width-programmatically/ thank you! + private static final String characterWidths = "[[\" \",35.156],[\"!\",39.355],[\"\\\"\",45.898],[\"#\",81.836],[\"$\",63.574],[\"%\",107.617],[\"&\",72.656],[\"'\",26.855],[\"(\",45.41],[\")\",45.41],[\"*\",63.574],[\"+\",81.836],[\",\",36.377],[\"-\",45.41],[\".\",36.377],[\"/\",45.41],[\"0\",63.574],[\"1\",63.574],[\"2\",63.574],[\"3\",63.574],[\"4\",63.574],[\"5\",63.574],[\"6\",63.574],[\"7\",63.574],[\"8\",63.574],[\"9\",63.574],[\":\",45.41],[\";\",45.41],[\"<\",81.836],[\"=\",81.836],[\">\",81.836],[\"?\",54.541],[\"@\",100],[\"A\",68.359],[\"B\",68.555],[\"C\",69.824],[\"D\",77.051],[\"E\",63.232],[\"F\",57.471],[\"G\",77.539],[\"H\",75.146],[\"I\",42.09],[\"J\",45.459],[\"K\",69.287],[\"L\",55.664],[\"M\",84.277],[\"N\",74.805],[\"O\",78.711],[\"P\",60.303],[\"Q\",78.711],[\"R\",69.531],[\"S\",68.359],[\"T\",61.621],[\"U\",73.193],[\"V\",68.359],[\"W\",98.877],[\"X\",68.506],[\"Y\",61.523],[\"Z\",68.506],[\"[\",45.41],[\"\\\\\",45.41],[\"]\",45.41],[\"^\",81.836],[\"_\",63.574],[\"`\",63.574],[\"a\",60.059],[\"b\",62.305],[\"c\",52.1],[\"d\",62.305],[\"e\",59.57],[\"f\",35.156],[\"g\",62.305],[\"h\",63.281],[\"i\",27.441],[\"j\",34.424],[\"k\",59.18],[\"l\",27.441],[\"m\",97.266],[\"n\",63.281],[\"o\",60.693],[\"p\",62.305],[\"q\",62.305],[\"r\",42.676],[\"s\",52.1],[\"t\",39.404],[\"u\",63.281],[\"v\",59.18],[\"w\",81.836],[\"x\",59.18],[\"y\",59.18],[\"z\",52.539],[\"{\",63.477],[\"|\",45.41],[\"}\",63.477],[\"~\",81.836],[\"_median\",63.281]]"; + private static final double[] widths = new double[128]; // 0-94 hold widths for corresponding chars (+32); 95 holds the fallback width. + + static { + SlimeUtils.jsonToSlimeOrThrow(characterWidths).get() + .traverse((ArrayTraverser) (i, pair) -> { + if (i < 95) + assert Arrays.equals(new byte[]{(byte) (i + 32)}, pair.entry(0).asUtf8()) : i + ": " + pair.entry(0).asString(); + else + assert "_median".equals(pair.entry(0).asString()); + + widths[i] = pair.entry(1).asDouble(); + }); + } + + /** Character pixel width of a 100px size Verdana font rendering of the given code point, for code points in the range [32, 126]. */ + public static double widthOf(int codePoint) { + return 32 <= codePoint && codePoint <= 126 ? widths[codePoint - 32] : widths[95]; + } + + /** Computes an approximate pixel width of a 10px size Verdana font rendering of the given string, ignoring kerning. */ + public static double widthOf(String text) { + return text.codePoints().mapToDouble(Badges::widthOf).sum() / 10; + } + + static String colorOf(Run run, Boolean wasOk) { + switch (run.status()) { + case running: + return wasOk ? "url(#run-on-success)" : "url(#run-on-failure)"; + case success: + return success; + default: + return failure; + } + } + + static String nameOf(JobType type) { + return type.isTest() ? type.isProduction() ? "test" + : type.jobName() + : type.jobName().replace("production-", ""); + } + + static final double xPad = 8; + static final double logoSize = 16; + static final String dark = "#5a5a5a"; + static final String success = "#00ff48"; + static final String running = "#ab83ff"; + static final String failure = "#bf103c"; + + static void addText(List texts, String text, double x, double width) { + texts.add(" " + text + "\n"); + texts.add(" " + text + "\n"); + } + + static void addShade(List sections, double x, double width) { + sections.add(" \n"); + } + + static void addShadow(List sections, double x) { + sections.add(" \n"); + } + + static String historyBadge(ApplicationId id, JobStatus status, int length) { + List sections = new ArrayList<>(); + List texts = new ArrayList<>(); + + double x = 0; + String text = id.toFullString(); + double textWidth = widthOf(text); + double dx = xPad + logoSize + xPad + textWidth + xPad; + + addShade(sections, x, dx); + sections.add(" \n"); + addText(texts, text, x + (xPad + logoSize + dx) / 2, textWidth); + x += dx; + + if (status.lastTriggered().isEmpty()) + return badge(sections, texts, x); + + Run lastTriggered = status.lastTriggered().get(); + List runs = status.runs().descendingMap().values().stream() + .filter(Run::hasEnded) + .limit(length + (lastTriggered.hasEnded() ? 0 : 1)) + .collect(toList()); + + boolean isOk = runs.isEmpty() || runs.get(0).status() == RunStatus.success; + if ( ! lastTriggered.hasEnded()) + runs.remove(0); + + text = lastTriggered.id().type().jobName(); + textWidth = widthOf(text); + dx = xPad + textWidth + xPad; + addShade(sections, x, dx); + sections.add(" \n"); + addShadow(sections, x + dx); + addText(texts, text, x + dx / 2, textWidth); + x += dx; + + dx = xPad * (128.0 / (32 + runs.size())); // Broader sections with shorter history. + for (Run run : runs) { + addShade(sections, x, dx); + sections.add(" \n"); + addShadow(sections, x + dx); + dx *= Math.pow(0.3, 1.0 / (runs.size() + 8)); // Gradually narrowing sections with age. + x += dx; + } + Collections.reverse(sections); + + return badge(sections, texts, x); + } + + static String overviewBadge(ApplicationId id, JobList jobs, SystemName system) { + // Put production tests right after their deployments, for a more compact rendering. + List runs = new ArrayList<>(jobs.lastTriggered().asList()); + for (int i = 0; i < runs.size(); i++) { + Run run = runs.get(i); + if (run.id().type().isProduction() && run.id().type().isTest()) { + int j = i; + while (!runs.get(j - 1).id().type().zone(system).equals(run.id().type().zone(system))) + runs.set(j, runs.get(--j)); + runs.set(j, run); + } + } + + List sections = new ArrayList<>(); + List texts = new ArrayList<>(); + + double x = 0; + String text = id.toFullString(); + double textWidth = widthOf(text); + double dx = xPad + logoSize + xPad + textWidth + xPad; + double tdx = xPad + widthOf("test"); + + addShade(sections, 0, dx); + sections.add(" \n"); + addText(texts, text, x + (xPad + logoSize + dx) / 2, textWidth); + x += dx; + + for (int i = 0; i < runs.size(); i++) { + Run run = runs.get(i); + Run test = i + 1 < runs.size() ? runs.get(i + 1) : null; + if (test == null || ! test.id().type().isTest() || ! test.id().type().isProduction()) + test = null; + + boolean isTest = run.id().type().isTest() && run.id().type().isProduction(); + text = nameOf(run.id().type()); + textWidth = widthOf(text); + dx = xPad + textWidth + (isTest ? 0 : xPad); + boolean wasOk = jobs.get(run.id().job()).flatMap(JobStatus::lastStatus).map(RunStatus.success::equals).orElse(true); + + addText(texts, text, x + (dx - (isTest ? xPad : 0)) / 2, textWidth); + + // Add "deploy" when appropriate + if ( ! run.id().type().isTest()) { + String deploy = "deploy"; + textWidth = widthOf(deploy); + addText(texts, deploy, x + dx + textWidth / 2, textWidth); + dx += textWidth + xPad; + } + + // Add shade across zone section. + if ( ! (isTest)) + addShade(sections, x, dx + (test != null ? tdx : 0)); + + // Add colored section for job ... + if (test == null) + sections.add(" \n"); + // ... with a slant if a test is next. + else + sections.add(" \n"); + + // Cast a shadow onto the next zone ... + if (test == null) + addShadow(sections, x + dx); + + x += dx; + } + Collections.reverse(sections); + + return badge(sections, texts, x); + } + + static String badge(List sections, List texts, double width) { + return "\n" + + " Deployment Status\n" + + // Lighting to give the badge a 3d look--dispersion at the top, shadow at the bottom. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Dispersed light at the left of the badge. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Shadow at the right of the badge. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Shadow to highlight the border between sections, without using a heavy separator. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Weak shade across each panel to highlight borders further. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Running color sloshing back and forth on top of the failure color. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Running color sloshing back and forth on top of the success color. + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Clipping to give the badge rounded corners. + " \n" + + " \n" + + " \n" + + // Badge section backgrounds with status colors and shades for distinction. + " \n" + + String.join("", sections) + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // The vespa.ai logo (with a slightly coloured shadow)! + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + // Application ID and job names. + String.join("", texts) + + " \n" + + "\n"; + } + +} \ No newline at end of file diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java index ff02a1e16be..21d8030dbee 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/BadgeApiTest.java @@ -2,11 +2,18 @@ package com.yahoo.vespa.hosted.controller.restapi.deployment; import com.yahoo.vespa.hosted.controller.ControllerTester; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; +import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; import org.junit.Test; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + /** * @author jonmv */ @@ -15,15 +22,40 @@ public class BadgeApiTest extends ControllerContainerTest { private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/"; @Test - public void testBadgeApi() { + public void testBadgeApi() throws IOException { ContainerTester tester = new ContainerTester(container, responseFiles); var application = new DeploymentTester(new ControllerTester(tester)).newDeploymentContext("tenant", "application", "default"); - application.submit(); + ApplicationPackage applicationPackage = new ApplicationPackageBuilder().systemTest() + .parallel("us-west-1", "aws-us-east-1a") + .test("us-west-1") + .test("aws-us-east-1a") + .region("ap-southeast-1") + .test("ap-southeast-1") + .region("eu-west-1") + .test("eu-west-1") + .build(); + application.submit(applicationPackage).deploy(); + application.submit(applicationPackage) + .runJob(JobType.systemTest) + .runJob(JobType.stagingTest) + .runJob(JobType.productionUsWest1) + .runJob(JobType.productionAwsUsEast1a) + .runJob(JobType.testUsWest1) + .runJob(JobType.testAwsUsEast1a) + .runJob(JobType.productionApSoutheast1) + .failDeployment(JobType.testApSoutheast1); + application.submit(applicationPackage) + .runJob(JobType.systemTest) + .runJob(JobType.stagingTest); + for (int i = 0; i < 32; i++) + application.failDeployment(JobType.productionUsWest1); + application.triggerJobs(); + tester.controller().applications().deploymentTrigger().reTrigger(application.instanceId(), JobType.testEuWest1); tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default"), - "", 302); - tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/system-test?historyLength=10"), - "", 302); + Files.readString(Paths.get(responseFiles + "overview.svg")), 200); + tester.assertResponse(authenticatedRequest("http://localhost:8080/badge/v1/tenant/application/default/production-us-west-1?historyLength=32"), + Files.readString(Paths.get(responseFiles + "history.svg")), 200); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg new file mode 100644 index 00000000000..a081b21ec3f --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/history.svg @@ -0,0 +1,176 @@ + + Deployment Status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tenant.application.default + tenant.application.default + production-us-west-1 + production-us-west-1 + + diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg new file mode 100644 index 00000000000..0ddd91d4008 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/overview.svg @@ -0,0 +1,125 @@ + + Deployment Status + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + tenant.application.default + tenant.application.default + system-test + system-test + staging-test + staging-test + us-west-1 + us-west-1 + deploy + deploy + test + test + aws-us-east-1a + aws-us-east-1a + deploy + deploy + test + test + ap-southeast-1 + ap-southeast-1 + deploy + deploy + test + test + eu-west-1 + eu-west-1 + deploy + deploy + test + test + + -- cgit v1.2.3