Skip to content

Commit 30f1bf6

Browse files
authored
Use Helidon gRPC client in Config Etcd module (#9674)
* Switches from Netty to our gRPC client. * Updates problematic IT tests and constrains its scope to run only with V3 servers. V2 support is deprecated and marked for removal. --------- Signed-off-by: Santiago Pericas-Geertsen <[email protected]>
1 parent 643a742 commit 30f1bf6

File tree

6 files changed

+58
-106
lines changed

6 files changed

+58
-106
lines changed

config/etcd/pom.xml

Lines changed: 4 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,10 @@
4949
<groupId>io.helidon.common</groupId>
5050
<artifactId>helidon-common-media-type</artifactId>
5151
</dependency>
52-
52+
<dependency>
53+
<groupId>io.helidon.webclient</groupId>
54+
<artifactId>helidon-webclient-grpc</artifactId>
55+
</dependency>
5356
<!-- etcd v2 -->
5457
<dependency>
5558
<groupId>org.mousio</groupId>
@@ -60,11 +63,6 @@
6063
<groupId>io.grpc</groupId>
6164
<artifactId>grpc-api</artifactId>
6265
</dependency>
63-
<dependency>
64-
<groupId>io.grpc</groupId>
65-
<artifactId>grpc-netty</artifactId>
66-
<scope>runtime</scope>
67-
</dependency>
6866
<dependency>
6967
<groupId>io.grpc</groupId>
7068
<artifactId>grpc-protobuf</artifactId>
@@ -136,21 +134,6 @@
136134
</execution>
137135
</executions>
138136
</plugin>
139-
<plugin>
140-
<groupId>org.apache.maven.plugins</groupId>
141-
<artifactId>maven-surefire-plugin</artifactId>
142-
<configuration>
143-
<systemPropertyVariables>
144-
<property>
145-
<!-- on big machines (e.g. 52 cores hyperthreaded)
146-
this failed with too many open files
147-
-->
148-
<name>io.netty.eventLoopThreads</name>
149-
<value>2</value>
150-
</property>
151-
</systemPropertyVariables>
152-
</configuration>
153-
</plugin>
154137
<plugin>
155138
<groupId>org.codehaus.mojo</groupId>
156139
<artifactId>build-helper-maven-plugin</artifactId>

config/etcd/src/main/java/io/helidon/config/etcd/internal/client/v2/EtcdV2Client.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,7 +55,7 @@ public class EtcdV2Client implements EtcdClient {
5555
*
5656
* @deprecated
5757
*/
58-
EtcdV2Client(URI... uris) {
58+
public EtcdV2Client(URI... uris) {
5959
etcd = new mousio.etcd4j.EtcdClient(uris);
6060
etcd.setRetryHandler(new RetryWithTimeout(100, 2000));
6161
}

config/etcd/src/main/java/io/helidon/config/etcd/internal/client/v3/EtcdV3Client.java

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -22,8 +22,8 @@
2222
import java.util.concurrent.Executor;
2323
import java.util.concurrent.Flow;
2424
import java.util.concurrent.SubmissionPublisher;
25-
import java.util.concurrent.TimeUnit;
2625

26+
import io.helidon.common.tls.Tls;
2727
import io.helidon.config.etcd.internal.client.EtcdClient;
2828
import io.helidon.config.etcd.internal.client.EtcdClientException;
2929
import io.helidon.config.etcd.internal.client.proto.KVGrpc;
@@ -34,23 +34,20 @@
3434
import io.helidon.config.etcd.internal.client.proto.WatchGrpc;
3535
import io.helidon.config.etcd.internal.client.proto.WatchRequest;
3636
import io.helidon.config.etcd.internal.client.proto.WatchResponse;
37+
import io.helidon.webclient.grpc.GrpcClient;
3738

3839
import com.google.protobuf.ByteString;
39-
import io.grpc.ManagedChannel;
40-
import io.grpc.ManagedChannelBuilder;
4140
import io.grpc.StatusRuntimeException;
4241
import io.grpc.stub.StreamObserver;
4342

4443
/**
4544
* Etcd API v3 client.
4645
*/
4746
public class EtcdV3Client implements EtcdClient {
48-
49-
private static final System.Logger LOGGER = System.getLogger(EtcdV3Client.class.getName());
47+
private static final Tls DISABLE_TLS = Tls.builder().enabled(false).build();
5048

5149
private final Map<String, SubmissionPublisher<Long>> publishers = new ConcurrentHashMap<>();
5250

53-
private final ManagedChannel channel;
5451
private final KVGrpc.KVBlockingStub kvStub;
5552
private final WatchGrpc.WatchStub watchStub;
5653

@@ -64,11 +61,12 @@ public EtcdV3Client(URI... uris) {
6461
throw new IllegalArgumentException("EtcdV3Client only supports a single URI");
6562
}
6663
URI uri = uris[0];
67-
ManagedChannelBuilder mcb = ManagedChannelBuilder.forAddress(uri.getHost(), uri.getPort());
68-
this.channel = mcb.usePlaintext().build();
69-
70-
kvStub = KVGrpc.newBlockingStub(channel);
71-
watchStub = WatchGrpc.newStub(channel);
64+
GrpcClient grpcClient = GrpcClient.builder()
65+
.baseUri(uri)
66+
.tls(DISABLE_TLS) // must explicitly disable it
67+
.build();
68+
kvStub = KVGrpc.newBlockingStub(grpcClient.channel());
69+
watchStub = WatchGrpc.newStub(grpcClient.channel());
7270
}
7371

7472
@Override
@@ -140,16 +138,7 @@ public Flow.Publisher<Long> watch(String key) throws EtcdClientException {
140138
}
141139

142140
@Override
143-
public void close() throws EtcdClientException {
141+
public void close() {
144142
publishers.values().forEach(SubmissionPublisher::close);
145-
if (!channel.isShutdown() && !channel.isTerminated()) {
146-
try {
147-
channel.awaitTermination(1, TimeUnit.SECONDS);
148-
} catch (InterruptedException e) {
149-
LOGGER.log(System.Logger.Level.INFO, "Error closing gRPC channel, reason: " + e.getLocalizedMessage(), e);
150-
} finally {
151-
channel.shutdown();
152-
}
153-
}
154143
}
155144
}

config/etcd/src/main/java/module-info.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2024 Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,6 +29,7 @@
2929
requires io.grpc.stub;
3030
requires io.helidon.common.media.type;
3131
requires io.helidon.common;
32+
requires io.helidon.webclient.grpc;
3233

3334
requires static java.annotation;
3435

Lines changed: 19 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2022 Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,29 +29,27 @@
2929
import io.helidon.config.etcd.internal.client.EtcdClient;
3030
import io.helidon.config.hocon.HoconConfigParser;
3131

32-
import org.junit.jupiter.params.ParameterizedTest;
33-
import org.junit.jupiter.params.provider.EnumSource;
32+
import org.junit.jupiter.api.Test;
3433

3534
import static org.hamcrest.MatcherAssert.assertThat;
3635
import static org.hamcrest.Matchers.hasSize;
3736
import static org.hamcrest.core.Is.is;
3837

3938
/**
40-
* Tests {@link EtcdConfigSource} with both version, {@link EtcdApi#v2} and {@link EtcdApi#v3}.
39+
* Integration test for {@link EtcdConfigSource} using {@link EtcdApi#v3}.
4140
*/
4241
public class EtcdConfigSourceIT {
4342

4443
private static final URI DEFAULT_URI = URI.create("http://localhost:2379");
4544

46-
@ParameterizedTest
47-
@EnumSource(EtcdApi.class)
48-
public void testConfig(EtcdApi version) throws Exception {
49-
putConfiguration(version, "/application.conf");
45+
@Test
46+
public void testConfig() throws Exception {
47+
putConfiguration("/application.conf");
5048
Config config = Config.builder()
5149
.sources(EtcdConfigSource.builder()
5250
.uri(DEFAULT_URI)
5351
.key("configuration")
54-
.api(version)
52+
.api(EtcdApi.v3)
5553
.mediaType(MediaTypes.APPLICATION_HOCON)
5654
.build())
5755
.addParser(HoconConfigParser.create())
@@ -60,15 +58,14 @@ public void testConfig(EtcdApi version) throws Exception {
6058
assertThat(config.get("security").asNodeList().get(), hasSize(1));
6159
}
6260

63-
@ParameterizedTest
64-
@EnumSource(EtcdApi.class)
65-
public void testConfigChanges(EtcdApi version) throws Exception {
66-
putConfiguration(version, "/application.conf");
61+
@Test
62+
public void testConfigChanges() throws Exception {
63+
putConfiguration("/application.conf");
6764
Config config = Config.builder()
6865
.sources(EtcdConfigSource.builder()
6966
.uri(DEFAULT_URI)
7067
.key("configuration")
71-
.api(version)
68+
.api(EtcdApi.v3)
7269
.mediaType(MediaTypes.APPLICATION_HOCON)
7370
.changeWatcher(EtcdWatcher.create())
7471
.build())
@@ -77,27 +74,23 @@ public void testConfigChanges(EtcdApi version) throws Exception {
7774

7875
assertThat(config.get("security").asNodeList().get(), hasSize(1));
7976

80-
CountDownLatch initLatch = new CountDownLatch(1);
8177
CountDownLatch nextLatch = new CountDownLatch(3);
78+
config.onChange(it -> nextLatch.countDown());
8279

83-
config.onChange(it -> initLatch.countDown());
84-
85-
assertThat(initLatch.await(1, TimeUnit.SECONDS), is(true));
86-
87-
putConfiguration(version, "/application2.conf");
80+
putConfiguration("/application2.conf");
8881
TimeUnit.MILLISECONDS.sleep(10);
89-
putConfiguration(version, "/application3.conf");
82+
putConfiguration("/application3.conf");
9083
TimeUnit.MILLISECONDS.sleep(10);
91-
putConfiguration(version, "/application4.conf");
84+
putConfiguration("/application4.conf");
9285

9386
assertThat(nextLatch.await(20, TimeUnit.SECONDS), is(true));
9487
}
9588

96-
private static void putConfiguration(EtcdApi version, String resourcePath) throws Exception {
97-
EtcdClient etcd = version.clientFactory().createClient(DEFAULT_URI);
98-
89+
private static void putConfiguration(String resourcePath) throws Exception {
90+
EtcdClient etcd = EtcdApi.v3.clientFactory().createClient(DEFAULT_URI);
9991
File file = new File(EtcdConfigSourceIT.class.getResource(resourcePath).getFile());
100-
etcd.put("configuration", String.join("\n", Files.readAllLines(file.toPath(), Charset.defaultCharset())));
92+
etcd.put("configuration", String.join("\n",
93+
Files.readAllLines(file.toPath(), Charset.defaultCharset())));
10194
etcd.close();
10295
}
10396
}

config/etcd/src/test/java/io/helidon/config/etcd/client/EtcdClientIT.java

Lines changed: 20 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2017, 2021 Oracle and/or its affiliates.
2+
* Copyright (c) 2017, 2025 Oracle and/or its affiliates.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -18,22 +18,17 @@
1818

1919
import java.lang.reflect.InvocationTargetException;
2020
import java.net.URI;
21-
import java.util.List;
2221
import java.util.Random;
2322
import java.util.concurrent.CountDownLatch;
24-
import java.util.concurrent.ExecutionException;
2523
import java.util.concurrent.Flow;
2624
import java.util.concurrent.TimeUnit;
27-
import java.util.stream.Stream;
2825

2926
import io.helidon.config.etcd.internal.client.EtcdClient;
3027
import io.helidon.config.etcd.internal.client.EtcdClientException;
31-
import io.helidon.config.etcd.internal.client.v2.EtcdV2Client;
3228
import io.helidon.config.etcd.internal.client.v3.EtcdV3Client;
3329

3430
import org.hamcrest.core.Is;
35-
import org.junit.jupiter.params.ParameterizedTest;
36-
import org.junit.jupiter.params.provider.MethodSource;
31+
import org.junit.jupiter.api.Test;
3732

3833
import static org.hamcrest.CoreMatchers.is;
3934
import static org.hamcrest.CoreMatchers.nullValue;
@@ -47,34 +42,26 @@ public class EtcdClientIT {
4742

4843
private static final URI uri = URI.create("http://localhost:2379");
4944

50-
private static Stream<Class<? extends EtcdClient>> clients() {
51-
return List.of(EtcdV2Client.class, EtcdV3Client.class).stream();
52-
}
53-
54-
@ParameterizedTest
55-
@MethodSource("clients")
56-
public <T extends EtcdClient> void testPutGet(Class<T> clientClass) throws EtcdClientException {
57-
runTest(clientClass, etcdClient -> {
45+
@Test
46+
public void testPutGet() {
47+
runTest(etcdClient -> {
5848
etcdClient.put("key", "value");
5949
String result = etcdClient.get("key");
6050
assertThat(result, is("value"));
61-
6251
});
6352
}
64-
65-
@ParameterizedTest
66-
@MethodSource("clients")
67-
public <T extends EtcdClient> void testGetNonExistingKey(Class<T> clientClass) throws EtcdClientException {
68-
runTest(clientClass, etcdClient -> {
53+
54+
@Test
55+
public void testGetNonExistingKey() {
56+
runTest(etcdClient -> {
6957
String result = etcdClient.get("non-existing-key");
7058
assertThat(result, nullValue());
7159
});
7260
}
7361

74-
@ParameterizedTest
75-
@MethodSource("clients")
76-
public <T extends EtcdClient> void testWatchNewKey(Class<T> clientClass) throws EtcdClientException, ExecutionException, InterruptedException {
77-
runTest(clientClass, etcdClient -> {
62+
@Test
63+
public void testWatchNewKey() {
64+
runTest(etcdClient -> {
7865
final String key = "key#" + new Random().nextLong();
7966
final String finalValue = "new value";
8067

@@ -115,10 +102,9 @@ public void onComplete() {
115102
});
116103
}
117104

118-
@ParameterizedTest
119-
@MethodSource("clients")
120-
public <T extends EtcdClient> void testWatchValueChanges(Class<T> clientClass) throws EtcdClientException, ExecutionException, InterruptedException {
121-
runTest(clientClass, etcdClient -> {
105+
@Test
106+
public void testWatchValueChanges() {
107+
runTest(etcdClient -> {
122108
final String key = "key";
123109

124110
etcdClient.put(key, "any value to change (just to be sure there is not already set to the final value");
@@ -166,14 +152,14 @@ public void onComplete() {
166152
*/
167153
@FunctionalInterface
168154
private interface EtcdClientConsumer {
169-
public void accept(EtcdClient t) throws EtcdClientException, InterruptedException;
155+
void accept(EtcdClient t) throws EtcdClientException, InterruptedException;
170156
}
171157

172-
private <T extends EtcdClient> void runTest(
173-
Class<T> clientClass,
174-
EtcdClientConsumer test) throws EtcdClientException {
158+
private <T extends EtcdClient> void runTest(EtcdClientConsumer test) {
175159
try {
176-
try (EtcdClient etcdClient = clientClass.getDeclaredConstructor(URI.class).newInstance(uri)) {
160+
URI[] uris = new URI[] {uri};
161+
try (EtcdClient etcdClient = EtcdV3Client.class.getDeclaredConstructor(URI[].class)
162+
.newInstance(new Object[] {uris})) {
177163
test.accept(etcdClient);
178164
} catch (EtcdClientException ex) {
179165
fail(ex);

0 commit comments

Comments
 (0)