diff options
Diffstat (limited to 'controller-server')
8 files changed, 253 insertions, 8 deletions
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/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/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" + } +] |