Skip to content

Commit 478b0fe

Browse files
committed
feat: check connection to cluster when creating a new client
1) Ping /live endpoint once we have the RestTransport but before the client is fully initialized. If the instance is not reachable, close the existing resouces and throw a WeaviateConnectException. The user can catch that earlier in their application, e.g. at startup, not when they send the first request. 2) Renamed connection helper methods: - local -> connectToLocal - wcd -> connectToWeaviateCloud - custom -> connectToCustom
1 parent 9029429 commit 478b0fe

10 files changed

Lines changed: 165 additions & 37 deletions

File tree

src/it/java/io/weaviate/containers/Weaviate.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ public WeaviateClient getNewClient(Function<Config.Custom, ObjectBuilder<Config>
7878
.grpcHost(host)
7979
.httpPort(getMappedPort(8080))
8080
.grpcPort(getMappedPort(50051)));
81-
return WeaviateClient.custom(customFn);
81+
return WeaviateClient.connectToCustom(customFn);
8282
}
8383

8484
public static Weaviate createDefault() {

src/main/java/io/weaviate/client6/v1/api/WeaviateApiException.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
package io.weaviate.client6.v1.api;
22

33
/**
4-
* Exception class thrown by client API message when the request's reached the
5-
* server, but the operation did not complete successfully either either due to
6-
* a bad request or a server error.
4+
* Exception class thrown by client when the request had reached the
5+
* server, but the operation did not complete successfully either
6+
* due to a bad request or a server error.
77
*/
88
public class WeaviateApiException extends WeaviateException {
99
private final String errorMessage;

src/main/java/io/weaviate/client6/v1/api/WeaviateClient.java

Lines changed: 35 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,6 @@ public class WeaviateClient implements AutoCloseable {
3232
public final WeaviateAliasClient alias;
3333

3434
public WeaviateClient(Config config) {
35-
this.config = config;
36-
3735
RestTransportOptions restOpt;
3836
GrpcChannelOptions grpcOpt;
3937
if (config.authentication() == null) {
@@ -52,11 +50,33 @@ public WeaviateClient(Config config) {
5250
grpcOpt = config.grpcTransportOptions(tokenProvider);
5351
}
5452

55-
this.restTransport = new DefaultRestTransport(restOpt);
56-
this.grpcTransport = new DefaultGrpcTransport(grpcOpt);
53+
// Initialize REST transport to a temporary variable to dispose of
54+
// the associated resources in case we have to throw an exception.
55+
// Assign to this.restTransport only once we're in the clear to
56+
// avoid publishing the object before it's fully initialized.
57+
var _restTransport = new DefaultRestTransport(restOpt);
58+
boolean isLive = false;
59+
try {
60+
isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT);
61+
} catch (IOException e) {
62+
throw new WeaviateConnectException(e);
63+
}
64+
65+
if (!isLive) {
66+
var ex = new WeaviateConnectException("Weaviate not available at " + restOpt.baseUrl());
67+
try {
68+
_restTransport.close();
69+
} catch (Exception e) {
70+
ex.addSuppressed(e);
71+
}
72+
throw ex;
73+
}
5774

75+
this.restTransport = _restTransport;
76+
this.grpcTransport = new DefaultGrpcTransport(grpcOpt);
5877
this.alias = new WeaviateAliasClient(restTransport);
5978
this.collections = new WeaviateCollectionsClient(restTransport, grpcTransport);
79+
this.config = config;
6080
}
6181

6282
/**
@@ -77,7 +97,7 @@ public WeaviateClient(Config config) {
7797
* Example:
7898
*
7999
* <pre>{@code
80-
* var client = WeaviateClient.local();
100+
* var client = WeaviateClient.connectToLocal();
81101
*
82102
* // Need to make the next request non-blocking
83103
* try (final var async = client.async()) {
@@ -92,9 +112,9 @@ public WeaviateClient(Config config) {
92112
* If you only intend to use {@link WeaviateClientAsync}, prefer creating it
93113
* directly via one of its static factories:
94114
* <ul>
95-
* <li>{@link WeaviateClientAsync#local}
96-
* <li>{@link WeaviateClientAsync#wcd}
97-
* <li>{@link WeaviateClientAsync#custom}
115+
* <li>{@link WeaviateClientAsync#connectToLocal}
116+
* <li>{@link WeaviateClientAsync#connectToWeaviateCloud}
117+
* <li>{@link WeaviateClientAsync#connectToCustom}
98118
* </ul>
99119
*
100120
* Otherwise the client wastes time initializing resources it will never use.
@@ -104,29 +124,29 @@ public WeaviateClientAsync async() {
104124
}
105125

106126
/** Connect to a local Weaviate instance. */
107-
public static WeaviateClient local() {
108-
return local(ObjectBuilder.identity());
127+
public static WeaviateClient connectToLocal() {
128+
return connectToLocal(ObjectBuilder.identity());
109129
}
110130

111131
/** Connect to a local Weaviate instance. */
112-
public static WeaviateClient local(Function<Config.Local, ObjectBuilder<Config>> fn) {
132+
public static WeaviateClient connectToLocal(Function<Config.Local, ObjectBuilder<Config>> fn) {
113133
return new WeaviateClient(fn.apply(new Config.Local()).build());
114134
}
115135

116136
/** Connect to a Weaviate Cloud instance. */
117-
public static WeaviateClient wcd(String httpHost, String apiKey) {
118-
return wcd(httpHost, apiKey, ObjectBuilder.identity());
137+
public static WeaviateClient connectToWeaviateCloud(String httpHost, String apiKey) {
138+
return connectToWeaviateCloud(httpHost, apiKey, ObjectBuilder.identity());
119139
}
120140

121141
/** Connect to a Weaviate Cloud instance. */
122-
public static WeaviateClient wcd(String httpHost, String apiKey,
142+
public static WeaviateClient connectToWeaviateCloud(String httpHost, String apiKey,
123143
Function<Config.WeaviateCloud, ObjectBuilder<Config>> fn) {
124144
var config = new Config.WeaviateCloud(httpHost, Authentication.apiKey(apiKey));
125145
return new WeaviateClient(fn.apply(config).build());
126146
}
127147

128148
/** Connect to a Weaviate instance with custom configuration. */
129-
public static WeaviateClient custom(Function<Config.Custom, ObjectBuilder<Config>> fn) {
149+
public static WeaviateClient connectToCustom(Function<Config.Custom, ObjectBuilder<Config>> fn) {
130150
return new WeaviateClient(fn.apply(new Config.Custom()).build());
131151
}
132152

src/main/java/io/weaviate/client6/v1/api/WeaviateClientAsync.java

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -45,17 +45,38 @@ public WeaviateClientAsync(Config config) {
4545
try (final var noAuthRest = new DefaultRestTransport(config.restTransportOptions())) {
4646
tokenProvider = config.authentication().getTokenProvider(noAuthRest);
4747
} catch (Exception e) {
48-
// Generally IOExceptions are caught in TokenProvider internals.
48+
// Generally exceptions are caught in TokenProvider internals.
4949
// This one may be thrown when noAuthRest transport is auto-closed.
5050
throw new WeaviateOAuthException(e);
5151
}
5252
restOpt = config.restTransportOptions(tokenProvider);
5353
grpcOpt = config.grpcTransportOptions(tokenProvider);
5454
}
5555

56-
this.restTransport = new DefaultRestTransport(restOpt);
57-
this.grpcTransport = new DefaultGrpcTransport(grpcOpt);
56+
// Initialize REST transport to a temporary variable to dispose of
57+
// the associated resources in case we have to throw an exception.
58+
// Assign to this.restTransport only once we're in the clear to
59+
// avoid publishing the object before it's fully initialized.
60+
var _restTransport = new DefaultRestTransport(restOpt);
61+
boolean isLive = false;
62+
try {
63+
isLive = _restTransport.performRequest(null, IsLiveRequest._ENDPOINT);
64+
} catch (IOException e) {
65+
throw new WeaviateConnectException(e);
66+
}
5867

68+
if (!isLive) {
69+
var ex = new WeaviateConnectException("Weaviate not available at " + restOpt.baseUrl());
70+
try {
71+
_restTransport.close();
72+
} catch (Exception e) {
73+
ex.addSuppressed(e);
74+
}
75+
throw ex;
76+
}
77+
78+
this.restTransport = _restTransport;
79+
this.grpcTransport = new DefaultGrpcTransport(grpcOpt);
5980
this.alias = new WeaviateAliasClientAsync(restTransport);
6081
this.collections = new WeaviateCollectionsClientAsync(restTransport, grpcTransport);
6182
}
@@ -67,8 +88,8 @@ public WeaviateClientAsync(Config config) {
6788
* This call is blocking if {@link Authentication} configured,
6889
* as the client will need to do the initial token exchange.
6990
*/
70-
public static WeaviateClientAsync local() {
71-
return local(ObjectBuilder.identity());
91+
public static WeaviateClientAsync connectToLocal() {
92+
return connectToLocal(ObjectBuilder.identity());
7293
}
7394

7495
/**
@@ -78,7 +99,7 @@ public static WeaviateClientAsync local() {
7899
* This call is blocking if {@link Authentication} configured,
79100
* as the client will need to do the initial token exchange.
80101
*/
81-
public static WeaviateClientAsync local(Function<Config.Local, ObjectBuilder<Config>> fn) {
102+
public static WeaviateClientAsync connectToLocal(Function<Config.Local, ObjectBuilder<Config>> fn) {
82103
return new WeaviateClientAsync(fn.apply(new Config.Local()).build());
83104
}
84105

@@ -89,8 +110,8 @@ public static WeaviateClientAsync local(Function<Config.Local, ObjectBuilder<Con
89110
* This call is blocking if {@link Authentication} configured,
90111
* as the client will need to do the initial token exchange.
91112
*/
92-
public static WeaviateClientAsync wcd(String httpHost, String apiKey) {
93-
return wcd(httpHost, apiKey, ObjectBuilder.identity());
113+
public static WeaviateClientAsync connectToWeaviateCloud(String httpHost, String apiKey) {
114+
return connectToWeaviateCloud(httpHost, apiKey, ObjectBuilder.identity());
94115
}
95116

96117
/**
@@ -100,7 +121,7 @@ public static WeaviateClientAsync wcd(String httpHost, String apiKey) {
100121
* This call is blocking if {@link Authentication} configured,
101122
* as the client will need to do the initial token exchange.
102123
*/
103-
public static WeaviateClientAsync wcd(String httpHost, String apiKey,
124+
public static WeaviateClientAsync connectToWeaviateCloud(String httpHost, String apiKey,
104125
Function<Config.WeaviateCloud, ObjectBuilder<Config>> fn) {
105126
var config = new Config.WeaviateCloud(httpHost, Authentication.apiKey(apiKey));
106127
return new WeaviateClientAsync(fn.apply(config).build());
@@ -113,7 +134,7 @@ public static WeaviateClientAsync wcd(String httpHost, String apiKey,
113134
* This call is blocking if {@link Authentication} configured,
114135
* as the client will need to do the initial token exchange.
115136
*/
116-
public static WeaviateClientAsync custom(Function<Config.Custom, ObjectBuilder<Config>> fn) {
137+
public static WeaviateClientAsync connectToCustom(Function<Config.Custom, ObjectBuilder<Config>> fn) {
117138
return new WeaviateClientAsync(Config.of(fn));
118139
}
119140

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
/** Exception thrown if the Weaviate instance appears to be offline. */
4+
public class WeaviateConnectException extends WeaviateException {
5+
public WeaviateConnectException(String message) {
6+
super(message);
7+
}
8+
9+
public WeaviateConnectException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
13+
public WeaviateConnectException(Throwable cause) {
14+
super(cause);
15+
}
16+
}

src/main/java/io/weaviate/client6/v1/api/WeaviateOAuthException.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
package io.weaviate.client6.v1.api;
22

33
/**
4-
* Exception class thrown by client API message when the request's reached the
5-
* server, but the operation did not complete successfully either either due to
6-
* a bad request or a server error.
4+
* Exception throws by the authentication layer if it encountered another
5+
* exception at any point of obtaining the new token or rotating one.
76
*/
87
public class WeaviateOAuthException extends WeaviateException {
98
public WeaviateOAuthException(String message) {
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
/** Exception thrown by the internal transport layer. Usually not retryable. */
4+
public class WeaviateTransportException extends WeaviateException {
5+
public WeaviateTransportException(String message) {
6+
super(message);
7+
}
8+
9+
public WeaviateTransportException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
13+
public WeaviateTransportException(Throwable cause) {
14+
super(cause);
15+
}
16+
}

src/main/java/io/weaviate/client6/v1/internal/rest/DefaultRestTransport.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import org.apache.hc.core5.io.CloseMode;
2929

3030
import io.weaviate.client6.v1.api.WeaviateApiException;
31+
import io.weaviate.client6.v1.api.WeaviateTransportException;
3132

3233
public class DefaultRestTransport implements RestTransport {
3334
private final CloseableHttpClient httpClient;
@@ -53,8 +54,7 @@ public DefaultRestTransport(RestTransportOptions transportOptions) {
5354
sslCtx.init(null, transportOptions.trustManagerFactory().getTrustManagers(), null);
5455
tlsStrategy = new DefaultClientTlsStrategy(sslCtx);
5556
} catch (NoSuchAlgorithmException | KeyManagementException e) {
56-
// todo: throw WeaviateConnectionException
57-
throw new RuntimeException("connect to Weaviate", e);
57+
throw new WeaviateTransportException("init custom SSL context", e);
5858
}
5959

6060
PoolingHttpClientConnectionManager syncManager = PoolingHttpClientConnectionManagerBuilder.create()
@@ -96,7 +96,6 @@ private <RequestT, ResponseT> ClassicHttpRequest prepareClassicRequest(RequestT
9696
var method = endpoint.method(request);
9797
var uri = uri(endpoint, request);
9898

99-
// TODO: apply options;
10099
var req = ClassicRequestBuilder.create(method).setUri(uri);
101100
var body = endpoint.body(request);
102101
if (body != null) {
@@ -180,8 +179,7 @@ private <ResponseT> ResponseT _handleResponse(Endpoint<?, ResponseT> endpoint, S
180179
return (ResponseT) ((Boolean) bool.getResult(statusCode));
181180
}
182181

183-
// TODO: make it a WeaviateTransportException
184-
throw new RuntimeException("Unhandled endpoint type " + endpoint.getClass().getSimpleName());
182+
throw new WeaviateTransportException("Unhandled endpoint type " + endpoint.getClass().getSimpleName());
185183
}
186184

187185
@Override
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
import org.junit.Test;
4+
5+
public class WeaviateClientAsyncTest {
6+
7+
@SuppressWarnings("resource")
8+
@Test(expected = WeaviateConnectException.class)
9+
public void testFailedConnection() {
10+
var config = new Config.Local();
11+
config.host("localhost").port(1234);
12+
new WeaviateClientAsync(config.build());
13+
}
14+
15+
@Test(expected = WeaviateConnectException.class)
16+
public void testFailedConnection_Local() {
17+
WeaviateClientAsync.connectToLocal();
18+
}
19+
20+
@Test(expected = WeaviateConnectException.class)
21+
public void testFailedConnection_WeaviateCloud() {
22+
WeaviateClientAsync.connectToWeaviateCloud("no-cluster.io", "no-key");
23+
}
24+
25+
@Test(expected = WeaviateConnectException.class)
26+
public void testFailedConnection_Custom() {
27+
WeaviateClient.connectToCustom(conn -> conn.httpHost("localhost").httpPort(1234));
28+
}
29+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package io.weaviate.client6.v1.api;
2+
3+
import org.junit.Test;
4+
5+
public class WeaviateClientTest {
6+
7+
@SuppressWarnings("resource")
8+
@Test(expected = WeaviateConnectException.class)
9+
public void testFailedConnection() {
10+
var config = new Config.Local();
11+
config.host("localhost").port(1234);
12+
new WeaviateClient(config.build());
13+
}
14+
15+
@Test(expected = WeaviateConnectException.class)
16+
public void testFailedConnection_Local() {
17+
WeaviateClient.connectToLocal();
18+
}
19+
20+
@Test(expected = WeaviateConnectException.class)
21+
public void testFailedConnection_WeaviateCloud() {
22+
WeaviateClient.connectToWeaviateCloud("no-cluster.io", "no-key");
23+
}
24+
25+
@Test(expected = WeaviateConnectException.class)
26+
public void testFailedConnection_Custom() {
27+
WeaviateClient.connectToCustom(conn -> conn.httpHost("localhost").httpPort(1234));
28+
}
29+
}

0 commit comments

Comments
 (0)