Skip to content

Commit a911a05

Browse files
committed
Filter wrong formatted discovery addresses
Instead of discarding the full list of addresses when at least one address does not match the format like host[:port] a discoverer just skips this broken address and carries on processing. Change discovery function requirements description in part of single value support. This is done to be consistent with other Tarantool connectors. Closes: #195, #196
1 parent 762ce2f commit a911a05

File tree

3 files changed

+119
-23
lines changed

3 files changed

+119
-23
lines changed

README.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,11 @@ tarantool> function get_cluster_nodes() return { 'host1:3301', 'host2:3302', 'ho
237237
238238
You need to pay attention to a function contract we are currently supporting:
239239
* The client never passes any arguments to a discovery function.
240-
* A discovery function _should_ return a single result of strings (i.e. single
241-
string `return 'host:3301'` or array of strings `return {'host1:3301', 'host2:3301'}`).
240+
* A discovery function _must_ return an array of strings (i.e `return {'host1:3301', 'host2:3301'}`).
241+
* Each string _should_ satisfy the following pattern `host[:port]`
242+
(or more strictly `/^[^\:]+(:\d)?$/` - a mandatory host containing any string
243+
and an optional colon followed by digits of the port). Also, the port must be
244+
in a range between 1 and 65535 if one is presented.
242245
* A discovery function _may_ return multi-results but the client takes
243246
into account only first of them (i.e. `return {'host:3301'}, discovery_delay`, where
244247
the second result is unused). Even more, any extra results __are reserved__ by the client
@@ -271,6 +274,8 @@ client.syncOps().insert(45, Arrays.asList(1, 1));
271274
* A discovery task always uses an active client connection to get the nodes list.
272275
It's in your responsibility to provide a function availability as well as a consistent
273276
nodes list on all instances you initially set or obtain from the task.
277+
* Every address which is unmatched with `host[:port]` pattern will be filtered out from
278+
the target addresses list.
274279
* If some error occurs while a discovery task is running then this task
275280
will be aborted without any after-effects for next task executions. These cases, for instance, are
276281
a wrong function result (see discovery function contract) or a broken connection.

src/main/java/org/tarantool/cluster/TarantoolClusterStoredFunctionDiscoverer.java

+38-11
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.tarantool.TarantoolClient;
44
import org.tarantool.TarantoolClientOps;
55
import org.tarantool.TarantoolClusterClientConfig;
6+
import org.tarantool.util.StringUtils;
67

78
import java.util.LinkedHashSet;
89
import java.util.List;
@@ -36,28 +37,54 @@ public Set<String> getInstances() {
3637
// validation against the data returned;
3738
// this strict-mode allows us to extend the contract in a non-breaking
3839
// way for old clients just reserve an extra return value in
39-
// terms of LUA multi-result support.
40-
checkResult(list);
41-
42-
List<Object> funcResult = (List<Object>) list.get(0);
43-
return funcResult.stream()
44-
.map(Object::toString)
45-
.collect(Collectors.toCollection(LinkedHashSet::new));
40+
// terms of Lua multi-result support.;
41+
return checkAndFilterAddresses(list);
4642
}
4743

4844
/**
4945
* Check whether the result follows the contract or not.
50-
* The contract is a mandatory <b>single array of strings</b>
46+
* The contract is a mandatory <b>single array of strings</b>.
47+
*
48+
* The simplified format for each string is host[:port].
5149
*
5250
* @param result result to be validated
5351
*/
54-
private void checkResult(List<?> result) {
52+
private Set<String> checkAndFilterAddresses(List<?> result) {
5553
if (result == null || result.isEmpty()) {
5654
throw new IllegalDiscoveryFunctionResult("Discovery function returned no data");
5755
}
58-
if (!((List<Object>)result.get(0)).stream().allMatch(item -> item instanceof String)) {
59-
throw new IllegalDiscoveryFunctionResult("The first value must be an array of strings");
56+
57+
return ((List<Object>) result.get(0)).stream()
58+
.filter(item -> item instanceof String)
59+
.map(Object::toString)
60+
.filter(s -> !StringUtils.isBlank(s))
61+
.filter(this::isAddress)
62+
.collect(Collectors.toCollection(LinkedHashSet::new));
63+
}
64+
65+
/**
66+
* Checks that address matches host[:port] format.
67+
*
68+
* @param address to be checked
69+
* @return true if address follows the format
70+
*/
71+
private boolean isAddress(String address) {
72+
if (address.endsWith(":")) {
73+
return false;
74+
}
75+
String[] addressParts = address.split(":");
76+
if (addressParts.length > 2) {
77+
return false;
78+
}
79+
if (addressParts.length == 2) {
80+
try {
81+
int port = Integer.parseInt(addressParts[1]);
82+
return (port > 0 && port < 65535);
83+
} catch (NumberFormatException e) {
84+
return false;
85+
}
6086
}
87+
return true;
6188
}
6289

6390
}

src/test/java/org/tarantool/cluster/ClusterServiceStoredFunctionDiscovererIT.java

+74-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.tarantool.cluster;
22

33
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
45
import static org.junit.jupiter.api.Assertions.assertNotNull;
56
import static org.junit.jupiter.api.Assertions.assertThrows;
67
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -23,6 +24,7 @@
2324

2425
import java.util.Arrays;
2526
import java.util.Collections;
27+
import java.util.HashSet;
2628
import java.util.List;
2729
import java.util.Set;
2830

@@ -70,7 +72,7 @@ public void testSuccessfulAddressParsing() {
7072
control.openConsole(INSTANCE_NAME).exec(functionCode);
7173

7274
TarantoolClusterStoredFunctionDiscoverer discoverer =
73-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
75+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
7476

7577
Set<String> instances = discoverer.getInstances();
7678

@@ -89,7 +91,7 @@ public void testSuccessfulUniqueAddressParsing() {
8991
control.openConsole(INSTANCE_NAME).exec(functionCode);
9092

9193
TarantoolClusterStoredFunctionDiscoverer discoverer =
92-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
94+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
9395

9496
Set<String> instances = discoverer.getInstances();
9597

@@ -108,7 +110,7 @@ public void testFunctionReturnedEmptyList() {
108110
control.openConsole(INSTANCE_NAME).exec(functionCode);
109111

110112
TarantoolClusterStoredFunctionDiscoverer discoverer =
111-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
113+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
112114

113115
Set<String> instances = discoverer.getInstances();
114116

@@ -122,7 +124,7 @@ public void testWrongFunctionName() {
122124
clusterConfig.clusterDiscoveryEntryFunction = "wrongFunction";
123125

124126
TarantoolClusterStoredFunctionDiscoverer discoverer =
125-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
127+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
126128

127129
assertThrows(TarantoolException.class, discoverer::getInstances);
128130
}
@@ -134,21 +136,24 @@ public void testWrongInstanceAddress() {
134136

135137
client.close();
136138
TarantoolClusterStoredFunctionDiscoverer discoverer =
137-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
139+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
138140

139141
assertThrows(CommunicationException.class, discoverer::getInstances);
140142
}
141143

142144
@Test
143-
@DisplayName("fetched with an exception when wrong data type returned")
145+
@DisplayName("fetched an empty list when wrong data type returned")
144146
public void testWrongTypeResultData() {
145147
String functionCode = makeDiscoveryFunction(ENTRY_FUNCTION_NAME, 42);
146148
control.openConsole(INSTANCE_NAME).exec(functionCode);
147149

148150
TarantoolClusterStoredFunctionDiscoverer discoverer =
149-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
151+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
150152

151-
assertThrows(IllegalDiscoveryFunctionResult.class, discoverer::getInstances);
153+
Set<String> instances = discoverer.getInstances();
154+
155+
assertNotNull(instances);
156+
assertTrue(instances.isEmpty());
152157
}
153158

154159
@Test
@@ -170,7 +175,7 @@ public void testWrongMultiResultData() {
170175
control.openConsole(INSTANCE_NAME).exec(functionCode);
171176

172177
TarantoolClusterStoredFunctionDiscoverer discoverer =
173-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
178+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
174179

175180
Set<String> instances = discoverer.getInstances();
176181

@@ -186,9 +191,68 @@ public void testFunctionWithError() {
186191
control.openConsole(INSTANCE_NAME).exec(functionCode);
187192

188193
TarantoolClusterStoredFunctionDiscoverer discoverer =
189-
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
194+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
190195

191196
assertThrows(TarantoolException.class, discoverer::getInstances);
192197
}
193198

199+
@Test
200+
@DisplayName("fetched a subset of valid addresses")
201+
public void testFilterBadAddressesData() {
202+
final List<String> allHosts = Arrays.asList(
203+
"host1:3313",
204+
"host:abc",
205+
"192.168.33.90",
206+
"myHost",
207+
"10.30.10.4:7814",
208+
"host:311:sub-host",
209+
"instance-2:",
210+
"host:0",
211+
"host:321981"
212+
);
213+
214+
final Set<String> expectedFiltered = new HashSet<>(
215+
Arrays.asList(
216+
"host1:3313",
217+
"192.168.33.90",
218+
"myHost",
219+
"10.30.10.4:7814"
220+
)
221+
);
222+
223+
String functionCode = makeDiscoveryFunction(ENTRY_FUNCTION_NAME, allHosts);
224+
control.openConsole(INSTANCE_NAME).exec(functionCode);
225+
226+
TarantoolClusterStoredFunctionDiscoverer discoverer =
227+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
228+
229+
Set<String> instances = discoverer.getInstances();
230+
231+
assertNotNull(instances);
232+
assertFalse(instances.isEmpty());
233+
assertEquals(expectedFiltered, instances);
234+
}
235+
236+
@Test
237+
@DisplayName("fetched an empty set after filtration")
238+
public void testFullBrokenAddressesList() {
239+
List<String> allHosts = Arrays.asList(
240+
"abc:edf",
241+
"192.168.33.90:",
242+
"host:-123",
243+
"host:0"
244+
);
245+
246+
String functionCode = makeDiscoveryFunction(ENTRY_FUNCTION_NAME, allHosts);
247+
control.openConsole(INSTANCE_NAME).exec(functionCode);
248+
249+
TarantoolClusterStoredFunctionDiscoverer discoverer =
250+
new TarantoolClusterStoredFunctionDiscoverer(clusterConfig, client);
251+
252+
Set<String> instances = discoverer.getInstances();
253+
254+
assertNotNull(instances);
255+
assertTrue(instances.isEmpty());
256+
}
257+
194258
}

0 commit comments

Comments
 (0)