diff options
author | Valerij Fredriksen <freva@users.noreply.github.com> | 2021-09-27 15:17:06 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-09-27 15:17:06 +0200 |
commit | 1654f886aa22dadab66a0d0b30024bbc46743ee1 (patch) | |
tree | 36b61fcbe2d3d8ca2a6770b9a4c79463acc769e1 | |
parent | c0cdd0f9a79a56a51d59da9398c4f13292404746 (diff) | |
parent | ecdb50a1446922301a6c4e013e09e068c8f48739 (diff) |
Merge pull request #19289 from vespa-engine/ogronnesby/create-application-on-deploy
Create application on deploy in all systems
3 files changed, 104 insertions, 0 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 1a2acb82348..98ac789de04 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 @@ -1967,6 +1967,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .flatMap(options -> optional("vespaVersion", options)) .map(Version::fromString); + ensureApplicationExists(TenantAndApplicationId.from(id), request); + controller.jobController().deploy(id, type, version, applicationPackage); RunId runId = controller.jobController().last(id, type).get().id(); Slime slime = new Slime(); @@ -2617,6 +2619,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { applicationPackage, Optional.of(requireUserPrincipal(request))); + ensureApplicationExists(TenantAndApplicationId.from(tenant, application), request); + return JobControllerApiHandlerHelper.submitResponse(controller.jobController(), tenant, application, @@ -2718,5 +2722,13 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler { .anyMatch(definition -> definition == RoleDefinition.hostedOperator); } + private void ensureApplicationExists(TenantAndApplicationId id, HttpRequest request) { + if (controller.applications().getApplication(id).isEmpty()) { + log.fine("Application does not exist in public, creating: " + id); + var credentials = accessControlRequests.credentials(id.tenant(), null /* not used on public */ , request.getJDiscRequest()); + controller.applications().createApplication(id, credentials); + } + } + } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java index 5f76a30bf45..8c6d368d93a 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiCloudTest.java @@ -1,6 +1,7 @@ // 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.application; +import ai.vespa.hosted.api.MultiPartStreamer; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.ApplicationName; import com.yahoo.config.provision.InstanceName; @@ -10,10 +11,12 @@ import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.LockedTenant; import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanId; +import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion; import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType; import com.yahoo.vespa.hosted.controller.api.integration.secrets.TenantSecretStore; import com.yahoo.vespa.hosted.controller.api.role.Role; import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId; +import com.yahoo.vespa.hosted.controller.application.pkg.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; @@ -232,6 +235,42 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { 200); } + @Test + public void create_application_on_deploy() { + var application = ApplicationName.from("unique"); + var applicationPackage = new ApplicationPackageBuilder().build(); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isEmpty()); + + tester.assertResponse( + request("/application/v4/tenant/scoober/application/unique/instance/default/deploy/dev-aws-us-east-1c", POST) + .data(createApplicationDeployData(Optional.of(applicationPackage), Optional.empty(), true)) + .roles(Set.of(Role.developer(tenantName))), + "{\"message\":\"Deployment started in run 1 of dev-aws-us-east-1c for scoober.unique. This may take about 15 minutes the first time.\",\"run\":1}"); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isPresent()); + } + + @Test + public void create_application_on_submit() { + var application = ApplicationName.from("unique"); + var applicationPackage = new ApplicationPackageBuilder() + .trustDefaultCertificate() + .build(); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isEmpty()); + + var data = ApplicationApiTest.createApplicationSubmissionData(applicationPackage, 123); + + tester.assertResponse( + request("/application/v4/tenant/scoober/application/unique/submit", POST) + .data(data) + .roles(Set.of(Role.developer(tenantName))), + "{\"message\":\"Application package version: 1.0.1-commit1, source revision of repository 'repository1', branch 'master' with commit 'commit1', by a@b, built against 6.1 at 1970-01-01T00:00:01Z\"}"); + + assertTrue(tester.controller().applications().getApplication(TenantAndApplicationId.from(tenantName, application)).isPresent()); + } + private ApplicationPackageBuilder prodBuilder() { return new ApplicationPackageBuilder() .instances("default") @@ -264,8 +303,31 @@ public class ApplicationApiCloudTest extends ControllerContainerCloudTest { JobType.productionAwsUsEast1c, Optional.empty(), applicationPackage); + } + private MultiPartStreamer createApplicationDeployData(Optional<ApplicationPackage> applicationPackage, + Optional<ApplicationVersion> applicationVersion, boolean deployDirectly) { + MultiPartStreamer streamer = new MultiPartStreamer(); + streamer.addJson("deployOptions", deployOptions(deployDirectly, applicationVersion)); + applicationPackage.ifPresent(ap -> streamer.addBytes("applicationZip", ap.zippedContent())); + return streamer; + } + + private String deployOptions(boolean deployDirectly, Optional<ApplicationVersion> applicationVersion) { + return "{\"vespaVersion\":null," + + "\"ignoreValidationErrors\":false," + + "\"deployDirectly\":" + deployDirectly + + applicationVersion.map(version -> + "," + + "\"buildNumber\":" + version.buildNumber().getAsLong() + "," + + "\"sourceRevision\":{" + + "\"repository\":\"" + version.source().get().repository() + "\"," + + "\"branch\":\"" + version.source().get().branch() + "\"," + + "\"commit\":\"" + version.source().get().commit() + "\"" + + "}" + ).orElse("") + + "}"; } } 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 5bd7afcb917..596a0b186db 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 @@ -1657,6 +1657,36 @@ public class ApplicationApiTest extends ControllerContainerTest { 403); } + @Test + public void create_application_on_deploy() { + // Setup + createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, USER_ID); + addUserToHostedOperatorRole(HostedAthenzIdentities.from(HOSTED_VESPA_OPERATOR)); + + // Create tenant + tester.assertResponse(request("/application/v4/tenant/tenant1", POST).userIdentity(USER_ID) + .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}") + .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT), + new File("tenant-without-applications.json")); + + // Deploy application + var id = ApplicationId.from("tenant1", "application1", "instance1"); + var appId = TenantAndApplicationId.from(id); + var entity = createApplicationDeployData(applicationPackageInstance1); + + assertTrue(tester.controller().applications().getApplication(appId).isEmpty()); + + // POST (deploy) an application to start a manual deployment to dev + tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploy/dev-us-east-1/", POST) + .data(entity) + .oktaIdentityToken(OKTA_IT) + .oktaAccessToken(OKTA_AT) + .userIdentity(USER_ID), + "{\"message\":\"Deployment started in run 1 of dev-us-east-1 for tenant1.application1.instance1. This may take about 15 minutes the first time.\",\"run\":1}"); + + assertTrue(tester.controller().applications().getApplication(appId).isPresent()); + } + private static String serializeInstant(Instant i) { return DateTimeFormatter.ISO_INSTANT.format(i.truncatedTo(ChronoUnit.SECONDS)); } |