diff options
Diffstat (limited to 'controller-server')
6 files changed, 167 insertions, 2 deletions
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandler.java new file mode 100644 index 00000000000..338bc29269a --- /dev/null +++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandler.java @@ -0,0 +1,71 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.cloudinfo; + +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.ThreadedHttpRequestHandler; +import com.yahoo.restapi.ErrorResponse; +import com.yahoo.restapi.Path; +import com.yahoo.restapi.ResourceResponse; +import com.yahoo.restapi.SlimeJsonResponse; +import com.yahoo.slime.Cursor; +import com.yahoo.slime.Slime; +import com.yahoo.vespa.hosted.controller.api.integration.ServiceRegistry; +import com.yahoo.vespa.hosted.controller.api.integration.zone.ZoneRegistry; +import com.yahoo.vespa.hosted.controller.restapi.ErrorResponses; +import com.yahoo.yolean.Exceptions; + +/** + * Read-only REST API that provides information about cloud resources (e.g. zones and flavors). + * + * @author freva + */ +@SuppressWarnings("unused") +public class CloudInfoApiHandler extends ThreadedHttpRequestHandler { + + private final ZoneRegistry zoneRegistry; + + public CloudInfoApiHandler(Context parentCtx, ServiceRegistry serviceRegistry) { + super(parentCtx); + this.zoneRegistry = serviceRegistry.zoneRegistry(); + } + + @Override + public HttpResponse handle(HttpRequest request) { + try { + return switch (request.getMethod()) { + case GET -> get(request); + default -> ErrorResponse.methodNotAllowed("Method '" + request.getMethod() + "' is unsupported"); + }; + } catch (IllegalArgumentException e) { + return ErrorResponse.badRequest(Exceptions.toMessageString(e)); + } catch (RuntimeException e) { + return ErrorResponses.logThrowing(request, log, e); + } + } + + private HttpResponse get(HttpRequest request) { + Path path = new Path(request.getUri()); + if (path.matches("/cloudinfo/v1")) return new ResourceResponse(request, "zones"); + if (path.matches("/cloudinfo/v1/zones")) return zones(request); + return notFound(path); + } + + private HttpResponse zones(HttpRequest request) { + Slime slime = new Slime(); + Cursor zones = slime.setObject().setArray("zones"); + zoneRegistry.zones().publiclyVisible().all().zones().forEach(zone -> { + Cursor object = zones.addObject(); + object.setString("environment", zone.getEnvironment().value()); + object.setString("region", zone.getRegionName().value()); + object.setString("cloud", zone.getCloudName().value()); + object.setString("availabilityZone", zone.getCloudNativeAvailabilityZone()); + }); + return new SlimeJsonResponse(slime); + } + + private HttpResponse notFound(Path path) { + return ErrorResponse.notFoundError("Nothing at " + path); + } + +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java index 528ef6d6192..6fd44e09d8d 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java @@ -20,15 +20,17 @@ public class ZoneApiMock implements ZoneApi { private final ZoneId virtualId; private final CloudName cloudName; private final String cloudNativeRegionName; + private final String cloudNativeAvailabilityZone; public static Builder newBuilder() { return new Builder(); } - private ZoneApiMock(SystemName systemName, ZoneId id, ZoneId virtualId, CloudName cloudName, String cloudNativeRegionName) { + private ZoneApiMock(SystemName systemName, ZoneId id, ZoneId virtualId, CloudName cloudName, String cloudNativeRegionName, String cloudNativeAvailabilityZone) { this.systemName = systemName; this.id = id; this.virtualId = virtualId; this.cloudName = cloudName; this.cloudNativeRegionName = cloudNativeRegionName; + this.cloudNativeAvailabilityZone = cloudNativeAvailabilityZone; if (virtualId != null && virtualId.equals(id)) { throw new IllegalArgumentException("Virtual ID cannot be equal to zone ID: " + id); } @@ -64,6 +66,9 @@ public class ZoneApiMock implements ZoneApi { public String getCloudNativeRegionName() { return cloudNativeRegionName; } @Override + public String getCloudNativeAvailabilityZone() { return cloudNativeAvailabilityZone; } + + @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; @@ -88,6 +93,7 @@ public class ZoneApiMock implements ZoneApi { private ZoneId virtualId = null; private CloudName cloudName = CloudName.DEFAULT; private String cloudNativeRegionName = id.region().value(); + private String cloudNativeAvailabilityZone = "az1"; public Builder with(ZoneId id) { this.id = id; @@ -124,8 +130,13 @@ public class ZoneApiMock implements ZoneApi { return this; } + public Builder withCloudNativeAvailabilityZone(String cloudAvailabilityZone) { + this.cloudNativeAvailabilityZone = cloudAvailabilityZone; + return this; + } + public ZoneApiMock build() { - return new ZoneApiMock(systemName, id, virtualId, cloudName, cloudNativeRegionName); + return new ZoneApiMock(systemName, id, virtualId, cloudName, cloudNativeRegionName, cloudNativeAvailabilityZone); } } 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 7522f42f91b..30f74f4bf70 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 @@ -96,6 +96,9 @@ public class ControllerContainerTest { <handler id='com.yahoo.vespa.hosted.controller.restapi.os.OsApiHandler'> <binding>http://localhost/os/v1/*</binding> </handler> + <handler id='com.yahoo.vespa.hosted.controller.restapi.cloudinfo.CloudInfoApiHandler'> + <binding>http://localhost/cloudinfo/v1/*</binding> + </handler> <handler id='com.yahoo.vespa.hosted.controller.restapi.zone.v2.ZoneApiHandler'> <binding>http://localhost/zone/v2</binding> <binding>http://localhost/zone/v2/*</binding> diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandlerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandlerTest.java new file mode 100644 index 00000000000..c4ce687504e --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandlerTest.java @@ -0,0 +1,51 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.controller.restapi.cloudinfo; + +import com.yahoo.config.provision.CloudName; +import com.yahoo.config.provision.SystemName; +import com.yahoo.config.provision.zone.ZoneApi; +import com.yahoo.config.provision.zone.ZoneId; +import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock; +import com.yahoo.vespa.hosted.controller.restapi.ContainerTester; +import com.yahoo.vespa.hosted.controller.restapi.ControllerContainerTest; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.io.File; + +/** + * @author freva + */ +class CloudInfoApiHandlerTest extends ControllerContainerTest { + private static final String responses = "src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/"; + + private ContainerTester tester; + + @Override + protected SystemName system() { + return SystemName.cd; + } + + @BeforeEach + public void before() { + tester = new ContainerTester(container, responses); + tester.serviceRegistry().zoneRegistry().setZones( + zone(CloudName.AWS, "prod", "aws-us-east-1a", "use1-az1"), + zone(CloudName.AWS, "prod", "aws-us-west-2c", "usw2-az4"), + zone(CloudName.GCP, "prod", "gcp-us-east1-f", "us-east1-f")); + } + + @Test + void test_api() { + tester.assertResponse(authenticatedRequest("http://localhost:8080/cloudinfo/v1/"), new File("root.json")); + tester.assertResponse(authenticatedRequest("http://localhost:8080/cloudinfo/v1/zones"), new File("zones.json")); + } + + private static ZoneApi zone(CloudName cloudName, String environment, String region, String availabilityZone) { + return ZoneApiMock.newBuilder() + .with(cloudName) + .with(ZoneId.from(environment, region)) + .withCloudNativeAvailabilityZone(availabilityZone) + .build(); + } +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/root.json new file mode 100644 index 00000000000..a733018428e --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/root.json @@ -0,0 +1,7 @@ +{ + "resources": [ + { + "url": "http://localhost:8080/cloudinfo/v1/zones/" + } + ] +} diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/zones.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/zones.json new file mode 100644 index 00000000000..d884b2c06a3 --- /dev/null +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/zones.json @@ -0,0 +1,22 @@ +{ + "zones": [ + { + "environment": "prod", + "region": "aws-us-east-1a", + "cloud": "aws", + "availabilityZone": "use1-az1" + }, + { + "environment": "prod", + "region": "aws-us-west-2c", + "cloud": "aws", + "availabilityZone": "usw2-az4" + }, + { + "environment": "prod", + "region": "gcp-us-east1-f", + "cloud": "gcp", + "availabilityZone": "us-east1-f" + } + ] +} |