diff options
Diffstat (limited to 'controller-server')
7 files changed, 185 insertions, 57 deletions
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 4b09e78ede2..2652b49f86f 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 @@ -345,13 +345,15 @@ public class ApplicationApiHandler extends LoggingRequestHandler { private void toSlime(Cursor object, Application application, HttpRequest request) { object.setString("application", application.id().application().value()); object.setString("instance", application.id().instance().value()); + // Currently deploying change if (application.change().isPresent()) { - Cursor deployingObject = object.setObject("deploying"); - application.change().platform().ifPresent(v -> deployingObject.setString("version", v.toString())); - application.change().application() - .filter(v -> v != ApplicationVersion.unknown) - .ifPresent(v -> toSlime(v, deployingObject.setObject("revision"))); + toSlime(object.setObject("deploying"), application.change()); + } + + // Outstanding change + if (application.outstandingChange().isPresent()) { + toSlime(object.setObject("outstandingChange"), application.outstandingChange()); } // Jobs sorted according to deployment spec @@ -454,6 +456,13 @@ public class ApplicationApiHandler extends LoggingRequestHandler { return new SlimeJsonResponse(slime); } + private void toSlime(Cursor object, Change change) { + change.platform().ifPresent(version -> object.setString("version", version.toString())); + change.application() + .filter(version -> !version.isUnknown()) + .ifPresent(version -> toSlime(version, object.setObject("revision"))); + } + private void toSlime(Cursor response, DeploymentId deploymentId, Deployment deployment, HttpRequest request) { Cursor serviceUrlArray = response.setArray("serviceUrls"); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java index ecb2f611171..93213172048 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageClient.java @@ -1,6 +1,8 @@ // Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller.restapi.statuspage; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.config.SlimeUtils; import org.apache.http.HttpStatus; import org.apache.http.client.config.RequestConfig; import org.apache.http.client.methods.CloseableHttpResponse; @@ -36,8 +38,8 @@ public class StatusPageClient { this.key = Objects.requireNonNull(key, "key cannot be null"); } - /** GET given page and return response body */ - public byte[] get(String page, Optional<String> since) { + /** GET given page and return response body as slime */ + public Slime get(String page, Optional<String> since) { HttpGet get = new HttpGet(pageUrl(page, since)); try (CloseableHttpClient client = client()) { try (CloseableHttpResponse response = client.execute(get)) { @@ -45,7 +47,8 @@ public class StatusPageClient { throw new IllegalArgumentException("Received status " + response.getStatusLine().getStatusCode() + " from StatusPage"); } - return EntityUtils.toByteArray(response.getEntity()); + byte[] body = EntityUtils.toByteArray(response.getEntity()); + return SlimeUtils.jsonToSlime(body); } } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java index e652e293c26..ad69681fe7e 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/statuspage/StatusPageProxyHandler.java @@ -6,13 +6,13 @@ import com.yahoo.container.jdisc.HttpRequest; import com.yahoo.container.jdisc.HttpResponse; import com.yahoo.container.jdisc.LoggingRequestHandler; import com.yahoo.container.jdisc.secretstore.SecretStore; +import com.yahoo.slime.Slime; 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.statuspage.config.StatuspageConfig; import com.yahoo.yolean.Exceptions; -import java.io.IOException; -import java.io.OutputStream; import java.net.URI; import java.util.Optional; import java.util.logging.Level; @@ -60,28 +60,8 @@ public class StatusPageProxyHandler extends LoggingRequestHandler { } StatusPageClient client = StatusPageClient.create(apiUrl, secretStore.getSecret(secretKey)); Optional<String> since = Optional.ofNullable(request.getProperty("since")); - byte[] response = client.get(path.get("page"), since); - return new ByteArrayHttpResponse(response); - } - - private static class ByteArrayHttpResponse extends HttpResponse { - - private final byte[] data; - - public ByteArrayHttpResponse(byte[] data) { - super(200); - this.data = data; - } - - @Override - public void render(OutputStream out) throws IOException { - out.write(data); - } - - @Override - public String getContentType() { - return "application/json"; - } + Slime statusPageResponse = client.get(path.get("page"), since); + return new SlimeJsonResponse(statusPageResponse); } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java index 53f32dbcacd..cc275b0636f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ContainerTester.java @@ -7,18 +7,21 @@ import com.yahoo.application.container.handler.Response; import com.yahoo.component.ComponentSpecification; import com.yahoo.component.Version; import com.yahoo.container.http.filter.FilterChainRepository; -import com.yahoo.io.IOUtils; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; import com.yahoo.jdisc.http.filter.SecurityRequestFilterChain; -import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; import com.yahoo.vespa.hosted.controller.Controller; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.SystemApplication; +import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; import com.yahoo.vespa.hosted.controller.versions.VersionStatus; import org.junit.ComparisonFailure; import java.io.File; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.CharacterCodingException; +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.Optional; import java.util.function.Supplier; import java.util.regex.Pattern; @@ -65,26 +68,31 @@ public class ContainerTester { computeVersionStatus(); } - public void assertResponse(Supplier<Request> request, File responseFile) throws IOException { + public void assertResponse(Supplier<Request> request, File responseFile) { assertResponse(request.get(), responseFile); } - public void assertResponse(Request request, File responseFile) throws IOException { + public void assertResponse(Request request, File responseFile) { assertResponse(request, responseFile, 200); } - public void assertResponse(Supplier<Request> request, File responseFile, int expectedStatusCode) throws IOException { + public void assertResponse(Supplier<Request> request, File responseFile, int expectedStatusCode) { assertResponse(request.get(), responseFile, expectedStatusCode); } - public void assertResponse(Request request, File responseFile, int expectedStatusCode) throws IOException { - String expectedResponse = IOUtils.readFile(new File(responseFilePath + responseFile.toString())); + public void assertResponse(Request request, File responseFile, int expectedStatusCode) { + String expectedResponse = readTestFile(responseFile.toString()); expectedResponse = include(expectedResponse); expectedResponse = expectedResponse.replaceAll("(\"[^\"]*\")|\\s*", "$1"); // Remove whitespace FilterResult filterResult = invokeSecurityFilters(request); request = filterResult.request; Response response = filterResult.response != null ? filterResult.response : container.handleRequest(request); - String responseString = response.getBodyAsString(); + String responseString; + try { + responseString = response.getBodyAsString(); + } catch (CharacterCodingException e) { + throw new UncheckedIOException(e); + } if (expectedResponse.contains("(ignore)")) { // Convert expected response to a literal pattern and replace any ignored field with a pattern that matches // until the first stop character @@ -141,7 +149,7 @@ public class ContainerTester { } /** Replaces @include(localFile) with the content of the file */ - private String include(String response) throws IOException { + private String include(String response) { // Please don't look at this code int includeIndex = response.indexOf("@include("); if (includeIndex < 0) return response; @@ -149,14 +157,22 @@ public class ContainerTester { String rest = response.substring(includeIndex + "@include(".length()); int filenameEnd = rest.indexOf(")"); String includeFileName = rest.substring(0, filenameEnd); - String includedContent = IOUtils.readFile(new File(responseFilePath + includeFileName)); + String includedContent = readTestFile(includeFileName); includedContent = include(includedContent); String postFix = rest.substring(filenameEnd + 1); postFix = include(postFix); return prefix + includedContent + postFix; } - static class FilterResult { + private String readTestFile(String name) { + try { + return new String(Files.readAllBytes(Paths.get(responseFilePath, name))); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private static class FilterResult { final Request request; final Response response; @@ -165,5 +181,6 @@ public class ContainerTester { this.response = response; } } + } 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 46c882eaa55..45513c2294f 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 @@ -18,7 +18,6 @@ import com.yahoo.vespa.athenz.api.AthenzUser; import com.yahoo.vespa.athenz.api.NToken; import com.yahoo.vespa.config.SlimeUtils; import com.yahoo.vespa.hosted.controller.Application; -import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; import com.yahoo.vespa.hosted.controller.api.identifiers.Property; import com.yahoo.vespa.hosted.controller.api.identifiers.PropertyId; import com.yahoo.vespa.hosted.controller.api.identifiers.ScrewdriverId; @@ -33,6 +32,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.organization.MockOrgani import com.yahoo.vespa.hosted.controller.api.integration.organization.User; import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneId; import com.yahoo.vespa.hosted.controller.application.ApplicationPackage; +import com.yahoo.vespa.hosted.controller.application.Change; import com.yahoo.vespa.hosted.controller.application.ClusterInfo; import com.yahoo.vespa.hosted.controller.application.ClusterUtilization; import com.yahoo.vespa.hosted.controller.application.Deployment; @@ -43,6 +43,7 @@ import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzClientFactoryMock; import com.yahoo.vespa.hosted.controller.athenz.mock.AthenzDbMock; import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder; import com.yahoo.vespa.hosted.controller.deployment.BuildJob; +import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock; import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; @@ -97,6 +98,7 @@ public class ApplicationApiTest extends ControllerContainerTest { .build(); private static final AthenzDomain ATHENZ_TENANT_DOMAIN = new AthenzDomain("domain1"); + private static final AthenzDomain ATHENZ_TENANT_DOMAIN_2 = new AthenzDomain("domain2"); private static final ScrewdriverId SCREWDRIVER_ID = new ScrewdriverId("12345"); private static final UserId USER_ID = new UserId("myuser"); private static final UserId HOSTED_VESPA_OPERATOR = new UserId("johnoperator"); @@ -145,7 +147,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // Add another Athens domain, so we can try to create more tenants - createAthenzDomainWithAdmin(new AthenzDomain("domain2"), USER_ID); // New domain to test tenant w/property ID + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN_2, USER_ID); // New domain to test tenant w/property ID // Add property info for that property id, as well, in the mock organization. addPropertyData((MockOrganization) controllerTester.controller().organization(), "1234"); // POST (add) a tenant with property ID @@ -194,7 +196,7 @@ public class ApplicationApiTest extends ControllerContainerTest { ATHENZ_TENANT_DOMAIN, new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(id.application().value())); // (Necessary but not provided in this API) - // Trigger deployment from completion of component job + // Pipeline notifies about completed component job controllerTester.jobCompletion(JobType.component) .application(id) .projectId(screwdriverProjectId) @@ -203,13 +205,13 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... systemtest tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default/", POST) - .data(createApplicationDeployData(applicationPackage, false)) + .data(createApplicationDeployData(Optional.empty(), false)) .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default", DELETE) .screwdriverIdentity(SCREWDRIVER_ID), "Deactivated tenant/tenant1/application/application1/environment/test/region/us-east-1/instance/default"); - // Called through the separate screwdriver/v1 API + controllerTester.jobCompletion(JobType.systemTest) .application(id) .projectId(screwdriverProjectId) @@ -217,7 +219,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... staging tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default/", POST) - .data(createApplicationDeployData(applicationPackage, false)) + .data(createApplicationDeployData(Optional.empty(), false)) .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/staging/region/us-east-3/instance/default", DELETE) @@ -230,7 +232,7 @@ public class ApplicationApiTest extends ControllerContainerTest { // ... prod zone tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/environment/prod/region/corp-us-east-1/instance/default/", POST) - .data(createApplicationDeployData(applicationPackage, false)) + .data(createApplicationDeployData(Optional.empty(), false)) .screwdriverIdentity(SCREWDRIVER_ID), new File("deploy-result.json")); controllerTester.jobCompletion(JobType.productionCorpUsEast1) @@ -239,6 +241,43 @@ public class ApplicationApiTest extends ControllerContainerTest { .unsuccessful() .submit(); + // POST (create) another application + ApplicationPackage applicationPackage = new ApplicationPackageBuilder() + .environment(Environment.prod) + .region("us-west-1") + .build(); + + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", POST) + .userIdentity(USER_ID) + .nToken(N_TOKEN), + new File("application-reference-2.json")); + + ApplicationId app2 = ApplicationId.from("tenant2", "application2", "default"); + long screwdriverProjectId2 = 456; + addScrewdriverUserToDeployRole(SCREWDRIVER_ID, + ATHENZ_TENANT_DOMAIN_2, + new com.yahoo.vespa.hosted.controller.api.identifiers.ApplicationId(app2.application().value())); + + // Trigger upgrade and then application change + controllerTester.controller().applications().deploymentTrigger().triggerChange(app2, Change.of(Version.fromString("7.0"))); + + controllerTester.jobCompletion(JobType.component) + .application(app2) + .projectId(screwdriverProjectId2) + .uploadArtifact(applicationPackage) + .submit(); + + // GET application having both change and outstanding change + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", GET) + .userIdentity(USER_ID), + new File("application2.json")); + + // DELETE application + tester.assertResponse(request("/application/v4/tenant/tenant2/application/application2", DELETE) + .userIdentity(USER_ID) + .nToken(N_TOKEN), + ""); + // GET tenant screwdriver projects tester.assertResponse(request("/application/v4/tenant-pipeline/", GET) .userIdentity(USER_ID), @@ -383,8 +422,6 @@ public class ApplicationApiTest extends ControllerContainerTest { tester.assertResponse(request("/application/v4/tenant/tenant1", DELETE).userIdentity(USER_ID) .nToken(N_TOKEN), new File("tenant-without-applications.json")); - - controllerTester.controller().deconstruct(); } private void addIssues(ContainerControllerTester tester, ApplicationId id) { @@ -395,7 +432,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testDeployDirectly() throws Exception { + public void testDeployDirectly() { // Setup ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); @@ -427,11 +464,10 @@ public class ApplicationApiTest extends ControllerContainerTest { new File("deploy-result.json")); } - // Tests deployment to config server when using just on API call // For now this depends on a switch in ApplicationController that does this for by- tenants in CD only @Test - public void testDeployDirectlyUsingOneCallForDeploy() throws Exception { + public void testDeployDirectlyUsingOneCallForDeploy() { // Setup ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); @@ -813,7 +849,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void deployment_succeeds_when_correct_domain_is_used() throws IOException { + public void deployment_succeeds_when_correct_domain_is_used() { ContainerControllerTester controllerTester = new ContainerControllerTester(container, responseFiles); ContainerTester tester = controllerTester.containerTester(); ApplicationPackage applicationPackage = new ApplicationPackageBuilder() @@ -844,7 +880,7 @@ public class ApplicationApiTest extends ControllerContainerTest { } @Test - public void testJobStatusReporting() throws Exception { + public void testJobStatusReporting() { ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); tester.containerTester().computeVersionStatus(); diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json new file mode 100644 index 00000000000..a4026d6a812 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application-reference-2.json @@ -0,0 +1,5 @@ +{ + "application": "application2", + "instance": "default", + "url": "http://localhost:8080/application/v4/tenant/tenant2/application/application2" +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json new file mode 100644 index 00000000000..fa51d645cfc --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/application2.json @@ -0,0 +1,78 @@ +{ + "application": "application2", + "instance": "default", + "deploying": { + "version": "(ignore)" + }, + "outstandingChange": { + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + } + }, + "deploymentJobs": [ + { + "type": "component", + "success": true, + "lastCompleted": { + "id": 42, + "version": "(ignore)", + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Application commit", + "at": "(ignore)" + }, + "lastSuccess": { + "id": 42, + "version": "(ignore)", + "revision": { + "hash": "(ignore)", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Application commit", + "at": "(ignore)" + } + }, + { + "type": "system-test", + "success": false, + "lastTriggered": { + "id": -1, + "version": "7.0.0", + "revision": { + "hash": "1.0.42-commit1", + "source": { + "gitRepository": "repository1", + "gitBranch": "master", + "gitCommit": "commit1" + } + }, + "reason": "Testing last changes outside prod", + "at": "(ignore)" + } + } + ], + "changeBlockers": [], + "compileVersion": "6.1.0", + "globalRotations": [], + "instances": [], + "metrics": { + "queryServiceQuality": 0.0, + "writeServiceQuality": 0.0 + }, + "activity": {} +} |