diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2017-11-21 14:14:23 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-21 14:14:23 +0100 |
commit | 3925924678c77c42c208c9b508ae72917df96300 (patch) | |
tree | 10dc724be1e25283b66b9b96c0371b14b745a3f1 /controller-server | |
parent | 5c30536ceafbeb1b4ab22dd730da841efd15495e (diff) | |
parent | 14ed128972d79fc11fa6647c37131e7145be2870 (diff) |
Merge branch 'master' into jvenstad/application-locking-revised
Diffstat (limited to 'controller-server')
17 files changed, 435 insertions, 55 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java index 7dff2b70317..4d1a009806f 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/ApplicationList.java @@ -116,7 +116,7 @@ public class ApplicationList { return listOf(list.stream().filter(application -> ! failingOn(version, application))); } - /** Returns the subset of applications which have at least one deployment */ + /** Returns the subset of applications which have at least one production deployment */ public ApplicationList hasDeployment() { return listOf(list.stream().filter(a -> !a.productionDeployments().isEmpty())); } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java index de771ff2e17..f5e2020d3e3 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/AthenzPrincipalFilter.java @@ -2,30 +2,27 @@ package com.yahoo.vespa.hosted.controller.athenz.filter; import com.google.inject.Inject; -import com.yahoo.jdisc.Response; -import com.yahoo.jdisc.handler.FastContentWriter; -import com.yahoo.jdisc.handler.ResponseDispatch; import com.yahoo.jdisc.handler.ResponseHandler; import com.yahoo.jdisc.http.filter.DiscFilterRequest; import com.yahoo.jdisc.http.filter.SecurityRequestFilter; -import com.yahoo.jdisc.http.server.jetty.ErrorResponseContentCreator; import com.yahoo.vespa.hosted.controller.athenz.AthenzPrincipal; import com.yahoo.vespa.hosted.controller.athenz.InvalidTokenException; import com.yahoo.vespa.hosted.controller.athenz.NToken; import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; -import java.util.Optional; import java.util.concurrent.Executor; +import static com.yahoo.vespa.hosted.controller.athenz.filter.SecurityFilterUtils.sendUnauthorized; + /** * Performs authentication by validating the principal token (NToken) header. * * @author bjorncs */ +// TODO bjorncs: Move this class into separate container-security bundle public class AthenzPrincipalFilter implements SecurityRequestFilter { - private final ErrorResponseContentCreator responseCreator = new ErrorResponseContentCreator(); private final NTokenValidator validator; private final String principalTokenHeader; @@ -47,7 +44,7 @@ public class AthenzPrincipalFilter implements SecurityRequestFilter { public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { String rawToken = request.getHeader(principalTokenHeader); if (rawToken == null || rawToken.isEmpty()) { - sendUnauthorized(request, responseHandler, "NToken is missing"); + sendUnauthorized(responseHandler, "NToken is missing"); return; } try { @@ -55,16 +52,7 @@ public class AthenzPrincipalFilter implements SecurityRequestFilter { request.setUserPrincipal(principal); request.setRemoteUser(principal.getName()); } catch (InvalidTokenException e) { - sendUnauthorized(request, responseHandler, e.getMessage()); - } - } - - private void sendUnauthorized(DiscFilterRequest request, ResponseHandler responseHandler, String message) { - try (FastContentWriter writer = ResponseDispatch.newInstance(Response.Status.UNAUTHORIZED) - .connectFastWriter(responseHandler)) { - writer.write( - responseCreator.createErrorContent( - request.getRequestURI(), Response.Status.UNAUTHORIZED, Optional.of(message))); + sendUnauthorized(responseHandler, e.getMessage()); } } diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/SecurityFilterUtils.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/SecurityFilterUtils.java new file mode 100644 index 00000000000..075e5e76acd --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/SecurityFilterUtils.java @@ -0,0 +1,32 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.athenz.filter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.yahoo.jdisc.Response; +import com.yahoo.jdisc.handler.FastContentWriter; +import com.yahoo.jdisc.handler.ResponseDispatch; +import com.yahoo.jdisc.handler.ResponseHandler; + +/** + * @author bjorncs + */ +class SecurityFilterUtils { + private static final ObjectMapper mapper = new ObjectMapper(); + + private SecurityFilterUtils() {} + + static void sendUnauthorized(ResponseHandler responseHandler, String message) { + Response response = new Response(Response.Status.UNAUTHORIZED); + response.headers().put("Content-Type", "application/json"); + ObjectNode errorMessage = mapper.createObjectNode(); + errorMessage.put("message", message); + try (FastContentWriter writer = ResponseDispatch.newInstance(response).connectFastWriter(responseHandler)) { + writer.write(mapper.writerWithDefaultPrettyPrinter().writeValueAsString(errorMessage)); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java new file mode 100644 index 00000000000..d125f279b63 --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/filter/UserAuthWithAthenzPrincipalFilter.java @@ -0,0 +1,76 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.athenz.filter; + +import com.google.inject.Inject; +import com.yahoo.jdisc.handler.ResponseHandler; +import com.yahoo.jdisc.http.filter.DiscFilterRequest; +import com.yahoo.vespa.hosted.controller.athenz.ZmsKeystore; +import com.yahoo.vespa.hosted.controller.athenz.config.AthenzConfig; +import com.yahoo.yolean.chain.Provides; + +import java.util.concurrent.Executor; +import java.util.stream.Stream; + +import static com.yahoo.vespa.hosted.controller.athenz.filter.SecurityFilterUtils.sendUnauthorized; + +/** + * A variant of the {@link AthenzPrincipalFilter} to be used in combination with a cookie-based + * security filter for user authentication + * Assumes that the user authentication filter configured in the same filter chain and is configured to run before this filter. + * + * @author bjorncs + */ +@Provides("UserAuthWithAthenzPrincipalFilter") +// TODO Remove this filter once migrated to Okta +public class UserAuthWithAthenzPrincipalFilter extends AthenzPrincipalFilter { + + private final String userAuthenticationPassThruAttribute; + + @Inject + public UserAuthWithAthenzPrincipalFilter(ZmsKeystore zmsKeystore, Executor executor, AthenzConfig config) { + super(zmsKeystore, executor, config); + this.userAuthenticationPassThruAttribute = config.userAuthenticationPassThruAttribute(); + } + + @Override + public void filter(DiscFilterRequest request, ResponseHandler responseHandler) { + if (request.getMethod().equals("OPTIONS")) return; // Skip authentication on OPTIONS - required for Javascript CORS + + switch (fromHttpRequest(request)) { + case USER_COOKIE_MISSING: + case USER_COOKIE_ALTERNATIVE_MISSING: + super.filter(request, responseHandler); // Cookie-based authentication failed, delegate to Athenz + break; + case USER_COOKIE_OK: + return; // Authenticated using user cookie + case USER_COOKIE_INVALID: + sendUnauthorized(responseHandler, "Your user cookie is invalid (either expired or tampered)"); + break; + } + } + + private UserAuthenticationResult fromHttpRequest(DiscFilterRequest request) { + if (!request.containsAttribute(userAuthenticationPassThruAttribute)) { + throw new IllegalStateException("User authentication filter passthru attribute missing"); + } + Integer statusCode = (Integer) request.getAttribute(userAuthenticationPassThruAttribute); + return Stream.of(UserAuthenticationResult.values()) + .filter(uar -> uar.statusCode == statusCode) + .findAny() + .orElseThrow(() -> new IllegalStateException("Invalid status code: " + statusCode)); + } + + private enum UserAuthenticationResult { + USER_COOKIE_MISSING(0), + USER_COOKIE_OK(1), + USER_COOKIE_INVALID(-1), + USER_COOKIE_ALTERNATIVE_MISSING(-2); + + final int statusCode; + + UserAuthenticationResult(int statusCode) { + this.statusCode = statusCode; + } + + } +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java index f26e748a5f0..8d7d0ddab91 100644 --- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java @@ -41,19 +41,21 @@ public class ApplicationOwnershipConfirmer extends Maintainer { /** File an ownership issue with the owners of all applications we know about. */ private void confirmApplicationOwnerships() { - for (Application application : controller().applications().asList()) { - try { - Tenant tenant = ownerOf(application.id()); - Optional<IssueId> ourIssueId = application.ownershipIssueId(); - ourIssueId = tenant.tenantType() == TenantType.USER - ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant)) - : ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor(tenant)); - ourIssueId.ifPresent(issueId -> store(issueId, application.id())); - } - catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. - log.log(Level.WARNING, "Exception caught when attempting to file an issue for " + application.id(), e); - } - } + for (Application application : controller().applications().asList()) + if (application.productionDeployments().isEmpty()) + store(null, application.id()); + else + try { + Tenant tenant = ownerOf(application.id()); + Optional<IssueId> ourIssueId = application.ownershipIssueId(); + ourIssueId = tenant.tenantType() == TenantType.USER + ? ownershipIssues.confirmOwnership(ourIssueId, application.id(), userFor(tenant)) + : ownershipIssues.confirmOwnership(ourIssueId, application.id(), propertyIdFor(tenant)); + ourIssueId.ifPresent(issueId -> store(issueId, application.id())); + } + catch (RuntimeException e) { // Catch errors due to wrong data in the controller, or issues client timeout. + log.log(Level.WARNING, "Exception caught when attempting to file an issue for " + application.id(), e); + } } /** Escalate ownership issues which have not been closed before a defined amount of time has passed. */ diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java new file mode 100644 index 00000000000..da58c4ef2da --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiHandler.java @@ -0,0 +1,130 @@ +// Copyright 2017 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.zone.v1; + +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.container.logging.AccessLog; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; +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.yolean.Exceptions; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.Executor; +import java.util.logging.Level; +import java.util.stream.Collectors; + +/** + * REST API that provides information about Hosted Vespa zones (version 1) + * + * @author mpolden + */ +@SuppressWarnings("unused") +public class ZoneApiHandler extends LoggingRequestHandler { + + private final ZoneRegistry zoneRegistry; + + public ZoneApiHandler(Executor executor, AccessLog accessLog, ZoneRegistry zoneRegistry) { + super(executor, accessLog); + this.zoneRegistry = zoneRegistry; + } + + @Override + public HttpResponse handle(HttpRequest request) { + try { + switch (request.getMethod()) { + case GET: + return get(request); + default: + return ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is unsupported"); + } + } catch (IllegalArgumentException e) { + return ErrorResponse.badRequest(Exceptions.toMessageString(e)); + } catch (RuntimeException e) { + log.log(Level.WARNING, "Unexpected error handling '" + request.getUri() + "'", e); + return ErrorResponse.internalServerError(Exceptions.toMessageString(e)); + } + } + + private HttpResponse get(HttpRequest request) { + Path path = new Path(request.getUri().getPath()); + if (path.matches("/zone/v1")) { + return root(request); + } + if (path.matches("/zone/v1/environment/{environment}")) { + return environment(request, Environment.from(path.get("environment"))); + } + if (path.matches("/zone/v1/environment/{environment}/default")) { + return defaultRegion(request, Environment.from(path.get("environment"))); + } + return notFound(path); + } + + private HttpResponse root(HttpRequest request) { + List<Environment> environments = zoneRegistry.zones().stream() + .map(Zone::environment) + .distinct() + .sorted(Comparator.comparing(Environment::value)) + .collect(Collectors.toList()); + Slime slime = new Slime(); + Cursor root = slime.setArray(); + environments.forEach(environment -> { + Cursor object = root.addObject(); + object.setString("name", environment.value()); + // Returning /zone/v2 is a bit strange, but that's what the original Jersey implementation did + object.setString("url", request.getUri() + .resolve("/zone/v2/environment/") + .resolve(environment.value()) + .toString()); + }); + return new SlimeJsonResponse(slime); + } + + private HttpResponse environment(HttpRequest request, Environment environment) { + List<Zone> zones = zoneRegistry.zones().stream() + .filter(zone -> zone.environment() == environment) + .collect(Collectors.toList()); + Slime slime = new Slime(); + Cursor root = slime.setArray(); + zones.forEach(zone -> { + Cursor object = root.addObject(); + object.setString("name", zone.region().value()); + object.setString("url", request.getUri() + .resolve("/zone/v2/environment/") + .resolve(environment.value() + "/") + .resolve("region/") + .resolve(zone.region().value()) + .toString()); + }); + return new SlimeJsonResponse(slime); + } + + private HttpResponse defaultRegion(HttpRequest request, Environment environment) { + RegionName region = zoneRegistry.getDefaultRegion(environment) + .orElseThrow(() -> new IllegalArgumentException( + "No default region for environment: " + environment + )); + Slime slime = new Slime(); + Cursor root = slime.setObject(); + root.setString("name", region.value()); + root.setString("url", request.getUri().resolve("region").resolve(region.value()).toString()); + return new SlimeJsonResponse(slime); + } + + private HttpResponse notFound(Path path) { + return ErrorResponse.notFoundError("Nothing at " + path); + } + + private static String url(HttpRequest request, String path) { + return request.getUri().resolve(path).toString(); + } + +} diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java new file mode 100644 index 00000000000..7793548766e --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/package-info.java @@ -0,0 +1,5 @@ +// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +/** + * @author mpolden + */ +package com.yahoo.vespa.hosted.controller.restapi.zone.v1; diff --git a/controller-server/src/main/resources/configdefinitions/athenz.def b/controller-server/src/main/resources/configdefinitions/athenz.def index 4e27e3ebd07..6d10f3dee28 100644 --- a/controller-server/src/main/resources/configdefinitions/athenz.def +++ b/controller-server/src/main/resources/configdefinitions/athenz.def @@ -13,6 +13,10 @@ ztsUrl string # Athenz domain for controller identity. The domain is also used for Athenz tenancy integration. domain string +# Name of the internal user authentication passthru attribute +userAuthenticationPassThruAttribute string +# TODO Remove once migrated to Okta + # Athenz service name for controller identity service.name string diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java index bf21467bc8d..6398a262763 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ZoneRegistryMock.java @@ -1,6 +1,8 @@ // Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.controller; +import com.google.inject.Inject; +import com.yahoo.component.AbstractComponent; import com.yahoo.config.provision.ApplicationId; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.RegionName; @@ -10,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; import java.net.URI; import java.time.Duration; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -19,15 +22,37 @@ import java.util.Optional; /** * @author mpolden */ -public class ZoneRegistryMock implements ZoneRegistry { +public class ZoneRegistryMock extends AbstractComponent implements ZoneRegistry { private final Map<Zone, Duration> deploymentTimeToLive = new HashMap<>(); + private final Map<Environment, RegionName> defaultRegionForEnvironment = new HashMap<>(); + private List<Zone> zones = new ArrayList<>(); + private SystemName system = SystemName.main; + + @Inject + public ZoneRegistryMock() { + this.zones.add(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("corp-us-east-1"))); + } - public void setDeploymentTimeToLive(Zone zone, Duration duration) { + public ZoneRegistryMock setDeploymentTimeToLive(Zone zone, Duration duration) { deploymentTimeToLive.put(zone, duration); + return this; } - private SystemName system = SystemName.main; + public ZoneRegistryMock setDefaultRegionForEnvironment(Environment environment, RegionName region) { + defaultRegionForEnvironment.put(environment, region); + return this; + } + + public ZoneRegistryMock setZones(List<Zone> zones) { + this.zones = zones; + return this; + } + + public ZoneRegistryMock setSystem(SystemName system) { + this.system = system; + return this; + } @Override public SystemName system() { @@ -36,12 +61,13 @@ public class ZoneRegistryMock implements ZoneRegistry { @Override public List<Zone> zones() { - return Collections.singletonList(new Zone(SystemName.main, Environment.from("prod"), RegionName.from("corp-us-east-1"))); + return Collections.unmodifiableList(zones); } @Override public Optional<Zone> getZone(Environment environment, RegionName region) { - return zones().stream().filter(z -> z.environment().equals(environment) && z.region().equals(region)).findFirst(); + return zones().stream().filter(z -> z.environment().equals(environment) && + z.region().equals(region)).findFirst(); } @Override @@ -64,6 +90,11 @@ public class ZoneRegistryMock implements ZoneRegistry { } @Override + public Optional<RegionName> getDefaultRegion(Environment environment) { + return Optional.ofNullable(defaultRegionForEnvironment.get(environment)); + } + + @Override public URI getMonitoringSystemUri(Environment environment, RegionName name, ApplicationId application) { return URI.create("http://monitoring-system.test/?environment=" + environment.value() + "®ion=" + name.value() + "&application=" + application.toShortString()); @@ -74,7 +105,4 @@ public class ZoneRegistryMock implements ZoneRegistry { return URI.create("http://dashboard.test"); } - public void setSystem(SystemName system) { - this.system = system; - } } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java index 23033fbc4f8..2b0e953c12c 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTester.java @@ -119,8 +119,13 @@ public class DeploymentTester { /** Simulate the full lifecycle of an application deployment as declared in given application package */ public Application createAndDeploy(String applicationName, int projectId, ApplicationPackage applicationPackage) { - tester.createTenant("tenant1", "domain1", 1L); - Application application = tester.createApplication(new TenantId("tenant1"), applicationName, "default", projectId); + TenantId tenantId = tester.createTenant("tenant1", "domain1", 1L); + return createAndDeploy(tenantId, applicationName, projectId, applicationPackage); + } + + /** Simulate the full lifecycle of an application deployment as declared in given application package */ + public Application createAndDeploy(TenantId tenantId, String applicationName, int projectId, ApplicationPackage applicationPackage) { + Application application = tester.createApplication(tenantId, applicationName, "default", projectId); deployCompletely(application, applicationPackage); return applications().require(application.id()); } @@ -130,6 +135,11 @@ public class DeploymentTester { return createAndDeploy(applicationName, projectId, applicationPackage(upgradePolicy)); } + /** Simulate the full lifecycle of an application deployment to prod.us-west-1 with the given upgrade policy */ + public Application createAndDeploy(TenantId tenantId, String applicationName, int projectId, String upgradePolicy) { + return createAndDeploy(tenantId, applicationName, projectId, applicationPackage(upgradePolicy)); + } + /** Complete an ongoing deployment */ public void deployCompletely(String applicationName) { deployCompletely(applications().require(ApplicationId.from("tenant1", applicationName, "default")), diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java index 68a1aab0f07..f5be882fcb8 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmerTest.java @@ -1,6 +1,9 @@ package com.yahoo.vespa.hosted.controller.maintenance; import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.controller.Application; import com.yahoo.vespa.hosted.controller.ControllerTester; import com.yahoo.vespa.hosted.controller.api.Tenant; @@ -9,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.TenantId; import com.yahoo.vespa.hosted.controller.api.integration.organization.IssueId; import com.yahoo.vespa.hosted.controller.api.integration.organization.OwnershipIssues; import com.yahoo.vespa.hosted.controller.api.integration.organization.User; +import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester; import com.yahoo.vespa.hosted.controller.persistence.MockCuratorDb; import org.junit.Before; import org.junit.Test; @@ -28,26 +32,25 @@ public class ApplicationOwnershipConfirmerTest { private MockOwnershipIssues issues; private ApplicationOwnershipConfirmer confirmer; - private ControllerTester tester; + private DeploymentTester tester; @Before public void setup() { - tester = new ControllerTester(); + tester = new DeploymentTester(); issues = new MockOwnershipIssues(); confirmer = new ApplicationOwnershipConfirmer(tester.controller(), Duration.ofDays(1), new JobControl(new MockCuratorDb()), issues); } @Test public void testConfirmation() { - TenantId property = tester.createTenant("tenant", "domain", 1L); - ApplicationId propertyAppId = tester.createApplication(property, "application", "default", 1).id(); - Supplier<Application> propertyApp = () -> tester.controller().applications().require(propertyAppId); + TenantId property = tester.controllerTester().createTenant("property", "domain", 1L); + tester.createAndDeploy(property, "application", 1, "default"); + Supplier<Application> propertyApp = () -> tester.controller().applications().require(ApplicationId.from("property", "application", "default")); TenantId user = new TenantId("by-user"); - tester.controller().tenants().addTenant(Tenant.createUserTenant(new TenantId("by-user")), Optional.empty()); - assertTrue(tester.controller().tenants().tenant(user).isPresent()); - ApplicationId userAppId = tester.createApplication(user, "application", "default", 1).id(); - Supplier<Application> userApp = () -> tester.controller().applications().require(userAppId); + tester.controller().tenants().addTenant(Tenant.createUserTenant(user), Optional.empty()); + tester.createAndDeploy(user, "application", 2, "default"); + Supplier<Application> userApp = () -> tester.controller().applications().require(ApplicationId.from("by-user", "application", "default")); assertFalse("No issue is initially stored for a new application.", propertyApp.get().ownershipIssueId().isPresent()); assertFalse("No issue is initially stored for a new application.", userApp.get().ownershipIssueId().isPresent()); @@ -59,8 +62,8 @@ public class ApplicationOwnershipConfirmerTest { confirmer.maintain(); confirmer.maintain(); - assertEquals("Confirmation issue has been filed for property owned application.", propertyApp.get().ownershipIssueId(), issueId); - assertEquals("Confirmation issue has been filed for user owned application.", userApp.get().ownershipIssueId(), issueId); + assertEquals("Confirmation issue has been filed for property owned application.", issueId, propertyApp.get().ownershipIssueId()); + assertEquals("Confirmation issue has been filed for user owned application.", issueId, userApp.get().ownershipIssueId()); assertTrue("Both applications have had their responses ensured.", issues.escalatedForProperty && issues.escalatedForUser); // No new issue is created, so return empty now. @@ -68,15 +71,27 @@ public class ApplicationOwnershipConfirmerTest { confirmer.maintain(); confirmer.maintain(); - assertEquals("Confirmation issue reference is not updated when no issue id is returned.", propertyApp.get().ownershipIssueId(), issueId); + assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, propertyApp.get().ownershipIssueId()); + assertEquals("Confirmation issue reference is not updated when no issue id is returned.", issueId, userApp.get().ownershipIssueId()); - // Time has passed, and a new confirmation issue is in order. + // The user deletes its production deployment — see that the issue is forgotten. + assertEquals("Confirmation issue for user is sitll open.", issueId, userApp.get().ownershipIssueId()); + tester.controller().applications().deactivate(userApp.get(), userApp.get().productionDeployments().keySet().stream().findAny().get()); + assertTrue("No production deployments are listed for user.", userApp.get().productionDeployments().isEmpty()); + confirmer.maintain(); + confirmer.maintain(); + + assertEquals("Confirmation issue has been forgotten for application without production deployments.", Optional.empty(), userApp.get().ownershipIssueId()); + + // Time has passed, and a new confirmation issue is in order for the property which is still in production. Optional<IssueId> issueId2 = Optional.of(IssueId.from("2")); issues.response = issueId2; confirmer.maintain(); confirmer.maintain(); - assertEquals("A new confirmation issue id is stored when something is returned to the maintainer.", propertyApp.get().ownershipIssueId(), issueId2); + assertEquals("A new confirmation issue id is stored when something is returned to the maintainer.", issueId2, propertyApp.get().ownershipIssueId()); + assertEquals("Confirmation issue for application without production deployments has not been filed.", Optional.empty(), userApp.get().ownershipIssueId()); + } private class MockOwnershipIssues implements OwnershipIssues { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java index 621767e83d7..2c1471b29b6 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java @@ -207,6 +207,12 @@ public class ApplicationSerializerTest { Application application = applicationSerializer.fromSlime(applicationSlime(false)); assertFalse(application.deploymentJobs().jobStatus().get(DeploymentJobs.JobType.systemTest).lastCompleted().get().upgrade()); } + + @Test + public void testCompleteApplicationDeserialization() { + Application application = applicationSerializer.fromSlime(SlimeUtils.jsonToSlime(longApplicationJson.getBytes(StandardCharsets.UTF_8))); + // ok if no error + } private Slime applicationSlime(boolean error) { return applicationSlime(123, error); @@ -244,4 +250,6 @@ public class ApplicationSerializerTest { " }\n" + "}\n"; } + + private final String longApplicationJson = "{\"id\":\"tripod:service-aggregation-vespa:default\",\"deploymentSpecField\":\"<deployment version='1.0'>\\n <test />\\n <!--<staging />-->\\n <prod global-service-id=\\\"tripod\\\">\\n <region active=\\\"true\\\">us-east-3</region>\\n <region active=\\\"true\\\">us-west-1</region>\\n </prod>\\n</deployment>\\n\",\"validationOverrides\":\"<validation-overrides>\\n <allow until=\\\"2016-04-28\\\" comment=\\\"Renaming content cluster\\\">content-cluster-removal</allow>\\n <allow until=\\\"2016-08-22\\\" comment=\\\"Migrating us-east-3 to C-2E\\\">cluster-size-reduction</allow>\\n <allow until=\\\"2017-06-30\\\" comment=\\\"Test Vespa upgrade tests\\\">force-automatic-tenant-upgrade-test</allow>\\n</validation-overrides>\\n\",\"deployments\":[{\"zone\":{\"environment\":\"prod\",\"region\":\"us-west-1\"},\"version\":\"6.173.62\",\"deployTime\":1510837817704,\"applicationPackageRevision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-3-16-100\",\"cost\":9,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"oxy-oxygen-2001-4998-c-2942--10d1.gq1.yahoo.com\",\"oxy-oxygen-2001-4998-c-2942--10e2.gq1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"oxy-oxygen-2001-4998-c-2941--106a.gq1.yahoo.com\",\"zt74700-v6-23.ostk.bm2.prod.gq1.yahoo.com\",\"zt74714-v6-28.ostk.bm2.prod.gq1.yahoo.com\",\"zt74730-v6-13.ostk.bm2.prod.gq1.yahoo.com\",\"zt74717-v6-7.ostk.bm2.prod.gq1.yahoo.com\",\"2080260-v6-12.ostk.bm2.prod.gq1.yahoo.com\",\"zt74719-v6-23.ostk.bm2.prod.gq1.yahoo.com\",\"zt74722-v6-26.ostk.bm2.prod.gq1.yahoo.com\",\"zt74704-v6-9.ostk.bm2.prod.gq1.yahoo.com\",\"oxy-oxygen-2001-4998-c-2942--107d.gq1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt74727-v6-21.ostk.bm2.prod.gq1.yahoo.com\",\"zt74773-v6-8.ostk.bm2.prod.gq1.yahoo.com\",\"zt74699-v6-25.ostk.bm2.prod.gq1.yahoo.com\",\"zt74766-v6-27.ostk.bm2.prod.gq1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.1720353499228221,\"mem\":0.4986146831512451,\"disk\":0.0617671330041831,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.07505730001866318,\"mem\":0.7936344432830811,\"disk\":0.2260549694485994,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.01712671480989384,\"mem\":0.0225852754983035,\"disk\":0.006084436856721915,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":1.25,\"writesPerSecond\":43.83199977874756,\"documentCount\":525880277.9999999,\"queryLatencyMillis\":5.607503938674927,\"writeLatencyMillis\":20.57866265104621}},{\"zone\":{\"environment\":\"test\",\"region\":\"us-east-1\"},\"version\":\"6.173.62\",\"deployTime\":1511256872316,\"applicationPackageRevision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"clusterInfo\":{},\"clusterUtils\":{},\"metrics\":{\"queriesPerSecond\":0,\"writesPerSecond\":0,\"documentCount\":0,\"queryLatencyMillis\":0,\"writeLatencyMillis\":0}},{\"zone\":{\"environment\":\"dev\",\"region\":\"us-east-1\"},\"version\":\"6.173.62\",\"deployTime\":1510597489464,\"applicationPackageRevision\":{\"applicationPackageHash\":\"59b883f263c2a3c23dfab249730097d7e0e1ed32\"},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"zt40807-v6-29.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40807-v6-24.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-2-8-50\",\"cost\":5,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40694-v6-21.ostk.bm2.prod.bf1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.191833330678661,\"mem\":0.4625738318415235,\"disk\":0.05582004563850269,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.2227037978608054,\"mem\":0.2051752598416401,\"disk\":0.05471533698695047,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.1869410834020498,\"mem\":0.1691722576000564,\"disk\":0.04977374774258153,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":0,\"writesPerSecond\":0,\"documentCount\":30916,\"queryLatencyMillis\":0,\"writeLatencyMillis\":0}},{\"zone\":{\"environment\":\"prod\",\"region\":\"us-east-3\"},\"version\":\"6.173.62\",\"deployTime\":1510817190016,\"applicationPackageRevision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"clusterInfo\":{\"tripod\":{\"flavor\":\"d-3-16-100\",\"cost\":9,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"container\",\"hostnames\":[\"zt40738-v6-13.ostk.bm2.prod.bf1.yahoo.com\",\"zt40783-v6-31.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregation\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40819-v6-7.ostk.bm2.prod.bf1.yahoo.com\",\"zt40661-v6-3.ostk.bm2.prod.bf1.yahoo.com\",\"zt40805-v6-30.ostk.bm2.prod.bf1.yahoo.com\",\"zt40702-v6-32.ostk.bm2.prod.bf1.yahoo.com\",\"zt40706-v6-3.ostk.bm2.prod.bf1.yahoo.com\",\"zt40691-v6-27.ostk.bm2.prod.bf1.yahoo.com\",\"zt40676-v6-15.ostk.bm2.prod.bf1.yahoo.com\",\"zt40788-v6-23.ostk.bm2.prod.bf1.yahoo.com\",\"zt40782-v6-30.ostk.bm2.prod.bf1.yahoo.com\",\"zt40802-v6-32.ostk.bm2.prod.bf1.yahoo.com\"]},\"tripodaggregationstream\":{\"flavor\":\"d-12-64-400\",\"cost\":38,\"flavorCpu\":0,\"flavorMem\":0,\"flavorDisk\":0,\"clusterType\":\"content\",\"hostnames\":[\"zt40779-v6-27.ostk.bm2.prod.bf1.yahoo.com\",\"zt40791-v6-15.ostk.bm2.prod.bf1.yahoo.com\",\"zt40733-v6-31.ostk.bm2.prod.bf1.yahoo.com\",\"zt40724-v6-30.ostk.bm2.prod.bf1.yahoo.com\"]}},\"clusterUtils\":{\"tripod\":{\"cpu\":0.2295038983007097,\"mem\":0.4627357390237263,\"disk\":0.05559941525894966,\"diskbusy\":0},\"tripodaggregation\":{\"cpu\":0.05340429087579549,\"mem\":0.8107630891552372,\"disk\":0.226444914138854,\"diskbusy\":0},\"tripodaggregationstream\":{\"cpu\":0.02148227413975218,\"mem\":0.02162174219104161,\"disk\":0.006057760545243265,\"diskbusy\":0}},\"metrics\":{\"queriesPerSecond\":1.734000012278557,\"writesPerSecond\":44.59999895095825,\"documentCount\":525868193.9999999,\"queryLatencyMillis\":5.65284947195106,\"writeLatencyMillis\":17.34593812832452}}],\"deploymentJobs\":{\"projectId\":102889,\"jobStatus\":[{\"jobType\":\"staging-test\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830134259},\"lastCompleted\":{\"id\":1184,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830684960},\"lastSuccess\":{\"id\":1184,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"system-test completed\",\"at\":1510830684960}},{\"jobType\":\"component\",\"lastCompleted\":{\"id\":849,\"version\":\"6.174.156\",\"upgrade\":false,\"reason\":\"Application commit\",\"at\":1511217733555},\"lastSuccess\":{\"id\":849,\"version\":\"6.174.156\",\"upgrade\":false,\"reason\":\"Application commit\",\"at\":1511217733555}},{\"jobType\":\"production-us-east-3\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510830685127},\"lastCompleted\":{\"id\":923,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510837650046},\"lastSuccess\":{\"id\":923,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"staging-test completed\",\"at\":1510837650046}},{\"jobType\":\"production-us-west-1\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510837650139},\"lastCompleted\":{\"id\":646,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510843559162},\"lastSuccess\":{\"id\":646,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"production-us-east-3 completed\",\"at\":1510843559162}},{\"jobType\":\"system-test\",\"jobError\":\"unknown\",\"lastTriggered\":{\"id\":-1,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"Available change in component\",\"at\":1511256608649},\"lastCompleted\":{\"id\":1686,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"Available change in component\",\"at\":1511256603353},\"firstFailing\":{\"id\":1659,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"upgrade\":false,\"reason\":\"component completed\",\"at\":1511219070725},\"lastSuccess\":{\"id\":1658,\"version\":\"6.173.62\",\"revision\":{\"applicationPackageHash\":\"9db423e1021d7b452d37ec6372bc757d9c1bda87\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"49cd7bbb1ed9f4b922083cb042590b0885ffe22b\"}},\"upgrade\":true,\"reason\":\"Upgrading to 6.173.62\",\"at\":1511175754163}}]},\"deployingField\":{\"applicationPackageHash\":\"ec548fa61cbfab7a270a51d46b1263ec1be5d9a8\",\"sourceRevision\":{\"repositoryField\":\"git@git.ouroath.com:Tripod/service-aggregation-vespa.git\",\"branchField\":\"origin/master\",\"commitField\":\"234f3e4e77049d0b9538c9e1b356d17eb1dedb6a\"}},\"outstandingChangeField\":false,\"queryQuality\":100,\"writeQuality\":99.99894341115082}"; } diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java index 25b7d51b84f..19c4def819f 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java @@ -70,6 +70,10 @@ public class ControllerContainerTest { " <handler id='com.yahoo.vespa.hosted.controller.restapi.screwdriver.ScrewdriverApiHandler'>" + " <binding>http://*/screwdriver/v1/*</binding>" + " </handler>" + + " <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v1.ZoneApiHandler'>" + + " <binding>http://*/zone/v1</binding>" + + " <binding>http://*/zone/v1/*</binding>" + + " </handler>" + "</jdisc>"; protected void assertResponse(Request request, int responseStatus, String responseMessage) throws IOException { diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java new file mode 100644 index 00000000000..b4373532721 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/ZoneApiTest.java @@ -0,0 +1,50 @@ +// Copyright 2017 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.zone.v1; + +import com.yahoo.application.container.handler.Request; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; +import com.yahoo.vespa.hosted.controller.ZoneRegistryMock; +import com.yahoo.vespa.hosted.controller.restapi.ContainerControllerTester; +import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +/** + * @author mpolden + */ +public class ZoneApiTest extends ControllerContainerTest { + + private static final String responseFiles = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/"; + private static final List<Zone> zones = Arrays.asList( + new Zone(Environment.prod, RegionName.from("us-north-1")), + new Zone(Environment.dev, RegionName.from("us-north-2")), + new Zone(Environment.test, RegionName.from("us-north-3")), + new Zone(Environment.staging, RegionName.from("us-north-4")) + ); + + @Before + public void before() { + ZoneRegistryMock zoneRegistry = (ZoneRegistryMock) container.components() + .getComponent(ZoneRegistryMock.class.getName()); + zoneRegistry.setDefaultRegionForEnvironment(Environment.dev, RegionName.from("us-north-2")) + .setZones(zones); + } + + @Test + public void test_requests_v1() throws Exception { + ContainerControllerTester tester = new ContainerControllerTester(container, responseFiles); + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1"), + new File("root.json")); + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1/environment/prod"), + new File("prod.json")); + tester.containerTester().assertResponse(new Request("http://localhost:8080/zone/v1/environment/dev/default"), + new File("default-for-region.json")); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/default-for-region.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/default-for-region.json new file mode 100644 index 00000000000..ea7709dec98 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/default-for-region.json @@ -0,0 +1,4 @@ +{ + "name": "us-north-2", + "url": "http://localhost:8080/zone/v1/environment/dev/us-north-2" +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/prod.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/prod.json new file mode 100644 index 00000000000..cebf48e6428 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/prod.json @@ -0,0 +1,6 @@ +[ + { + "name": "us-north-1", + "url": "http://localhost:8080/zone/v2/environment/prod/region/us-north-1" + } +] diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/root.json new file mode 100644 index 00000000000..b3bd5247414 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/zone/v1/responses/root.json @@ -0,0 +1,18 @@ +[ + { + "name": "dev", + "url": "http://localhost:8080/zone/v2/environment/dev" + }, + { + "name": "prod", + "url": "http://localhost:8080/zone/v2/environment/prod" + }, + { + "name": "staging", + "url": "http://localhost:8080/zone/v2/environment/staging" + }, + { + "name": "test", + "url": "http://localhost:8080/zone/v2/environment/test" + } +] |