diff options
author | Jon Marius Venstad <jonmv@users.noreply.github.com> | 2023-09-29 14:21:20 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-09-29 14:21:20 +0200 |
commit | eb4d4bee35b5152f9b461f33e2467978d8486e24 (patch) | |
tree | a75e34c6716abad9e5fbaead4e18eef58365fed7 /configserver | |
parent | f0862db365dd351f5adc480649f9aba18cbd409d (diff) | |
parent | 1b0cfae1d7c756c2caeef468d5d2725f81493fdc (diff) |
Merge pull request #28711 from vespa-engine/jonmv/token-tell-handler
Jonmv/token tell handler
Diffstat (limited to 'configserver')
7 files changed, 333 insertions, 30 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..e675e00b642 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,18 @@ 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.ActiveTokenFingerprints.Token; +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 +131,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 +160,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 +190,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye metric, new SecretStoreValidator(secretStore), new DefaultClusterReindexingStatusClient(), + new ActiveTokenFingerprintsClient(), flagSource); } @@ -205,6 +208,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 +223,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 +242,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<Token>> activeTokens = Map.of(); public Builder withTenantRepository(TenantRepository tenantRepository) { this.tenantRepository = tenantRepository; @@ -298,6 +304,11 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return this; } + public Builder withActiveTokens(Map<String, List<Token>> tokens) { + this.activeTokens = tokens; + return this; + } + public ApplicationRepository build() { return new ApplicationRepository(tenantRepository, tenantRepository.hostProvisionerProvider().getHostProvisioner(), @@ -313,6 +324,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye metric, secretStoreValidator, ClusterReindexingStatusClient.DUMMY_INSTANCE, + __ -> activeTokens, flagSource); } @@ -612,6 +624,10 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye return uncheck(() -> clusterReindexingStatusClient.getReindexingStatus(getApplication(applicationId))); } + public Map<String, List<Token>> 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..9cde5e38302 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprints.java @@ -0,0 +1,18 @@ +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 tokens and their fingerprints for each token-enabled container host in the application, that is currently up. */ + Map<String, List<Token>> get(ModelResult application); + + record Token(String id, List<String> fingerprints) { } + +} 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..4e9eac7a9a6 --- /dev/null +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClient.java @@ -0,0 +1,123 @@ +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.toMap; +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<Token>> 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<Token>> getFingerprints(List<ServiceInfo> services) { + Map<String, List<Token>> tokens = new ConcurrentHashMap<>(); + Phaser phaser = new Phaser(services.size() + 1); + for (ServiceInfo service : services) getFingerprints(tokens, service, phaser); + phaser.arriveAndAwaitAdvance(); + return tokens; + } + + // 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<Token>> hostTokens, 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) hostTokens.put(service.getHostName(), parseTokens(result)); + phaser.arrive(); + } + @Override public void failed(Exception ex) { phaser.arrive(); } + @Override public void cancelled() { phaser.arrive(); } + }); + } + + private static List<Token> parseTokens(SimpleHttpResponse response) { + return entriesStream(jsonToSlime(response.getBodyBytes()).get().field("tokens")) + .map(entry -> new Token(entry.field("id").asString(), + entriesStream(entry.field("fingerprints")).map(Inspector::asString).toList())) + .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 bd6e0f90b54..f39feceeeb1 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,9 +95,11 @@ 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)); + if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/reindexing")) return getReindexingStatus(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/status/{*}")) return serviceStatusPage(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/state/v1/{*}")) return serviceStateV1(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request); @@ -105,7 +107,6 @@ public class ApplicationHandler extends HttpHandler { if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/serviceconverge/{hostAndPort}")) return checkServiceConverge(applicationId(path), path.get("hostAndPort"), request); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/suspended")) return isSuspended(applicationId(path)); if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/tester/{command}")) return testerRequest(applicationId(path), path.get("command"), request); - if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/quota")) return quotaUsage(applicationId(path)); return ErrorResponse.notFoundError("Nothing at " + path); } @@ -150,18 +151,11 @@ public class ApplicationHandler extends HttpHandler { } private HttpResponse serviceStatusPage(ApplicationId applicationId, String service, String hostname, HttpURL.Path pathSuffix, HttpRequest request) { - HttpURL.Path pathPrefix = HttpURL.Path.empty(); - switch (service) { - case "container-clustercontroller": - pathPrefix = pathPrefix.append("clustercontroller-status").append("v1"); - break; - case "distributor": - case "storagenode": - pathPrefix = pathPrefix.append("contentnode-status").append("v1"); - break; - default: - throw new com.yahoo.vespa.config.server.NotFoundException("No status page for service: " + service); - } + HttpURL.Path pathPrefix = switch (service) { + case "container-clustercontroller" -> HttpURL.Path.empty().append("clustercontroller-status").append("v1"); + case "distributor", "storagenode" -> HttpURL.Path.empty().append("contentnode-status").append("v1"); + default -> throw new NotFoundException("No status page for service: " + service); + }; return applicationRepository.proxyServiceHostnameRequest(applicationId, hostname, service, pathPrefix.append(pathSuffix), Query.empty().add(request.getJDiscRequest().parameters()), null); } @@ -194,6 +188,22 @@ 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, tokens) -> { + Cursor hostObject = hostsArray.addObject(); + hostObject.setString("host", host); + Cursor tokensArray = hostObject.setArray("tokens"); + tokens.forEach(token -> { + Cursor tokenObject = tokensArray.addObject(); + tokenObject.setString("id", token.id()); + token.fingerprints().forEach(tokenObject.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(""); @@ -213,19 +223,13 @@ public class ApplicationHandler extends HttpHandler { } private HttpResponse testerRequest(ApplicationId applicationId, String command, HttpRequest request) { - switch (command) { - case "status": - return applicationRepository.getTesterStatus(applicationId); - case "log": - Long after = Long.valueOf(request.getProperty("after")); - return applicationRepository.getTesterLog(applicationId, after); - case "ready": - return applicationRepository.isTesterReady(applicationId); - case "report": - return applicationRepository.getTestReport(applicationId); - default: - throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString()); - } + return switch (command) { + case "status" -> applicationRepository.getTesterStatus(applicationId); + case "log" -> applicationRepository.getTesterLog(applicationId, Long.valueOf(request.getProperty("after"))); + case "ready" -> applicationRepository.isTesterReady(applicationId); + case "report" -> applicationRepository.getTestReport(applicationId); + default -> throw new IllegalArgumentException("Unknown tester command in request " + request.getUri().toString()); + }; } private HttpResponse quotaUsage(ApplicationId applicationId) { 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..03e379311cc --- /dev/null +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/ActiveTokenFingerprintsClientTest.java @@ -0,0 +1,123 @@ +// 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.application.ActiveTokenFingerprints.Token; +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(""" + { "tokens": [ {"id": "t1", "fingerprints": [ "foo", "bar", "baz" ] } ] } + """))); + server3.stubFor(get(urlEqualTo(uriPath)).willReturn(aResponse().withStatus(503))); + server4.stubFor(get(urlEqualTo(uriPath)).willReturn(okJson(""" + { "tokens": [ {"id": "t2", "fingerprints": [ "quu" ] } ] } + """))); + Map<String, List<Token>> expected = Map.of("localhost", + List.of(new Token("t1", 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/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java index 6bd20a29cf8..72cfe466993 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java @@ -10,6 +10,8 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.Map; +import static java.nio.charset.StandardCharsets.UTF_8; + /** * Base class for session handler tests * @@ -52,7 +54,7 @@ public class SessionHandlerTest { public static String getRenderedString(HttpResponse response) throws IOException { ByteArrayOutputStream baos = new ByteArrayOutputStream(); response.render(baos); - return baos.toString(StandardCharsets.UTF_8); + return baos.toString(UTF_8); } public enum Cmd { 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..6fb5db70b68 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 @@ -28,6 +28,7 @@ import com.yahoo.vespa.config.server.MockLogRetriever; import com.yahoo.vespa.config.server.MockProvisioner; import com.yahoo.vespa.config.server.MockSecretStoreValidator; import com.yahoo.vespa.config.server.MockTesterClient; +import com.yahoo.vespa.config.server.application.ActiveTokenFingerprints.Token; import com.yahoo.vespa.config.server.application.ApplicationCuratorDatabase; import com.yahoo.vespa.config.server.application.ApplicationReindexing; import com.yahoo.vespa.config.server.application.ClusterReindexing; @@ -117,6 +118,7 @@ public class ApplicationHandlerTest { private ManualClock clock; private List<Endpoint> expectedEndpoints; private Availability availability; + private Map<String, List<Token>> activeTokenFingerprints; @Rule public TemporaryFolder temporaryFolder = new TemporaryFolder(); @@ -140,6 +142,7 @@ public class ApplicationHandlerTest { .build(); tenantRepository.addTenant(mytenantName); orchestrator = new OrchestratorMock(); + activeTokenFingerprints = new HashMap<>(); applicationRepository = new ApplicationRepository.Builder() .withTenantRepository(tenantRepository) .withOrchestrator(orchestrator) @@ -149,6 +152,7 @@ public class ApplicationHandlerTest { .withConfigserverConfig(configserverConfig) .withSecretStoreValidator(secretStoreValidator) .withEndpointsChecker(endpoints -> { assertEquals(expectedEndpoints, endpoints); return availability; }) + .withActiveTokens(activeTokenFingerprints) .build(); } @@ -238,6 +242,19 @@ public class ApplicationHandlerTest { } @Test + public void testGetTokenFingerprints() throws IOException { + applicationRepository.deploy(testApp, prepareParams(applicationId)); + activeTokenFingerprints.putAll(Map.of("host", List.of(new Token("t1", List.of("fingers", "toes")), + new Token("t2", List.of())), + "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","tokens":[{"id":"t1","fingerprints":["fingers","toes"]},{"id":"t2","fingerprints":[]}]},{"host":"toast","tokens":[]}]}""", + 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); |