summaryrefslogtreecommitdiffstats
path: root/controller-server
diff options
context:
space:
mode:
authorValerij Fredriksen <valerijf@yahooinc.com>2023-03-29 12:15:29 +0200
committerValerij Fredriksen <valerijf@yahooinc.com>2023-03-29 12:24:00 +0200
commit538f72529022e703aea37c3baec7eba5403aedfd (patch)
treee69f6b8d71b62b71f8a9f6b93643a14bad0d88d3 /controller-server
parent4b57fc3f8936eb026b5231e79f8680bd30420bab (diff)
Add API to list zones
Diffstat (limited to 'controller-server')
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandler.java71
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ZoneApiMock.java15
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/ControllerContainerTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/CloudInfoApiHandlerTest.java51
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/root.json7
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/cloudinfo/responses/zones.json22
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"
+ }
+ ]
+}