summaryrefslogtreecommitdiffstats
path: root/vespa-feed-client
diff options
context:
space:
mode:
authorjonmv <venstad@gmail.com>2022-12-07 11:07:31 +0100
committerjonmv <venstad@gmail.com>2022-12-07 11:07:31 +0100
commit2bd824a3524541d4c18b0f143ae9f6db9976695a (patch)
treeda45e8671180b3effdc0f716a0aad51dd43674fb /vespa-feed-client
parent9f1903169c46c1ab145c3e111e7809565a1873cf (diff)
Perform a dummy operation when setting up feed client, to catch configuration errors
Diffstat (limited to 'vespa-feed-client')
-rw-r--r--vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java84
-rw-r--r--vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java12
2 files changed, 67 insertions, 29 deletions
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java
index 80a107fbdc6..5c1c6ce0a91 100644
--- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java
+++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpFeedClient.java
@@ -15,16 +15,22 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
+import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.StringJoiner;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
+import static ai.vespa.feed.client.OperationParameters.empty;
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.Objects.requireNonNull;
@@ -51,6 +57,7 @@ class HttpFeedClient implements FeedClient {
this.requestHeaders = new HashMap<>(builder.requestHeaders);
this.requestStrategy = requestStrategy;
this.speedTest = builder.speedTest;
+ verifyConnection(builder);
}
@Override
@@ -113,31 +120,57 @@ class HttpFeedClient implements FeedClient {
return promise;
}
+ private void verifyConnection(FeedClientBuilderImpl builder) {
+ if (builder.dryrun) return;
+ try (Cluster cluster = new ApacheCluster(builder)) {
+ HttpRequest request = new HttpRequest("POST",
+ getPath(DocumentId.of("feeder", "handshake", "dummy")) + getQuery(empty(), true),
+ requestHeaders,
+ null,
+ Duration.ofSeconds(10));
+ CompletableFuture<HttpResponse> future = new CompletableFuture<>();
+ cluster.dispatch(request, future);
+ HttpResponse response = future.get(20, TimeUnit.SECONDS);
+ if (response.code() != 200) {
+ throw new FeedException("non-200 response: " + response);
+ }
+ }
+ catch (IOException e) {
+ throw new FeedException("failed handshake with server: " + e, e);
+ }
+ catch (ExecutionException e) {
+ throw new FeedException("failed handshake with server: " + e.getCause(), e.getCause());
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ throw new FeedException("interrupted during handshake with server", e);
+ }
+ catch (TimeoutException e) {
+ throw new FeedException("timed out during handshake with server", e);
+ }
+ }
+
private enum Outcome { success, conditionNotMet, vespaFailure, transportFailure };
static Result.Type toResultType(Outcome outcome) {
- switch (outcome) {
- case success: return Result.Type.success;
- case conditionNotMet: return Result.Type.conditionNotMet;
- default: throw new IllegalArgumentException("No corresponding result type for '" + outcome + "'");
- }
+ return switch (outcome) {
+ case success -> Result.Type.success;
+ case conditionNotMet -> Result.Type.conditionNotMet;
+ default -> throw new IllegalArgumentException("No corresponding result type for '" + outcome + "'");
+ };
}
static Result toResult(HttpRequest request, HttpResponse response, DocumentId documentId) {
- Outcome outcome;
- switch (response.code()) {
- case 200: outcome = Outcome.success; break;
- case 412: outcome = Outcome.conditionNotMet; break;
- case 502:
- case 504:
- case 507: outcome = Outcome.vespaFailure; break;
- default: outcome = Outcome.transportFailure;
- }
+ Outcome outcome = switch (response.code()) {
+ case 200 -> Outcome.success;
+ case 412 -> Outcome.conditionNotMet;
+ case 502, 504, 507 -> Outcome.vespaFailure;
+ default -> Outcome.transportFailure;
+ };
String message = null;
String trace = null;
- try {
- JsonParser parser = factory.createParser(response.body());
+ try (JsonParser parser = factory.createParser(response.body())) {
if (parser.nextToken() != JsonToken.START_OBJECT)
throw new ResultParseException(
documentId,
@@ -147,8 +180,8 @@ class HttpFeedClient implements FeedClient {
String name;
while ((name = parser.nextFieldName()) != null) {
switch (name) {
- case "message": message = parser.nextTextValue(); break;
- case "trace": {
+ case "message" -> message = parser.nextTextValue();
+ case "trace" -> {
if (parser.nextToken() != JsonToken.START_ARRAY)
throw new ResultParseException(documentId,
"Expected 'trace' to be an array, but got '" + parser.currentToken() + "' in: " +
@@ -156,13 +189,13 @@ class HttpFeedClient implements FeedClient {
int start = (int) parser.getTokenLocation().getByteOffset();
int depth = 1;
while (depth > 0) switch (parser.nextToken()) {
- case START_ARRAY: ++depth; break;
- case END_ARRAY: --depth; break;
+ case START_ARRAY -> ++depth;
+ case END_ARRAY -> --depth;
}
int end = (int) parser.getTokenLocation().getByteOffset() + 1;
trace = new String(response.body(), start, end - start, UTF_8);
- }; break;
- default: parser.nextToken();
+ }
+ default -> parser.nextToken();
}
}
@@ -211,12 +244,7 @@ class HttpFeedClient implements FeedClient {
}
static String encode(String raw) {
- try {
- return URLEncoder.encode(raw, UTF_8.name());
- }
- catch (UnsupportedEncodingException e) {
- throw new IllegalStateException(e);
- }
+ return URLEncoder.encode(raw, UTF_8);
}
static String getQuery(OperationParameters params, boolean speedTest) {
diff --git a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java
index 5353ab92fb6..e9c996ae560 100644
--- a/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java
+++ b/vespa-feed-client/src/test/java/ai/vespa/feed/client/impl/HttpFeedClientTest.java
@@ -4,6 +4,7 @@ package ai.vespa.feed.client.impl;
import ai.vespa.feed.client.DocumentId;
import ai.vespa.feed.client.FeedClient;
import ai.vespa.feed.client.FeedClientBuilder;
+import ai.vespa.feed.client.FeedException;
import ai.vespa.feed.client.HttpResponse;
import ai.vespa.feed.client.OperationParameters;
import ai.vespa.feed.client.OperationStats;
@@ -42,7 +43,8 @@ class HttpFeedClientTest {
@Override public void await() { throw new UnsupportedOperationException(); }
@Override public CompletableFuture<HttpResponse> enqueue(DocumentId documentId, HttpRequest request) { return dispatch.get().apply(documentId, request); }
}
- FeedClient client = new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))), new MockRequestStrategy());
+ FeedClient client = new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))).setDryrun(true),
+ new MockRequestStrategy());
// Update is a PUT, and 200 OK is a success.
dispatch.set((documentId, request) -> {
@@ -203,4 +205,12 @@ class HttpFeedClientTest {
assertEquals("Status 500 executing 'POST /document/v1/ns/type/docid/0': Alla ska i jorden.", expected.getCause().getMessage());
}
+ @Test
+ void testHandshake() {
+ assertEquals("failed handshake with server: java.net.UnknownHostException: dummy: nodename nor servname provided, or not known",
+ assertThrows(FeedException.class,
+ () -> new HttpFeedClient(new FeedClientBuilderImpl(Collections.singletonList(URI.create("https://dummy:123"))), null))
+ .getMessage());
+ }
+
}