summaryrefslogtreecommitdiffstats
path: root/hosted-api
diff options
context:
space:
mode:
authorJon Marius Venstad <jvenstad@yahoo-inc.com>2019-06-13 13:52:18 +0200
committerJon Marius Venstad <jvenstad@yahoo-inc.com>2019-06-13 13:52:18 +0200
commit2bb71582b979a83a39260f3f11466737b94ee47f (patch)
treeac4d17548ae440348a7ecd62bbd87cfa998c6496 /hosted-api
parent7d2366a939f64f964a208e01c4455dd530f833c6 (diff)
Wrap TestConfig in TestRuntime, which includes and Authenticator, and obtain the former from controller
Diffstat (limited to 'hosted-api')
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/Authenticator.java23
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java43
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java55
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java70
4 files changed, 179 insertions, 12 deletions
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/Authenticator.java b/hosted-api/src/main/java/ai/vespa/hosted/api/Authenticator.java
new file mode 100644
index 00000000000..acd8a215e7f
--- /dev/null
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/Authenticator.java
@@ -0,0 +1,23 @@
+package ai.vespa.hosted.api;
+
+import javax.net.ssl.SSLContext;
+import java.net.http.HttpRequest;
+import java.util.Optional;
+
+/**
+ * Adds environment dependent authentication to HTTP request against hosted Vespa API and deployments.
+ *
+ * @author jonmv
+ */
+public interface Authenticator {
+
+ /** Returns an SSLContext which provides authentication against a Vespa endpoint. */
+ SSLContext sslContext();
+
+ /** Adds necessary authentication to the given HTTP request builder, to pass the data plane of a Vespa endpoint. */
+ HttpRequest.Builder authenticated(HttpRequest.Builder request);
+
+ /** Returns a client authenticated to talk to the hosted Vespa API. */
+ ControllerHttpClient controller();
+
+}
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
index d59eb166e2b..153a03e56d2 100644
--- a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
@@ -104,14 +104,14 @@ public abstract class ControllerHttpClient {
/** Deactivates the deployment of the given application in the given zone. */
public String deactivate(ApplicationId id, ZoneId zone) {
- return asText(send(request(HttpRequest.newBuilder(deploymentPath(id, zone))
- .timeout(Duration.ofSeconds(10)),
- DELETE)));
+ return asString(send(request(HttpRequest.newBuilder(deploymentPath(id, zone))
+ .timeout(Duration.ofSeconds(10)),
+ DELETE)));
}
- /** Returns the default {@link Environment#dev} {@link ZoneId}, to use for development deployments. */
- public ZoneId devZone() {
- Inspector rootObject = toInspector(send(request(HttpRequest.newBuilder(defaultRegionPath())
+ /** Returns the default {@link ZoneId} for the given environment, if any. */
+ public ZoneId defaultZone(Environment environment) {
+ Inspector rootObject = toInspector(send(request(HttpRequest.newBuilder(defaultRegionPath(environment))
.timeout(Duration.ofSeconds(10)),
GET)));
return ZoneId.from("dev", rootObject.field("name").asString());
@@ -125,6 +125,11 @@ public abstract class ControllerHttpClient {
.field("compileVersion").asString();
}
+ /** Returns the test config for functional and verification tests of the indicated Vespa deployment. */
+ public TestConfig testConfig(ApplicationId id, ZoneId zone) {
+ return TestConfig.fromJson(asBytes(send(request(HttpRequest.newBuilder(testConfigPath(id, zone)), GET))));
+ }
+
/** Returns the sorted list of log entries after the given after from the deployment job of the given ids. */
public DeploymentLog deploymentLog(ApplicationId id, ZoneId zone, long run, long after) {
return toDeploymentLog(send(request(HttpRequest.newBuilder(runPath(id, zone, run, after))
@@ -137,6 +142,7 @@ public abstract class ControllerHttpClient {
return deploymentLog(id, zone, run, -1);
}
+ /** Returns an authenticated request from the given input. Override this for, e.g., request signing. */
protected HttpRequest request(HttpRequest.Builder request, Method method, Supplier<InputStream> data) {
return request.method(method.name(), ofInputStream(data)).build();
}
@@ -181,15 +187,22 @@ public abstract class ControllerHttpClient {
"deploy", jobNameOf(zone));
}
+ private URI jobPath(ApplicationId id, ZoneId zone) {
+ return concatenated(instancePath(id), "job", jobNameOf(zone));
+ }
+
+ private URI testConfigPath(ApplicationId id, ZoneId zone) {
+ return concatenated(instancePath(id), "test-config");
+ }
+
private URI runPath(ApplicationId id, ZoneId zone, long run, long after) {
- return withQuery(concatenated(instancePath(id),
- "job", jobNameOf(zone),
+ return withQuery(concatenated(jobPath(id, zone),
"run", Long.toString(run)),
"after", Long.toString(after));
}
- private URI defaultRegionPath() {
- return concatenated(endpoint, "zone", "v1", "environment", Environment.dev.value(), "default");
+ private URI defaultRegionPath(Environment environment) {
+ return concatenated(endpoint, "zone", "v1", "environment", environment.value(), "default");
}
private static URI concatenated(URI base, String... parts) {
@@ -247,9 +260,15 @@ public abstract class ControllerHttpClient {
return streamer;
}
- private static String asText(HttpResponse<byte[]> response) {
+ /** Returns the response body as a String, or throws if the status code is non-2XX. */
+ private static String asString(HttpResponse<byte[]> response) {
+ return new String(asBytes(response), UTF_8);
+ }
+
+ /** Returns the response body as a byte array, or throws if the status code is non-2XX. */
+ private static byte[] asBytes(HttpResponse<byte[]> response) {
toInspector(response);
- return new String(response.body(), UTF_8);
+ return response.body();
}
/** Returns an {@link Inspector} for the assumed JSON formatted response, or throws if the status code is non-2XX. */
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java b/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
new file mode 100644
index 00000000000..61893a30e7e
--- /dev/null
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
@@ -0,0 +1,55 @@
+package ai.vespa.hosted.api;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.RegionName;
+
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+
+/**
+ * Utilities and common definitions of system properties defining a Vespa application project.
+ *
+ * @author jonmv
+ */
+public class Properties {
+
+ public static ApplicationId application() {
+ return ApplicationId.from(requireNonBlankProperty("tenant"),
+ requireNonBlankProperty("application"),
+ getNonBlankProperty("instance").orElse("default"));
+ }
+
+ public static Optional<Environment> environment() {
+ return getNonBlankProperty("environment").map(Environment::from);
+ }
+
+ public static Optional<RegionName> region() {
+ return getNonBlankProperty("region").map(RegionName::from);
+ }
+
+ public static URI endpoint() {
+ return URI.create(requireNonBlankProperty("endpoint"));
+ }
+
+ public static Path privateKeyFile() {
+ return Paths.get(requireNonBlankProperty("privateKeyFile"));
+ }
+
+ public static Path certificateFile() {
+ return Paths.get(requireNonBlankProperty("certificateFile"));
+ }
+
+ /** Returns the system property with the given name if it is set, or empty. */
+ public static Optional<String> getNonBlankProperty(String name) {
+ return Optional.ofNullable(System.getProperty(name)).filter(value -> ! value.isBlank());
+ }
+
+ /** Returns the system property with the given name if it is set, or throws. */
+ public static String requireNonBlankProperty(String name) {
+ return getNonBlankProperty(name).orElseThrow(() -> new IllegalStateException("Missing required property '" + name + "'"));
+ }
+
+}
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java b/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java
new file mode 100644
index 00000000000..a0c679f312e
--- /dev/null
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/TestConfig.java
@@ -0,0 +1,70 @@
+package ai.vespa.hosted.api;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.zone.ZoneId;
+import com.yahoo.slime.Inspector;
+import com.yahoo.slime.JsonDecoder;
+import com.yahoo.slime.ObjectTraverser;
+import com.yahoo.slime.Slime;
+
+import java.net.URI;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Config required to run a functional or verification test of a Vespa deployment.
+ *
+ * @author jvenstad
+ */
+public class TestConfig {
+
+ private final ApplicationId application;
+ private final ZoneId zone;
+ private final SystemName system;
+ private final Map<ZoneId, Map<String, URI>> deployments;
+
+ public TestConfig(ApplicationId application, ZoneId zone, SystemName system, Map<ZoneId, Map<String, URI>> deployments) {
+ if ( ! deployments.containsKey(zone))
+ throw new IllegalArgumentException("Config must contain a deployment for its zone, but only does for " + deployments.keySet());
+ this.application = requireNonNull(application);
+ this.zone = requireNonNull(zone);
+ this.system = requireNonNull(system);
+ this.deployments = deployments.entrySet().stream()
+ .collect(Collectors.toUnmodifiableMap(entry -> entry.getKey(),
+ entry -> Map.copyOf(entry.getValue())));
+ }
+
+ public static TestConfig fromJson(byte[] jsonBytes) {
+ Inspector config = new JsonDecoder().decode(new Slime(), jsonBytes).get();
+ ApplicationId application = ApplicationId.fromSerializedForm(config.field("application").asString());
+ ZoneId zone = ZoneId.from(config.field("zone").asString());
+ SystemName system = SystemName.from(config.field("system").asString());
+ Map<ZoneId, Map<String, URI>> deployments = new HashMap<>();
+ config.field("clusterEndpoints").traverse((ObjectTraverser) (zoneId, endpointsObject) -> {
+ Map<String, URI> endpoints = new HashMap<>();
+ endpointsObject.traverse((ObjectTraverser) (cluster, uri) -> endpoints.put(cluster, URI.create(uri.asString())));
+ deployments.put(ZoneId.from(zoneId), endpoints);
+ });
+ return new TestConfig(application, zone, system, deployments);
+ }
+
+ /** Returns the full id of the application to test. */
+ public ApplicationId application() { return application; }
+
+ /** Returns the zone of the deployment to test. */
+ public ZoneId zone() { return zone; }
+
+ /** Returns an immutable view of deployments, per zone, of the application to test. */
+ public Map<ZoneId, Map<String, URI>> allDeployments() { return deployments; }
+
+ /** Returns the deployment to test in this test runtime. */
+ public Map<String, URI> deploymentToTest() { return deployments.get(zone); }
+
+ /** Returns the hosted Vespa system this is run against. */
+ public SystemName system() { return system; }
+
+}