Skip to content

Commit 551b969

Browse files
authored
Handle http(s):// prefixed endpoints properly in all cases (#35)
* Handle http(s):// prefixed endpoints properly in all cases Also - Stop using deprecated method in grpc NameResolver API - Add unit tests * Check explicitly for timeout in WatchTest
1 parent 006677c commit 551b969

9 files changed

Lines changed: 105 additions & 42 deletions

File tree

src/main/java/com/ibm/etcd/client/EtcdClient.java

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
import java.util.concurrent.TimeUnit;
3838
import java.util.concurrent.atomic.AtomicInteger;
3939
import java.util.function.Consumer;
40+
import java.util.regex.Matcher;
41+
import java.util.regex.Pattern;
4042

4143
import javax.net.ssl.SSLException;
4244
import javax.net.ssl.TrustManagerFactory;
@@ -105,6 +107,10 @@ public class EtcdClient implements KvStoreClient {
105107
public static final long DEFAULT_TIMEOUT_MS = 10_000L; // 10sec default
106108
public static final int DEFAULT_SESSION_TIMEOUT_SECS = 20; // 20sec default
107109

110+
// (not intended to be strict hostname validation here)
111+
protected static final Pattern ADDR_PATT =
112+
Pattern.compile("(?:https?://|dns:///)?([a-zA-Z0-9\\-.]+)(?::(\\d+))?");
113+
108114
private final int sessionTimeoutSecs;
109115

110116
private final ByteString name, password;
@@ -302,7 +308,7 @@ public Builder withSessionTimeoutSecs(int timeoutSecs) {
302308
* @param sizeInBytes
303309
*/
304310
public Builder withMaxInboundMessageSize(int sizeInBytes) {
305-
this.maxInboundMessageSize = sizeInBytes;
311+
this.maxInboundMessageSize = sizeInBytes;
306312
return this;
307313
}
308314

@@ -312,7 +318,7 @@ public Builder withMaxInboundMessageSize(int sizeInBytes) {
312318
public EtcdClient build() {
313319
NettyChannelBuilder ncb;
314320
if (endpoints.size() == 1) {
315-
ncb = NettyChannelBuilder.forTarget(endpoints.get(0));
321+
ncb = NettyChannelBuilder.forTarget(endpointToUriString(endpoints.get(0)));
316322
if (overrideAuthority != null) {
317323
ncb.overrideAuthority(overrideAuthority);
318324
}
@@ -336,6 +342,19 @@ public EtcdClient build() {
336342
}
337343
}
338344

345+
static String endpointToUriString(String endpoint) {
346+
Preconditions.checkNotNull(endpoint, "null endpoint");
347+
Matcher m = ADDR_PATT.matcher(endpoint.trim());
348+
if (!m.matches()) {
349+
throw new IllegalArgumentException("invalid endpoint: " + endpoint);
350+
}
351+
String portStr = m.group(2);
352+
if (portStr == null) {
353+
portStr = String.valueOf(DEFAULT_PORT);
354+
}
355+
return "dns:///" + m.group(1) + ":" + portStr;
356+
}
357+
339358
private static int defaultThreadCount() {
340359
return Math.min(6, Runtime.getRuntime().availableProcessors());
341360
}

src/main/java/com/ibm/etcd/client/StaticEtcdNameResolverFactory.java

Lines changed: 11 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import java.util.Arrays;
2323
import java.util.Collections;
2424
import java.util.List;
25-
import java.util.regex.Matcher;
26-
import java.util.regex.Pattern;
2725
import java.util.stream.Stream;
2826

2927
import io.grpc.Attributes;
@@ -42,16 +40,12 @@ class StaticEtcdNameResolverFactory extends NameResolver.Factory {
4240

4341
protected static final NameResolver.Factory DNS_PROVIDER = new DnsNameResolverProvider();
4442

45-
// (not intended to be strict hostname validation here)
46-
protected static final Pattern ADDR_PATT =
47-
Pattern.compile("(?:https?://)?([a-zA-Z0-9\\-.]+)(?::(\\d+))?");
48-
4943
static class SubResolver {
5044
final NameResolver resolver;
5145
List<EquivalentAddressGroup> eagList = Collections.emptyList();
5246

53-
public SubResolver(URI uri, NameResolver.Helper helper) {
54-
this.resolver = DNS_PROVIDER.newNameResolver(uri, helper);
47+
public SubResolver(URI uri, NameResolver.Args args) {
48+
this.resolver = DNS_PROVIDER.newNameResolver(uri, args);
5549
}
5650

5751
void updateEagList(List<EquivalentAddressGroup> servers, boolean ownAuthority) {
@@ -79,34 +73,23 @@ public StaticEtcdNameResolverFactory(List<String> endpoints, String overrideAuth
7973
throw new IllegalArgumentException("endpoints");
8074
}
8175
this.overrideAuthority = overrideAuthority;
82-
int count = endpoints.size();
83-
uris = new URI[count];
84-
for (int i = 0; i < count; i++) {
85-
String endpoint = endpoints.get(i).trim();
86-
Matcher m = ADDR_PATT.matcher(endpoint);
87-
if (!m.matches()) {
88-
throw new IllegalArgumentException("invalid endpoint: " + endpoint);
89-
}
90-
String portStr = m.group(2);
91-
if (portStr == null) {
92-
portStr = String.valueOf(EtcdClient.DEFAULT_PORT);
93-
}
94-
uris[i] = URI.create("dns:///" + m.group(1) + ":" + portStr);
95-
}
96-
if (count > 1) {
76+
uris = endpoints.stream()
77+
.map(ep -> URI.create(EtcdClient.endpointToUriString(ep)))
78+
.toArray(URI[]::new);
79+
if (uris.length > 1) {
9780
Collections.shuffle(Arrays.asList(uris));
9881
}
9982
}
10083

10184
@Override
102-
public NameResolver newNameResolver(URI targetUri, NameResolver.Helper helper) {
85+
public NameResolver newNameResolver(URI targetUri, NameResolver.Args args) {
10386
if (!ETCD.equals(targetUri.getScheme())) {
10487
return null;
10588
}
10689
if (uris.length == 1) {
107-
return new SubResolver(uris[0], helper).resolver;
90+
return new SubResolver(uris[0], args).resolver;
10891
}
109-
SubResolver[] resolvers = createSubResolvers(helper);
92+
SubResolver[] resolvers = createSubResolvers(args);
11093
return new NameResolver() {
11194
int currentCount = 0;
11295
@Override
@@ -157,11 +140,11 @@ public void shutdown() {
157140
};
158141
}
159142

160-
private SubResolver[] createSubResolvers(NameResolver.Helper helper) {
143+
private SubResolver[] createSubResolvers(NameResolver.Args args) {
161144
int count = uris.length;
162145
SubResolver[] resolvers = new SubResolver[count];
163146
for (int i = 0; i < count; i++) {
164-
resolvers[i] = new SubResolver(uris[i], helper);
147+
resolvers[i] = new SubResolver(uris[i], args);
165148
}
166149
return resolvers;
167150
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2017, 2018 IBM Corp. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
5+
* use this file except in compliance with the License. You may obtain a copy
6+
* of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package com.ibm.etcd.client;
17+
18+
import static com.ibm.etcd.client.KeyUtils.bs;
19+
import static org.junit.Assert.assertEquals;
20+
21+
import java.util.Collections;
22+
23+
import org.junit.Test;
24+
25+
import com.ibm.etcd.client.kv.KvClient;
26+
27+
public class ClientBuilderTest {
28+
29+
@Test
30+
public void testForEndpoint() throws Exception {
31+
try (KvStoreClient client = EtcdClient.forEndpoint("localhost", 2379)
32+
.withPlainText().build()) {
33+
basicTest(client);
34+
}
35+
}
36+
37+
@Test
38+
public void testForEndpointsSingle() throws Exception {
39+
String[] endpoints = { "localhost:2379", "http://localhost:2379",
40+
"https://localhost:2379", "dns:///localhost:2379" };
41+
42+
for (String endpoint : endpoints) {
43+
try (KvStoreClient client = EtcdClient.forEndpoints(Collections.singletonList(endpoint))
44+
.withPlainText().build()) {
45+
basicTest(client);
46+
}
47+
}
48+
}
49+
50+
@Test
51+
public void testForEndpointsMulti() throws Exception {
52+
try (KvStoreClient client = EtcdClient.forEndpoints(
53+
"localhost:2379,http://localhost:2379,https://localhost:2379,dns:///localhost:2379")
54+
.withPlainText().build()) {
55+
basicTest(client);
56+
}
57+
}
58+
59+
static void basicTest(KvStoreClient client) {
60+
KvClient kvc = client.getKvClient();
61+
kvc.put(bs("cbt"), bs("test")).sync();
62+
assertEquals("test", kvc.delete(bs("cbt")).prevKv().sync()
63+
.getPrevKvs(0).getValue().toStringUtf8());
64+
}
65+
}

src/test/java/com/ibm/etcd/client/EtcdTestSuite.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
import com.ibm.etcd.client.utils.RangeCacheTest;
3636

3737
@RunWith(Suite.class)
38-
@SuiteClasses({KvTest.class, WatchTest.class, LeaseTest.class,
38+
@SuiteClasses({ClientBuilderTest.class, KvTest.class, WatchTest.class, LeaseTest.class,
3939
LockTest.class, PersistentLeaseKeyTest.class, RangeCacheTest.class})
4040
public class EtcdTestSuite {
4141

src/test/java/com/ibm/etcd/client/KvTest.java

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package com.ibm.etcd.client;
1717

18+
import static com.ibm.etcd.client.KeyUtils.bs;
1819
import static org.junit.Assert.*;
1920

2021
import java.util.UUID;
@@ -27,8 +28,6 @@
2728

2829
import com.google.common.util.concurrent.ListenableFuture;
2930
import com.google.protobuf.ByteString;
30-
import com.ibm.etcd.client.EtcdClient;
31-
import com.ibm.etcd.client.KvStoreClient;
3231
import com.ibm.etcd.client.kv.KvClient;
3332

3433
import io.grpc.Deadline;
@@ -188,10 +187,6 @@ public void testSyncDeadlock() throws Exception {
188187
}
189188
}
190189

191-
public static ByteString bs(String str) {
192-
return ByteString.copyFromUtf8(str);
193-
}
194-
195190
static String t(long start) {
196191
return String.format("%.3f ", (System.currentTimeMillis() - start) / 1000.0);
197192
}

src/test/java/com/ibm/etcd/client/WatchTest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package com.ibm.etcd.client;
1717

18-
import static com.ibm.etcd.client.KvTest.bs;
18+
import static com.ibm.etcd.client.KeyUtils.bs;
1919
import static com.ibm.etcd.client.KvTest.t;
2020
import static org.junit.Assert.*;
2121

@@ -163,7 +163,8 @@ public void onCompleted() {
163163
proxy.start();
164164

165165
// watch should be unaffected - next event seen should be the missed one
166-
wu = (WatchUpdate)watchEvents.poll(5000L, TimeUnit.MILLISECONDS);
166+
wu = (WatchUpdate) watchEvents.poll(6000L, TimeUnit.MILLISECONDS);
167+
assertNotNull("Expected watch event not received after server reconnection", wu);
167168
assertEquals(bs("/watchtest/e"), wu.getEvents().get(0).getKv().getKey());
168169

169170
watch.close();

src/test/java/com/ibm/etcd/client/utils/LeaderElectionTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
*/
1616
package com.ibm.etcd.client.utils;
1717

18-
import static com.ibm.etcd.client.KvTest.bs;
18+
import static com.ibm.etcd.client.KeyUtils.bs;
1919
import static org.junit.Assert.*;
2020

2121
import org.junit.Test;

src/test/java/com/ibm/etcd/client/utils/PersistentLeaseKeyTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import java.util.List;
2121
import java.util.concurrent.TimeUnit;
2222

23-
import static com.ibm.etcd.client.KvTest.bs;
23+
import static com.ibm.etcd.client.KeyUtils.bs;
2424
import static java.util.concurrent.TimeUnit.MILLISECONDS;
2525

2626
import org.junit.AfterClass;

src/test/java/com/ibm/etcd/client/utils/RangeCacheTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import static com.ibm.etcd.client.KeyUtils.fromHexString;
1919
import static com.ibm.etcd.client.KeyUtils.plusOne;
2020
import static com.ibm.etcd.client.KeyUtils.toHexString;
21-
import static com.ibm.etcd.client.KvTest.bs;
21+
import static com.ibm.etcd.client.KeyUtils.bs;
2222
import static org.junit.Assert.assertEquals;
2323
import static org.junit.Assert.assertFalse;
2424
import static org.junit.Assert.assertNull;

0 commit comments

Comments
 (0)