From 1a4c0d52ab509782dacf128624665789c9b0f59b Mon Sep 17 00:00:00 2001 From: jonmv Date: Thu, 28 Apr 2022 10:12:06 +0200 Subject: Split out the general part of the configserver-client --- configserver-client/OWNERS | 1 - configserver-client/README.md | 2 - configserver-client/pom.xml | 75 ------ .../hosted/client/AbstractConfigServerClient.java | 259 ------------------- .../ai/vespa/hosted/client/ConfigServerClient.java | 278 --------------------- .../vespa/hosted/client/ForwardingInputStream.java | 55 ---- .../hosted/client/HttpConfigServerClient.java | 66 ----- .../hosted/client/MockConfigServerClient.java | 57 ----- .../hosted/client/HttpConfigServerClientTest.java | 98 -------- .../ai/vespa/hosted/client/WireMockExtension.java | 42 ---- 10 files changed, 933 deletions(-) delete mode 100644 configserver-client/OWNERS delete mode 100644 configserver-client/README.md delete mode 100644 configserver-client/pom.xml delete mode 100644 configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java delete mode 100644 configserver-client/src/main/java/ai/vespa/hosted/client/ConfigServerClient.java delete mode 100644 configserver-client/src/main/java/ai/vespa/hosted/client/ForwardingInputStream.java delete mode 100644 configserver-client/src/main/java/ai/vespa/hosted/client/HttpConfigServerClient.java delete mode 100644 configserver-client/src/main/java/ai/vespa/hosted/client/MockConfigServerClient.java delete mode 100644 configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java delete mode 100644 configserver-client/src/test/java/ai/vespa/hosted/client/WireMockExtension.java (limited to 'configserver-client') diff --git a/configserver-client/OWNERS b/configserver-client/OWNERS deleted file mode 100644 index d0a102ecbf4..00000000000 --- a/configserver-client/OWNERS +++ /dev/null @@ -1 +0,0 @@ -jonmv diff --git a/configserver-client/README.md b/configserver-client/README.md deleted file mode 100644 index 7f124bbb3ec..00000000000 --- a/configserver-client/README.md +++ /dev/null @@ -1,2 +0,0 @@ - -# HTTP client for configserver APIs, built on Apache http client 5 diff --git a/configserver-client/pom.xml b/configserver-client/pom.xml deleted file mode 100644 index 91ac43c72f6..00000000000 --- a/configserver-client/pom.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - 4.0.0 - - parent - com.yahoo.vespa - 7-SNAPSHOT - ../parent/pom.xml - - configserver-client - HTTP client for configserver APIs, built on Apache http client 5 - - - - com.yahoo.vespa - http-utils - ${project.version} - compile - - - - com.yahoo.vespa - vespa-athenz - ${project.version} - provided - - - com.yahoo.vespa - container-apache-http-client-bundle - ${project.version} - provided - - - com.yahoo.vespa - security-utils - ${project.version} - provided - - - com.yahoo.vespa - config-provisioning - ${project.version} - provided - - - com.yahoo.vespa - vespajlib - ${project.version} - provided - - - com.yahoo.vespa - yolean - ${project.version} - provided - - - - org.junit.jupiter - junit-jupiter - test - - - com.github.tomakehurst - wiremock-standalone - 2.27.2 - test - - - - diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java b/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java deleted file mode 100644 index 3422d96e1d3..00000000000 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/AbstractConfigServerClient.java +++ /dev/null @@ -1,259 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import ai.vespa.hosted.client.ConfigServerClient.RequestBuilder; -import ai.vespa.http.HttpURL; -import ai.vespa.http.HttpURL.Path; -import ai.vespa.http.HttpURL.Query; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.client5.http.protocol.HttpClientContext; -import org.apache.hc.core5.http.ClassicHttpRequest; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.Method; -import org.apache.hc.core5.http.ParseException; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.io.entity.HttpEntities; -import org.apache.hc.core5.http.io.support.ClassicRequestBuilder; - -import java.io.IOException; -import java.io.UncheckedIOException; -import java.net.URI; -import java.time.Duration; -import java.util.List; -import java.util.concurrent.TimeUnit; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.logging.Logger; - -import static java.util.Objects.requireNonNull; -import static java.util.logging.Level.FINE; -import static java.util.logging.Level.WARNING; - -/** - * @author jonmv - */ -public abstract class AbstractConfigServerClient implements ConfigServerClient { - - private static final Logger log = Logger.getLogger(AbstractConfigServerClient.class.getName()); - - /** Executes the request with the given context. The caller must close the response. */ - abstract ClassicHttpResponse execute(ClassicHttpRequest request, HttpClientContext context) throws IOException; - - /** Executes the given request with response/error handling and retries. */ - private T execute(RequestBuilder builder, - BiFunction handler, - ExceptionHandler catcher) { - HttpClientContext context = HttpClientContext.create(); - context.setRequestConfig(builder.config); - - Throwable thrown = null; - for (URI host : builder.hosts) { - ClassicHttpRequest request = ClassicRequestBuilder.create(builder.method.name()) - .setUri(HttpURL.from(host) - .appendPath(builder.path) - .appendQuery(builder.query) - .asURI()) - .build(); - request.setEntity(builder.entity); - try { - try { - return handler.apply(execute(request, context), request); - } - catch (IOException e) { - catcher.handle(e, request); - throw RetryException.wrap(e, request); - } - } - catch (RetryException e) { - if (thrown == null) - thrown = e.getCause(); - else - thrown.addSuppressed(e.getCause()); - - if (builder.entity != null && ! builder.entity.isRepeatable()) { - log.log(WARNING, "Cannot retry " + request + " as entity is not repeatable"); - break; - } - log.log(FINE, e.getCause(), () -> request + " failed; will retry"); - } - } - if (thrown != null) { - if (thrown instanceof IOException) - throw new UncheckedIOException((IOException) thrown); - else if (thrown instanceof RuntimeException) - throw (RuntimeException) thrown; - else - throw new IllegalStateException("Illegal retry cause: " + thrown.getClass(), thrown); - } - - throw new IllegalStateException("No hosts to perform the request against"); - } - - @Override - public ConfigServerClient.RequestBuilder send(HostStrategy hosts, Method method) { - return new RequestBuilder(hosts, method); - } - - /** Builder for a request against a given set of hosts. */ - class RequestBuilder implements ConfigServerClient.RequestBuilder { - - private final Method method; - private final HostStrategy hosts; - private HttpURL.Path path = Path.empty(); - private HttpURL.Query query = Query.empty(); - private HttpEntity entity; - private RequestConfig config = ConfigServerClient.defaultRequestConfig; - private ResponseVerifier verifier = ConfigServerClient.throwOnError; - private ExceptionHandler catcher = ConfigServerClient.retryAll; - - private RequestBuilder(HostStrategy hosts, Method method) { - if ( ! hosts.iterator().hasNext()) - throw new IllegalArgumentException("Host strategy cannot be empty"); - - this.hosts = hosts; - this.method = requireNonNull(method); - } - - @Override - public RequestBuilder at(Path subPath) { - path = path.append(subPath); - return this; - } - - @Override - public ConfigServerClient.RequestBuilder body(byte[] json) { - return body(HttpEntities.create(json, ContentType.APPLICATION_JSON)); - } - - @Override - public RequestBuilder body(HttpEntity entity) { - this.entity = requireNonNull(entity); - return this; - } - - @Override - public ConfigServerClient.RequestBuilder emptyParameters(List keys) { - for (String key : keys) - query = query.add(key); - - return this; - } - - @Override - public RequestBuilder parameters(List pairs) { - if (pairs.size() % 2 != 0) - throw new IllegalArgumentException("Must supply parameter key/values in pairs"); - - for (int i = 0; i < pairs.size(); ) { - String key = pairs.get(i++), value = pairs.get(i++); - if (value != null) - query = query.add(key, value); - } - - return this; - } - - @Override - public ConfigServerClient.RequestBuilder parameters(Query query) { - this.query = this.query.add(query.entries()); - return this; - } - - @Override - public RequestBuilder timeout(Duration timeout) { - return config(RequestConfig.copy(config) - .setResponseTimeout(timeout.toMillis(), TimeUnit.MILLISECONDS) - .build()); - } - - @Override - public RequestBuilder config(RequestConfig config) { - this.config = requireNonNull(config); - return this; - } - - @Override - public RequestBuilder catching(ExceptionHandler catcher) { - this.catcher = requireNonNull(catcher); - return this; - } - - @Override - public RequestBuilder throwing(ResponseVerifier verifier) { - this.verifier = requireNonNull(verifier); - return this; - } - - @Override - public String read() { - return handle((response, __) -> { - try (response) { - return response.getEntity() == null ? "" : EntityUtils.toString(response.getEntity()); - } - catch (ParseException e) { - throw new IllegalStateException(e); // This isn't actually thrown by apache >_< - } - }); - } - - @Override - public T read(Function mapper) { - return handle((response, __) -> { - try (response) { - return mapper.apply(response.getEntity() == null ? new byte[0] : EntityUtils.toByteArray(response.getEntity())); - } - }); - } - - @Override - public void discard() throws UncheckedIOException, ResponseException { - handle((response, __) -> { - try (response) { - return null; - } - }); - } - - @Override - public HttpInputStream stream() throws UncheckedIOException, ResponseException { - return handle((response, __) -> new HttpInputStream(response)); - } - - @Override - public T handle(ResponseHandler handler) { - return execute(this, - (response, request) -> { - try { - verifier.verify(response, request); // This throws on unacceptable responses. - return handler.handle(response, request); - } - catch (IOException | RuntimeException | Error e) { - try { - response.close(); - } - catch (IOException f) { - e.addSuppressed(f); - } - if (e instanceof IOException) { - catcher.handle((IOException) e, request); - throw RetryException.wrap((IOException) e, request); - } - else - sneakyThrow(e); // e is a runtime exception or an error, so this is fine. - throw new AssertionError("Should not happen"); - } - }, - catcher); - } - - } - - - @SuppressWarnings("unchecked") - private static void sneakyThrow(Throwable t) throws T { - throw (T) t; - } - -} \ No newline at end of file diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/ConfigServerClient.java b/configserver-client/src/main/java/ai/vespa/hosted/client/ConfigServerClient.java deleted file mode 100644 index 47e062766c6..00000000000 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/ConfigServerClient.java +++ /dev/null @@ -1,278 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import ai.vespa.http.HttpURL; -import ai.vespa.http.HttpURL.Path; -import ai.vespa.http.HttpURL.Query; -import org.apache.hc.client5.http.config.RequestConfig; -import org.apache.hc.core5.http.ClassicHttpRequest; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.HttpEntity; -import org.apache.hc.core5.http.HttpStatus; -import org.apache.hc.core5.http.Method; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.util.Timeout; - -import java.io.Closeable; -import java.io.IOException; -import java.io.InputStream; -import java.io.UncheckedIOException; -import java.net.URI; -import java.time.Duration; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.function.Function; -import java.util.stream.IntStream; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Objects.requireNonNull; -import static java.util.stream.Collectors.toUnmodifiableList; - -/** - * @author jonmv - */ -public interface ConfigServerClient extends Closeable { - - RequestConfig defaultRequestConfig = RequestConfig.custom() - .setConnectionRequestTimeout(Timeout.ofSeconds(5)) - .setConnectTimeout(Timeout.ofSeconds(5)) - .setRedirectsEnabled(false) - .build(); - - /** Does nothing, letting the client wrap with a {@link RetryException} and re-throw. */ - ExceptionHandler retryAll = (exception, request) -> { }; - - /** Throws a a {@link RetryException} if {@code statusCode == 503}, or a {@link ResponseException} unless {@code 200 <= statusCode < 300}. */ - ResponseVerifier throwOnError = new DefaultResponseVerifier() { }; - - /** Reads the response body, throwing an {@link UncheckedIOException} if this fails, or {@code null} if there is none. */ - static byte[] getBytes(ClassicHttpResponse response) { - try { - return response.getEntity() == null ? null : EntityUtils.toByteArray(response.getEntity()); - } - catch (IOException e) { - throw new UncheckedIOException(e); - } - } - - /** Creates a builder for sending the given method, using the specified host strategy. */ - RequestBuilder send(HostStrategy hosts, Method method); - - /** Builder for a request against a given set of hosts, using this config server client. */ - interface RequestBuilder { - - /** Appends to the request path, with no trailing slash. */ - default RequestBuilder at(String... pathSegments) { return at(List.of(pathSegments)); } - - /** Appends to the request path, with no trailing slash. */ - default RequestBuilder at(List pathSegments) { return at(Path.empty().append(pathSegments).withoutTrailingSlash()); } - - /** Appends to the request path. */ - RequestBuilder at(HttpURL.Path path); - - /** Sets the request body as UTF-8 application/json. */ - RequestBuilder body(byte[] json); - - /** Sets the request body. */ - RequestBuilder body(HttpEntity entity); - - /** Sets query parameters without a value, like {@code ?debug&recursive}. */ - default RequestBuilder emptyParameters(String... keys) { - return emptyParameters(Arrays.asList(keys)); - } - - /** Sets query parameters without a value, like {@code ?debug&recursive}. */ - RequestBuilder emptyParameters(List keys); - - /** Sets the parameter key/values for the request. Number of arguments must be even. Null values are omitted. */ - default RequestBuilder parameters(String... pairs) { - return parameters(Arrays.asList(pairs)); - } - - /** Sets the parameter key/values for the request. Number of arguments must be even. Pairs with {@code null} values are omitted. */ - RequestBuilder parameters(List pairs); - - /** Appends all parameters from the given query. */ - RequestBuilder parameters(Query query); - - /** Overrides the default socket read timeout of the request. {@code Duration.ZERO} gives infinite timeout. */ - RequestBuilder timeout(Duration timeout); - - /** Overrides the default request config of the request. */ - RequestBuilder config(RequestConfig config); - - /** - * Sets the catch clause for {@link IOException}s during execution of this. - * The default is to wrap the IOException in a {@link RetryException} and rethrow this; - * this makes the client retry the request, as long as there are remaining entries in the {@link HostStrategy}. - * If the catcher returns normally, the exception is wrapped and retried, as per the default. - */ - RequestBuilder catching(ExceptionHandler catcher); - - /** - * Sets the (error) response handler for this request. The default is {@link #throwOnError}. - * When the handler returns normally, the response is treated as a success, and passed on to a response mapper. - */ - RequestBuilder throwing(ResponseVerifier handler); - - /** Reads the response as a {@link String}, or throws if unsuccessful. */ - String read(); - - /** Reads and maps the response, or throws if unsuccessful. */ - T read(Function mapper); - - /** Discards the response, but throws if unsuccessful. */ - void discard(); - - /** Returns the raw response input stream, or throws if unsuccessful. The caller must close the returned stream. */ - HttpInputStream stream(); - - /** Uses the response and request, if successful, to generate a mapped response. */ - T handle(ResponseHandler handler); - - } - - - class HttpInputStream extends ForwardingInputStream { - - private final ClassicHttpResponse response; - - protected HttpInputStream(ClassicHttpResponse response) throws IOException { - super(response.getEntity() != null ? response.getEntity().getContent() - : InputStream.nullInputStream()); - this.response = response; - } - - public int statusCode() { return response.getCode(); } - - public String contentType() { return response.getEntity().getContentType(); } - - @Override - public void close() throws IOException { - super.close(); - response.close(); - } - - } - - - /** Reads a successful response and request to compute a result. */ - @FunctionalInterface - interface ResponseHandler { - - /** Called with successful responses, as per {@link ResponseVerifier}. The caller must close the response. */ - T handle(ClassicHttpResponse response, ClassicHttpRequest request) throws IOException; - - } - - - /** Verifies a response, throwing on error responses, possibly indicating retries. */ - @FunctionalInterface - interface ResponseVerifier { - - /** Whether this status code means the response is an error response. */ - default boolean isError(int statusCode) { - return statusCode < HttpStatus.SC_OK || HttpStatus.SC_REDIRECTION <= statusCode; - } - - /** Whether this status code means we should retry. Has no effect if this is not also an error. */ - default boolean shouldRetry(int statusCode) { - return statusCode == HttpStatus.SC_SERVICE_UNAVAILABLE; - } - - /** Verifies the given response, consuming it and throwing if it is an error; or leaving it otherwise. */ - default void verify(ClassicHttpResponse response, ClassicHttpRequest request) throws IOException { - if (isError(response.getCode())) { - try (response) { - byte[] body = response.getEntity() == null ? new byte[0] : EntityUtils.toByteArray(response.getEntity()); - RuntimeException exception = toException(response.getCode(), body, request); - throw shouldRetry(response.getCode()) ? new RetryException(exception) : exception; - } - } - } - - /** Throws the appropriate exception, for the given status code and body. */ - RuntimeException toException(int statusCode, byte[] body, ClassicHttpRequest request); - - } - - - interface DefaultResponseVerifier extends ResponseVerifier { - - @Override - default RuntimeException toException(int statusCode, byte[] body, ClassicHttpRequest request) { - return new ResponseException(request + " failed with status " + statusCode + " and body '" + new String(body, UTF_8) + "'"); - } - - } - - - @FunctionalInterface - interface ExceptionHandler { - - /** - * Called with any IO exception that might occur when attempting to send the request. - * To retry, wrap the exception with a {@link RetryException} and re-throw, or exit normally. - * Any other thrown exception will propagate out to the caller. - */ - void handle(IOException exception, ClassicHttpRequest request); - - } - - - /** What host(s) to try for a request, in what order. A host may be specified multiple times, for retries. */ - @FunctionalInterface - interface HostStrategy extends Iterable { - - /** Attempts each request once against each listed host. */ - static HostStrategy ordered(List hosts) { - return List.copyOf(hosts)::iterator; - } - - /** Attempts each request once against each listed host, in random order. */ - static HostStrategy shuffling(List hosts) { - return () -> { - List copy = new ArrayList<>(hosts); - Collections.shuffle(copy); - return copy.iterator(); - }; - } - - /** Attempts each request against the host the specified number of times. */ - static HostStrategy repeating(URI host, int count) { - return ordered(IntStream.range(0, count).mapToObj(__ -> host).collect(toUnmodifiableList())); - } - - } - - - /** Exception wrapper that signals retries should be attempted. */ - final class RetryException extends RuntimeException { - - public RetryException(IOException cause) { - super(requireNonNull(cause)); - } - - public RetryException(RuntimeException cause) { - super(requireNonNull(cause)); - } - - static RetryException wrap(IOException exception, ClassicHttpRequest request) { - return new RetryException(new UncheckedIOException(request + " failed (" + exception.getClass().getSimpleName() + ")", exception)); - } - - } - - - /** An exception due to server error, a bad request, or similar, which resulted in a non-OK HTTP response. */ - class ResponseException extends RuntimeException { - - public ResponseException(String message) { - super(message); - } - - } - -} \ No newline at end of file diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/ForwardingInputStream.java b/configserver-client/src/main/java/ai/vespa/hosted/client/ForwardingInputStream.java deleted file mode 100644 index 402c2689ca7..00000000000 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/ForwardingInputStream.java +++ /dev/null @@ -1,55 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import java.io.IOException; -import java.io.InputStream; - -import static java.util.Objects.requireNonNull; - -/** - * @author jonmv - */ -public class ForwardingInputStream extends InputStream { - - private final InputStream delegate; - - public ForwardingInputStream(InputStream delegate) { - this.delegate = requireNonNull(delegate); - } - - @Override - public int read() throws IOException { - return delegate.read(); - } - - @Override - public int read(byte[] b, int off, int len) throws IOException { - return delegate.read(b, off, len); - } - - @Override - public int available() throws IOException { - return delegate.available(); - } - - @Override - public void close() throws IOException { - delegate.close(); - } - - @Override - public synchronized void mark(int readlimit) { - delegate.mark(readlimit); - } - - @Override - public synchronized void reset() throws IOException { - delegate.reset(); - } - - @Override - public boolean markSupported() { - return delegate.markSupported(); - } - -} diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/HttpConfigServerClient.java b/configserver-client/src/main/java/ai/vespa/hosted/client/HttpConfigServerClient.java deleted file mode 100644 index 6365e2b6b7d..00000000000 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/HttpConfigServerClient.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import ai.vespa.util.http.hc5.VespaHttpClientBuilder; -import com.yahoo.vespa.athenz.api.AthenzIdentity; -import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager; -import org.apache.hc.client5.http.protocol.HttpClientContext; -import org.apache.hc.core5.http.ClassicHttpRequest; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.io.SocketConfig; -import org.apache.hc.core5.util.TimeValue; -import org.apache.hc.core5.util.Timeout; - -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; -import java.io.IOException; -import java.util.Collection; -import java.util.Set; - -/** - * @author jonmv - */ -public class HttpConfigServerClient extends AbstractConfigServerClient { - - private final CloseableHttpClient client; - - public HttpConfigServerClient(Collection serverIdentities, String userAgent) { - if (serverIdentities.isEmpty()) - throw new IllegalArgumentException("At least one trusted server identity must be provided"); - - this.client = createClient(serverIdentities, userAgent); - } - - @Override - public void close() throws IOException { - client.close(); - } - - @Override - protected ClassicHttpResponse execute(ClassicHttpRequest request, HttpClientContext context) throws IOException { - return client.execute(request, context); - } - - private static CloseableHttpClient createClient(Collection serverIdentities, String userAgent) { - return VespaHttpClientBuilder.create(socketFactories -> { - var manager = new PoolingHttpClientConnectionManager(socketFactories); - manager.setMaxTotal(1024); - manager.setDefaultMaxPerRoute(128); - manager.setDefaultSocketConfig(SocketConfig.custom().setSoTimeout(Timeout.ofSeconds(5)).build()); - manager.setValidateAfterInactivity(TimeValue.ofSeconds(10)); - return manager; - }, - new AthenzIdentityVerifier(Set.copyOf(serverIdentities)) { - @Override public boolean verify(String hostname, SSLSession session) { - return super.verify(hostname, session) || "localhost".equals(hostname); - } - }, - false) - .disableAutomaticRetries() - .setUserAgent(userAgent) - .build(); - } - -} diff --git a/configserver-client/src/main/java/ai/vespa/hosted/client/MockConfigServerClient.java b/configserver-client/src/main/java/ai/vespa/hosted/client/MockConfigServerClient.java deleted file mode 100644 index 16aad80670b..00000000000 --- a/configserver-client/src/main/java/ai/vespa/hosted/client/MockConfigServerClient.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import org.apache.hc.client5.http.protocol.HttpClientContext; -import org.apache.hc.core5.http.ClassicHttpRequest; -import org.apache.hc.core5.http.ClassicHttpResponse; -import org.apache.hc.core5.http.ContentType; -import org.apache.hc.core5.http.io.entity.HttpEntities; -import org.apache.hc.core5.http.message.BasicClassicHttpResponse; - -import java.io.IOException; -import java.util.ArrayDeque; -import java.util.Deque; -import java.util.function.Function; - -/** - * @author jonmv - */ -public class MockConfigServerClient extends AbstractConfigServerClient { - - private final Deque expectations = new ArrayDeque<>(); - - @Override - protected ClassicHttpResponse execute(ClassicHttpRequest request, HttpClientContext context) throws IOException { - Expectation expectation = expectations.poll(); - if (expectation == null) - throw new AssertionError("No further requests expected, but got " + request); - - return expectation.handle(request); - } - - @Override - public void close() { - if ( ! expectations.isEmpty()) - throw new AssertionError(expectations.size() + " more requests were expected"); - } - - public void expect(Expectation expectation) { - expectations.add(expectation); - } - - public void expect(int status, Function mapper) { - expect(request -> { - BasicClassicHttpResponse response = new BasicClassicHttpResponse(status); - response.setEntity(HttpEntities.create(mapper.apply(request), ContentType.APPLICATION_JSON)); - return response; - }); - } - - @FunctionalInterface - public interface Expectation { - - ClassicHttpResponse handle(ClassicHttpRequest request) throws IOException; - - } - -} diff --git a/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java b/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java deleted file mode 100644 index e969201605e..00000000000 --- a/configserver-client/src/test/java/ai/vespa/hosted/client/HttpConfigServerClientTest.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import ai.vespa.hosted.client.ConfigServerClient.ResponseException; -import ai.vespa.hosted.client.ConfigServerClient.HostStrategy; -import com.github.tomakehurst.wiremock.http.Fault; -import com.yahoo.vespa.athenz.api.AthenzService; -import org.apache.hc.core5.http.Method; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.RegisterExtension; - -import java.io.UncheckedIOException; -import java.net.URI; -import java.util.List; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.anyRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.okJson; -import static com.github.tomakehurst.wiremock.client.WireMock.post; -import static com.github.tomakehurst.wiremock.client.WireMock.postRequestedFor; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * @author jonmv - */ -class HttpConfigServerClientTest { - - @RegisterExtension - final WireMockExtension server = new WireMockExtension(); - - final ConfigServerClient client = new HttpConfigServerClient(List.of(new AthenzService("mydomain", "yourservice")), "user"); - - @Test - void testRetries() { - // Two servers in list--two attempts on IOException. - server.stubFor(get("/root?query=foo")) - .setResponse(okJson("{}").withFault(Fault.RANDOM_DATA_THEN_CLOSE) - .build()); - assertThrows(UncheckedIOException.class, - () -> client.send(HostStrategy.ordered(List.of(URI.create("http://localhost:" + server.port()), - URI.create("http://localhost:" + server.port() + "/"))), - Method.GET) - .at("root") - .parameters("query", "foo") - .discard()); - server.verify(2, getRequestedFor(urlEqualTo("/root?query=foo"))); - server.verify(2, anyRequestedFor(anyUrl())); - server.resetRequests(); - - // Two attempts on a different IOException. - server.stubFor(post("/prefix/root")) - .setResponse(okJson("{}").withFault(Fault.EMPTY_RESPONSE) - .build()); - assertThrows(UncheckedIOException.class, - () -> client.send(HostStrategy.shuffling(List.of(URI.create("http://localhost:" + server.port() + "/prefix"), - URI.create("http://localhost:" + server.port() + "/prefix/"))), - Method.POST) - .body("hello".getBytes(UTF_8)) - .at("root") - .stream()); - server.verify(2, postRequestedFor(urlEqualTo("/prefix/root")).withRequestBody(equalTo("hello"))); - server.verify(2, anyRequestedFor(anyUrl())); - server.resetRequests(); - - // Successful attempt returns. - server.stubFor(get("/root/boot/toot")) - .setResponse(okJson("{}").build()); - assertEquals("{}", - client.send(HostStrategy.repeating(URI.create("http://localhost:" + server.port()), 10), - Method.GET) - .at("root", "boot") - .at("toot") - .read(String::new)); - server.verify(1, getRequestedFor(urlEqualTo("/root/boot/toot"))); - server.verify(1, anyRequestedFor(anyUrl())); - server.resetRequests(); - - // ResponseException is not retried. - server.stubFor(get("/")) - .setResponse(aResponse().withStatus(409).withBody("hi").build()); - ResponseException thrown = assertThrows(ResponseException.class, - () -> client.send(HostStrategy.repeating(URI.create("http://localhost:" + server.port()), 10), - Method.GET) - .read(String::new)); - assertEquals("GET http://localhost:" + server.port() + "/ failed with status 409 and body 'hi'", thrown.getMessage()); - server.verify(1, getRequestedFor(urlEqualTo("/"))); - server.verify(1, anyRequestedFor(anyUrl())); - server.resetRequests(); - } - -} diff --git a/configserver-client/src/test/java/ai/vespa/hosted/client/WireMockExtension.java b/configserver-client/src/test/java/ai/vespa/hosted/client/WireMockExtension.java deleted file mode 100644 index d95650727c0..00000000000 --- a/configserver-client/src/test/java/ai/vespa/hosted/client/WireMockExtension.java +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -package ai.vespa.hosted.client; - -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.core.Options; -import com.github.tomakehurst.wiremock.core.WireMockConfiguration; -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - -/** - * Allows wiremock to be used as a JUnit 5 extension, like - *
- *
- *   @RegisterExtension
- *   WireMockExtension mockServer1 = new WireMockExtension();
- * 
- */ -public class WireMockExtension extends WireMockServer implements BeforeEachCallback, AfterEachCallback { - - public WireMockExtension() { - this(WireMockConfiguration.options() - .dynamicPort() - .dynamicHttpsPort()); - } - - public WireMockExtension(Options options) { - super(options); - } - - @Override - public void beforeEach(ExtensionContext extensionContext) { - start(); - } - - @Override - public void afterEach(ExtensionContext extensionContext) { - stop(); - resetAll(); - } - -} -- cgit v1.2.3