summaryrefslogtreecommitdiffstats
path: root/configserver
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2023-09-28 15:31:08 +0200
committerjonmv <venstad@gmail.com>2023-09-28 15:39:53 +0200
commitd10c4a0aebadf5ffb73218b8444d489a79b159ae (patch)
treeb912f7966b0ad7e30197f33db71ccf5eef350271 /configserver
parent71bfc352333411f0a3bdebdb7cde032fdb2ff96a (diff)
Fetch active tokens for an application, through /application/v2/
Diffstat (limited to 'configserver')
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java21
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java16
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java119
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java12
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java121
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java15
6 files changed, 301 insertions, 3 deletions
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index 63faf806e9c..058606a789e 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -41,16 +41,17 @@ import com.yahoo.slime.Slime;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
import com.yahoo.vespa.applicationmodel.InfrastructureApplication;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprintsClient;
import com.yahoo.vespa.config.server.application.Application;
import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase;
import com.yahoo.vespa.config.server.application.ApplicationData;
import com.yahoo.vespa.config.server.application.ApplicationReindexing;
-import com.yahoo.vespa.config.server.application.ApplicationReindexing.Status;
import com.yahoo.vespa.config.server.application.ApplicationVersions;
import com.yahoo.vespa.config.server.application.ClusterReindexing;
import com.yahoo.vespa.config.server.application.ClusterReindexingStatusClient;
import com.yahoo.vespa.config.server.application.CompressedApplicationInputStream;
import com.yahoo.vespa.config.server.application.ConfigConvergenceChecker;
+import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints;
import com.yahoo.vespa.config.server.application.DefaultClusterReindexingStatusClient;
import com.yahoo.vespa.config.server.application.FileDistributionStatus;
import com.yahoo.vespa.config.server.application.HttpProxy;
@@ -129,7 +130,6 @@ import static com.yahoo.vespa.config.server.tenant.TenantRepository.HOSTED_VESPA
import static com.yahoo.vespa.curator.Curator.CompletionWaiter;
import static com.yahoo.yolean.Exceptions.uncheck;
import static java.nio.file.Files.readAttributes;
-import static java.util.Comparator.naturalOrder;
/**
* The API for managing applications.
@@ -159,6 +159,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private final Metric metric;
private final SecretStoreValidator secretStoreValidator;
private final ClusterReindexingStatusClient clusterReindexingStatusClient;
+ private final ActiveTokenFingerprints activeTokenFingerprints;
private final FlagSource flagSource;
@Inject
@@ -188,6 +189,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
metric,
new SecretStoreValidator(secretStore),
new DefaultClusterReindexingStatusClient(),
+ new ActiveTokenFingerprintsClient(),
flagSource);
}
@@ -205,6 +207,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
Metric metric,
SecretStoreValidator secretStoreValidator,
ClusterReindexingStatusClient clusterReindexingStatusClient,
+ ActiveTokenFingerprints activeTokenFingerprints,
FlagSource flagSource) {
this.tenantRepository = Objects.requireNonNull(tenantRepository);
this.hostProvisioner = Objects.requireNonNull(hostProvisioner);
@@ -219,7 +222,8 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
this.testerClient = Objects.requireNonNull(testerClient);
this.metric = Objects.requireNonNull(metric);
this.secretStoreValidator = Objects.requireNonNull(secretStoreValidator);
- this.clusterReindexingStatusClient = clusterReindexingStatusClient;
+ this.clusterReindexingStatusClient = Objects.requireNonNull(clusterReindexingStatusClient);
+ this.activeTokenFingerprints = Objects.requireNonNull(activeTokenFingerprints);
this.flagSource = flagSource;
}
@@ -237,6 +241,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
private SecretStoreValidator secretStoreValidator = new SecretStoreValidator(new SecretStoreProvider().get());
private FlagSource flagSource = new InMemoryFlagSource();
private ConfigConvergenceChecker configConvergenceChecker = new ConfigConvergenceChecker();
+ private Map<String, List<String>> activeTokens = Map.of();
public Builder withTenantRepository(TenantRepository tenantRepository) {
this.tenantRepository = tenantRepository;
@@ -298,6 +303,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return this;
}
+ public Builder withActiveTokens(Map<String, List<String>> tokens) {
+ this.activeTokens = tokens;
+ return this;
+ }
+
public ApplicationRepository build() {
return new ApplicationRepository(tenantRepository,
tenantRepository.hostProvisionerProvider().getHostProvisioner(),
@@ -313,6 +323,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
metric,
secretStoreValidator,
ClusterReindexingStatusClient.DUMMY_INSTANCE,
+ __ -> activeTokens,
flagSource);
}
@@ -612,6 +623,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
return uncheck(() -> clusterReindexingStatusClient.getReindexingStatus(getApplication(applicationId)));
}
+ public Map<String, List<String>> activeTokenFingerprints(ApplicationId applicationId) {
+ return activeTokenFingerprints.get(getApplication(applicationId));
+ }
+
public Long getApplicationGeneration(ApplicationId applicationId) {
return getApplication(applicationId).getApplicationGeneration();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java
new file mode 100644
index 00000000000..cf67d6e4abd
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java
@@ -0,0 +1,16 @@
+package com.yahoo.vespa.config.server.application;
+
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author jonmv
+ */
+public interface ActiveTokenFingerprints {
+
+ /** Lists all active token fingerprints for each token-enabled container host in the application, that is currently up. */
+ Map<String, List<String>> get(ModelResult application);
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java
new file mode 100644
index 00000000000..d5beb263155
--- /dev/null
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java
@@ -0,0 +1,119 @@
+package com.yahoo.vespa.config.server.application;
+
+import ai.vespa.http.DomainName;
+import ai.vespa.http.HttpURL;
+import ai.vespa.http.HttpURL.Path;
+import ai.vespa.http.HttpURL.Scheme;
+import ai.vespa.util.http.hc5.VespaAsyncHttpClientBuilder;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.AuthMethod;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.slime.Inspector;
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+import org.apache.hc.client5.http.async.methods.SimpleHttpResponse;
+import org.apache.hc.client5.http.async.methods.SimpleRequestBuilder;
+import org.apache.hc.client5.http.config.ConnectionConfig;
+import org.apache.hc.client5.http.config.RequestConfig;
+import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient;
+import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder;
+import org.apache.hc.core5.concurrent.FutureCallback;
+import org.apache.hc.core5.io.CloseMode;
+import org.apache.hc.core5.reactor.IOReactorConfig;
+import org.apache.hc.core5.util.TimeValue;
+import org.apache.hc.core5.util.Timeout;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Phaser;
+
+import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
+import static com.yahoo.config.model.api.container.ContainerServiceType.QRSERVER;
+import static com.yahoo.slime.SlimeUtils.entriesStream;
+import static com.yahoo.slime.SlimeUtils.jsonToSlime;
+import static java.util.stream.Collectors.toSet;
+
+/**
+ * @author jonmv
+ */
+public class ActiveTokenFingerprintsClient implements ActiveTokenFingerprints, AutoCloseable {
+
+ private final CloseableHttpAsyncClient httpClient = createHttpClient();
+
+ public ActiveTokenFingerprintsClient() {
+ httpClient.start();
+ }
+
+ @Override
+ public Map<String, List<String>> get(ModelResult application) {
+ Set<String> containersWithTokenFilter = application.getModel().applicationClusterInfo().stream()
+ .flatMap(cluster -> cluster.endpoints().stream())
+ .filter(endpoint -> endpoint.authMethod() == AuthMethod.token)
+ .flatMap(endpoint -> endpoint.hostNames().stream())
+ .collect(toSet());
+ return getFingerprints(application.getModel().getHosts().stream()
+ .filter(host -> containersWithTokenFilter.contains(host.getHostname()))
+ .flatMap(host -> host.getServices().stream())
+ .filter(service -> service.getServiceType().equals(CONTAINER.serviceName)
+ || service.getServiceType().equals(QRSERVER.serviceName))
+ .toList());
+ }
+
+ private Map<String, List<String>> getFingerprints(List<ServiceInfo> services) {
+ Map<String, List<String>> fingerprints = new ConcurrentHashMap<>();
+ Phaser phaser = new Phaser(services.size() + 1);
+ for (ServiceInfo service : services) getFingerprints(fingerprints, service, phaser);
+ phaser.arriveAndAwaitAdvance();
+ return fingerprints;
+ }
+
+ // A container may be unable to provide its fingerprints for a number of reasons, which may be OK, so
+ // we only track those containers which return an OK response, but we do require at least one such response.
+ private void getFingerprints(Map<String, List<String>> hostFingerprints, ServiceInfo service, Phaser phaser) {
+ URI uri = HttpURL.create(Scheme.http,
+ DomainName.of(service.getHostName()),
+ service.getPorts().stream().filter(port -> port.getTags().stream().anyMatch("http"::equals)).findAny().get().getPort(),
+ Path.parse("/data-plane-tokens/v1"))
+ .asURI();
+ httpClient.execute(SimpleRequestBuilder.get(uri).build(), new FutureCallback<>() {
+ @Override public void completed(SimpleHttpResponse result) {
+ if (result.getCode() == 200) hostFingerprints.put(service.getHostName(), parseFingerprints(result));
+ phaser.arrive();
+ }
+ @Override public void failed(Exception ex) { phaser.arrive(); }
+ @Override public void cancelled() { phaser.arrive(); }
+ });
+ }
+
+ private static List<String> parseFingerprints(SimpleHttpResponse response) {
+ return entriesStream(jsonToSlime(response.getBodyBytes()).get().field("fingerprints")).map(Inspector::asString).toList();
+ }
+
+ private static CloseableHttpAsyncClient createHttpClient() {
+ return VespaAsyncHttpClientBuilder
+ .create(tlsStrategy -> PoolingAsyncClientConnectionManagerBuilder.create()
+ .setTlsStrategy(tlsStrategy)
+ .setDefaultConnectionConfig(ConnectionConfig.custom()
+ .setConnectTimeout(Timeout.ofSeconds(2))
+ .build())
+ .build())
+ .setIOReactorConfig(IOReactorConfig.custom()
+ .setSoTimeout(Timeout.ofSeconds(2))
+ .build())
+ .setDefaultRequestConfig(
+ RequestConfig.custom()
+ .setConnectionRequestTimeout(Timeout.ofSeconds(2))
+ .setResponseTimeout(Timeout.ofSeconds(2))
+ .build())
+ .setUserAgent("data-plane-token-client")
+ .build();
+ }
+
+ @Override
+ public void close() throws Exception {
+ httpClient.close(CloseMode.GRACEFUL);
+ httpClient.awaitShutdown(TimeValue.ofSeconds(10));
+ }
+
+}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index 5206308a664..69d0365f089 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -95,6 +95,7 @@ public class ApplicationHandler extends HttpHandler {
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}")) return getApplicationResponse(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/content/{*}")) return content(applicationId(path), path.getRest(), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/filedistributionstatus")) return filedistributionStatus(applicationId(path), request);
+ if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/active-token-fingerprints")) return activeTokenFingerprints(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/logs")) return logs(applicationId(path), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/deployment")) return deploymentMetrics(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/searchnode")) return searchNodeMetrics(applicationId(path));
@@ -187,6 +188,17 @@ public class ApplicationHandler extends HttpHandler {
return applicationRepository.fileDistributionStatus(applicationId, getTimeoutFromRequest(request));
}
+ private HttpResponse activeTokenFingerprints(ApplicationId applicationId) {
+ Slime slime = new Slime();
+ Cursor hostsArray = slime.setObject().setArray("hosts");
+ applicationRepository.activeTokenFingerprints(applicationId).forEach((host, fingerprints) -> {
+ Cursor hostObject = hostsArray.addObject();
+ hostObject.setString("host", host);
+ fingerprints.forEach(hostObject.setArray("fingerprints")::addString);
+ });
+ return new SlimeJsonResponse(slime);
+ }
+
private HttpResponse logs(ApplicationId applicationId, HttpRequest request) {
Optional<DomainName> hostname = Optional.ofNullable(request.getProperty("hostname")).map(DomainName::of);
String apiParams = Optional.ofNullable(request.getUri().getQuery()).map(q -> "?" + q).orElse("");
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java
new file mode 100644
index 00000000000..dea224d2e9b
--- /dev/null
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java
@@ -0,0 +1,121 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.config.server.application;// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.yahoo.config.ConfigInstance.Builder;
+import com.yahoo.config.FileReference;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.AuthMethod;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.DnsName;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.RoutingMethod;
+import com.yahoo.config.model.api.ApplicationClusterEndpoint.Scope;
+import com.yahoo.config.model.api.ApplicationClusterInfo;
+import com.yahoo.config.model.api.HostInfo;
+import com.yahoo.config.model.api.Model;
+import com.yahoo.config.model.api.PortInfo;
+import com.yahoo.config.model.api.ServiceInfo;
+import com.yahoo.config.provision.AllocatedHosts;
+import com.yahoo.vespa.config.ConfigKey;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.config.server.modelfactory.ModelResult;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.okJson;
+import static com.github.tomakehurst.wiremock.client.WireMock.serverError;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
+import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author jonmv
+ */
+public class ActiveTokenFingerprintsClientTest {
+
+ @Rule public final WireMockRule server1 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server2 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server3 = new WireMockRule(options().dynamicPort(), true);
+ @Rule public final WireMockRule server4 = new WireMockRule(options().dynamicPort(), true);
+
+ @Test
+ public void verifyMultipleResponsesCombine() throws Exception {
+ try (ActiveTokenFingerprintsClient client = new ActiveTokenFingerprintsClient()) {
+ ModelResult app = MockModel::new;
+ String uriPath = "/data-plane-tokens/v1";
+ server1.stubFor(get(urlEqualTo(uriPath)).willReturn(serverError()));
+ server2.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson("""
+ { "fingerprints": [ "foo", "bar", "baz" ] }
+ """)));
+ server3.stubFor(get(urlEqualTo(uriPath)).willReturn(aResponse().withStatus(503)));
+ server4.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson("""
+ { "fingerprints": [ "quu", "qux", "fez" ] }
+ """)));
+ Map<String, List<String>> expected = Map.of("localhost", List.of("foo", "bar", "baz"));
+ assertEquals(expected, client.get(app));
+ }
+ }
+
+ private class MockModel implements Model {
+
+ @Override
+ public Collection<HostInfo> getHosts() {
+ return List.of(host(server1.port(), "localhost"),
+ host(server2.port(), "localhost"),
+ host(server3.port(), "localhost"),
+ host(server4.port(), "127.0.0.1")); // Should not be included, see application cluster info below.
+
+ }
+
+ private HostInfo host(int port, String host) {
+ return new HostInfo(host,
+ List.of(new ServiceInfo("container",
+ CONTAINER.serviceName,
+ List.of(new PortInfo(port, List.of("http"))),
+ Map.of(),
+ "myconfigId",
+ host),
+ new ServiceInfo("logserver",
+ LOGSERVER_CONTAINER.serviceName,
+ List.of(new PortInfo(port, List.of("http"))),
+ Map.of(),
+ "myconfigId",
+ "127.0.0.1"))); // Don't hit this.
+ }
+
+ @Override
+ public Set<ApplicationClusterInfo> applicationClusterInfo() {
+ return Set.of(new ApplicationClusterInfo() {
+ @Override public List<ApplicationClusterEndpoint> endpoints() {
+ return List.of(ApplicationClusterEndpoint.builder()
+ .dnsName(DnsName.from("foo"))
+ .routingMethod(RoutingMethod.exclusive)
+ .authMethod(AuthMethod.token)
+ .scope(Scope.zone)
+ .clusterId("bar")
+ .hosts(List.of("localhost"))
+ .build());
+ }
+ @Override public boolean getDeferChangesUntilRestart() { throw new UnsupportedOperationException(); }
+ @Override public String name() { throw new UnsupportedOperationException(); }
+ });
+ }
+
+ @Override public Builder getConfigInstance(ConfigKey<?> configKey, ConfigDefinition configDefinition) { throw new UnsupportedOperationException(); }
+ @Override public Set<ConfigKey<?>> allConfigsProduced() { throw new UnsupportedOperationException(); }
+ @Override public Set<String> allConfigIds() { throw new UnsupportedOperationException(); }
+ @Override public Set<FileReference> fileReferences() { throw new UnsupportedOperationException(); }
+ @Override public AllocatedHosts allocatedHosts() { throw new UnsupportedOperationException(); }
+
+ }
+
+}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 951ef9df2f4..5b2d5d491a1 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -117,6 +117,7 @@ public class ApplicationHandlerTest {
private ManualClock clock;
private List<Endpoint> expectedEndpoints;
private Availability availability;
+ private Map<String, List<String>> activeTokenFingerprints;
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@@ -140,6 +141,7 @@ public class ApplicationHandlerTest {
.build();
tenantRepository.addTenant(mytenantName);
orchestrator = new OrchestratorMock();
+ activeTokenFingerprints = new HashMap<>();
applicationRepository = new ApplicationRepository.Builder()
.withTenantRepository(tenantRepository)
.withOrchestrator(orchestrator)
@@ -149,6 +151,7 @@ public class ApplicationHandlerTest {
.withConfigserverConfig(configserverConfig)
.withSecretStoreValidator(secretStoreValidator)
.withEndpointsChecker(endpoints -> { assertEquals(expectedEndpoints, endpoints); return availability; })
+ .withActiveTokens(activeTokenFingerprints)
.build();
}
@@ -238,6 +241,18 @@ public class ApplicationHandlerTest {
}
@Test
+ public void testGetTokenFingerprints() throws IOException {
+ applicationRepository.deploy(testApp, prepareParams(applicationId));
+ activeTokenFingerprints.putAll(Map.of("host", List.of("fingers", "toes"),
+ "toast", List.of()));
+ HttpResponse response = createApplicationHandler().handleGET(createTestRequest(toUrlPath(applicationId, Zone.defaultZone(), true) + "/active-token-fingerprints", GET));
+ assertEquals(200, response.getStatus());
+ assertEquals("""
+ {"hosts":[{"host":"host","fingerprints":["fingers","toes"]},{"host":"toast","fingerprints":[]}]}""",
+ getRenderedString(response));
+ }
+
+ @Test
public void testReindex() throws Exception {
ApplicationCuratorDatabase database = applicationRepository.getTenant(applicationId).getApplicationRepo().database();
reindexing(applicationId, GET, "{\"error-code\": \"NOT_FOUND\", \"message\": \"Application 'default.default' not found\"}", 404);