Repository: brooklyn-server Updated Branches: refs/heads/master a3193573b -> f4281af2e
JcloudsLocation: Ability to supply external implementation for the socket tester predicate Project: http://git-wip-us.apache.org/repos/asf/brooklyn-server/repo Commit: http://git-wip-us.apache.org/repos/asf/brooklyn-server/commit/c2b04ca1 Tree: http://git-wip-us.apache.org/repos/asf/brooklyn-server/tree/c2b04ca1 Diff: http://git-wip-us.apache.org/repos/asf/brooklyn-server/diff/c2b04ca1 Branch: refs/heads/master Commit: c2b04ca16b59daa7d5ae3f26f1fc06559b32a322 Parents: a319357 Author: Valentin Aitken <bos...@gmail.com> Authored: Fri Sep 16 18:34:41 2016 +0300 Committer: Valentin Aitken <bos...@gmail.com> Committed: Tue Sep 20 18:48:23 2016 +0300 ---------------------------------------------------------------------- .../brooklyn/core/config/ConfigUtils.java | 18 ++ .../location/cloud/CloudLocationConfig.java | 13 +- .../location/jclouds/JcloudsLocation.java | 35 +++- .../brooklyn/location/jclouds/JcloudsUtil.java | 7 +- ...nReachabilityPredicateInstantiationTest.java | 191 +++++++++++++++++++ .../apache/brooklyn/util/net/Networking.java | 7 + .../util/net/ReachableSocketFinder.java | 2 +- 7 files changed, 269 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/core/src/main/java/org/apache/brooklyn/core/config/ConfigUtils.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/config/ConfigUtils.java b/core/src/main/java/org/apache/brooklyn/core/config/ConfigUtils.java index 80d06b9..d5b0c41 100644 --- a/core/src/main/java/org/apache/brooklyn/core/config/ConfigUtils.java +++ b/core/src/main/java/org/apache/brooklyn/core/config/ConfigUtils.java @@ -33,7 +33,9 @@ import org.apache.brooklyn.config.ConfigKey.HasConfigKey; import org.apache.brooklyn.core.config.ConfigUtils; import org.apache.brooklyn.core.config.WrappedConfigKey; import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -126,4 +128,20 @@ public class ConfigUtils { } return Collections.unmodifiableSet(result); } + + /** + * Look for keys with <code>configPrefix</code> in <code>configBag</code> and + * those which have <code>configPrefix</code> are added in <code>destinationBucket</code> but without <code>configPrefix</code>. + * @param configPrefix prefix to look for + * @param configBag keys to look in + * @param destinationBucket should not be an ImmutableMap + */ + public static void addUnprefixedConfigKeyInConfigBack(String configPrefix, ConfigBag configBag, Map<String, Object> destinationBucket) { + for (Map.Entry<ConfigKey<?>, ?> entry : configBag.getAllConfigAsConfigKeyMap().entrySet()) { + String keyName = entry.getKey().getName(); + if (keyName.startsWith(configPrefix)) { + destinationBucket.put(Strings.removeFromStart(keyName, configPrefix), entry.getValue()); + } + } + } } http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java index 3bde8c7..21cd132 100644 --- a/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java +++ b/core/src/main/java/org/apache/brooklyn/core/location/cloud/CloudLocationConfig.java @@ -21,6 +21,8 @@ package org.apache.brooklyn.core.location.cloud; import java.util.Collection; import com.google.common.annotations.Beta; +import com.google.common.base.Predicate; +import com.google.common.net.HostAndPort; import com.google.common.reflect.TypeToken; import org.apache.brooklyn.api.location.MachineLocationCustomizer; @@ -30,6 +32,7 @@ import org.apache.brooklyn.core.config.ConfigKeys; import org.apache.brooklyn.core.config.MapConfigKey; import org.apache.brooklyn.core.location.LocationConfigKeys; import org.apache.brooklyn.util.core.flags.SetFromFlag; +import org.apache.brooklyn.util.net.Networking; public interface CloudLocationConfig { @@ -79,7 +82,15 @@ public interface CloudLocationConfig { + "if 'false', will default to the node's first public IP (or privae if no public IPs); " + "if 'true' uses default duration; otherwise accepts a time string e.g. '5m' (the default) or a number of milliseconds", "5m"); - public static final ConfigKey<String> WAIT_FOR_SSHABLE = ConfigKeys.newStringConfigKey("waitForSshable", + ConfigKey<Predicate<HostAndPort>> POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE = ConfigKeys.newConfigKey(new TypeToken<Predicate<HostAndPort>>(){}, "pollForFirstReachableAddress.predicate", + "Predicate<HostAndPort> implementation which checks whether machine is up or not."); + + ConfigKey<Class<? extends Predicate<HostAndPort>>> POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE = ConfigKeys.newConfigKey(new TypeToken<Class<? extends Predicate<HostAndPort>>>(){}, "pollForFirstReachableAddress.predicate.type", + "Predicate<HostAndPort> class. " + + "Other keys prefixed with pollForFirstReachableAddress.predicate.<property> will be passed to the Map constructor of the Predicate<HostAndPort> implementation.", + Networking.IsReachablePredicate.class); + + public static final ConfigKey<String> WAIT_FOR_SSHABLE = ConfigKeys.newStringConfigKey("waitForSshable", "Whether and how long to wait for a newly provisioned VM to be accessible via ssh; " + "if 'false', won't check; if 'true' uses default duration; otherwise accepts a time string e.g. '5m' (the default) or a number of milliseconds", "5m"); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java index a1d1293..29fbfce 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsLocation.java @@ -28,6 +28,7 @@ import static org.jclouds.util.Throwables2.getFirstThrowableOfType; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.security.KeyPair; @@ -2862,7 +2863,39 @@ public class JcloudsLocation extends AbstractCloudMachineProvisioningLocation im String result; if (enabled) { Duration timeout = "true".equals(pollForFirstReachable) ? Duration.FIVE_MINUTES : Duration.of(pollForFirstReachable); - result = JcloudsUtil.getFirstReachableAddress(node, timeout); + + Predicate<HostAndPort> pollForFirstReachableHostAndPortPredicate; + if (setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE) != null) { + LOG.debug("Using pollForFirstReachableAddress.predicate supplied from config for location " + this + " " + + POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + " : " + setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE)); + pollForFirstReachableHostAndPortPredicate = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE); + } else { + LOG.debug("Using pollForFirstReachableAddress.predicate.type supplied from config for location " + this + " " + POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE.getName() + + " : " + setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE)); + + Class<? extends Predicate<HostAndPort>> predicateType = setup.get(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE); + + Map<String, Object> args = MutableMap.of(); + ConfigUtils.addUnprefixedConfigKeyInConfigBack(POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".", setup, args); + try { + pollForFirstReachableHostAndPortPredicate = predicateType.getConstructor(Map.class).newInstance(args); + } catch (NoSuchMethodException|IllegalAccessException e) { + try { + pollForFirstReachableHostAndPortPredicate = predicateType.newInstance(); + } catch (IllegalAccessException|InstantiationException newInstanceException) { + throw Exceptions.propagate("Instantiating " + predicateType + " failed.", newInstanceException); + } + } catch (InvocationTargetException|InstantiationException e) { + throw Exceptions.propagate("Problem trying to instantiate " + predicateType + " with Map constructor.", e); + } + } + + try { + result = JcloudsUtil.getFirstReachableAddress(node, timeout, pollForFirstReachableHostAndPortPredicate); + } catch (Exception e) { + throw Exceptions.propagate("Problem instantiating reachability checker for " + this + + " pollForFirstReachableHostAndPortPredicate: " + pollForFirstReachableHostAndPortPredicate, e); + } LOG.debug("Using first-reachable address "+result+" for node "+node+" in "+this); } else { result = Iterables.getFirst(Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses()), null); http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java index 685f81d..0ba9b29 100644 --- a/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java +++ b/locations/jclouds/src/main/java/org/apache/brooklyn/location/jclouds/JcloudsUtil.java @@ -41,6 +41,7 @@ import org.apache.brooklyn.core.config.Sanitizer; import org.apache.brooklyn.util.collections.MutableList; import org.apache.brooklyn.util.core.config.ConfigBag; import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.net.Networking; import org.apache.brooklyn.util.net.Protocol; import org.apache.brooklyn.util.net.ReachableSocketFinder; import org.apache.brooklyn.util.ssh.BashCommands; @@ -354,6 +355,10 @@ public class JcloudsUtil implements JcloudsLocationConfig { } public static String getFirstReachableAddress(NodeMetadata node, Duration timeout) { + return getFirstReachableAddress(node, timeout, new Networking.IsReachablePredicate()); + } + + public static String getFirstReachableAddress(NodeMetadata node, Duration timeout, Predicate<HostAndPort> socketTester) { final int port = node.getLoginPort(); List<HostAndPort> sockets = FluentIterable .from(Iterables.concat(node.getPublicAddresses(), node.getPrivateAddresses())) @@ -365,7 +370,7 @@ public class JcloudsUtil implements JcloudsLocationConfig { ListeningExecutorService executor = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool()); try { - ReachableSocketFinder finder = new ReachableSocketFinder(executor); + ReachableSocketFinder finder = new ReachableSocketFinder(socketTester, executor); HostAndPort result = finder.findOpenSocketOnNode(sockets, timeout); return result.getHostText(); } catch (Exception e) { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationReachabilityPredicateInstantiationTest.java ---------------------------------------------------------------------- diff --git a/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationReachabilityPredicateInstantiationTest.java b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationReachabilityPredicateInstantiationTest.java new file mode 100644 index 0000000..8178639 --- /dev/null +++ b/locations/jclouds/src/test/java/org/apache/brooklyn/location/jclouds/JcloudsLocationReachabilityPredicateInstantiationTest.java @@ -0,0 +1,191 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.brooklyn.location.jclouds; + +import com.google.common.base.Predicate; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.net.HostAndPort; +import org.apache.brooklyn.core.location.cloud.CloudLocationConfig; +import org.apache.brooklyn.util.core.config.ConfigBag; +import org.jclouds.compute.domain.NodeMetadata; +import org.jclouds.compute.domain.NodeMetadataBuilder; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +public class JcloudsLocationReachabilityPredicateInstantiationTest { + public static final HostAndPort ALLOWED_HOST_AND_PORT = HostAndPort.fromParts("localhost", 223); + private BailOutJcloudsLocation jcloudsLocation; + + private final NodeMetadata node = new NodeMetadataBuilder() + .id("ID") + .loginPort(ALLOWED_HOST_AND_PORT.getPort()) + .status(NodeMetadata.Status.RUNNING) + .privateAddresses(ImmutableList.of(ALLOWED_HOST_AND_PORT.getHostText())) + .build(); + + @BeforeMethod + public void setUp() { + jcloudsLocation = new BailOutJcloudsLocation(); + } + + @Test + public void testConfigLocationWithReachabilityPredicate() { + List<String> hostsMatchedHolder = new ArrayList<>(); + Predicate<HostAndPort> hostAndPortPredicate = new RecordingReachabilityCheckMapAndFlagsConstructor(ImmutableMap.<String, Object>of("hostsMatchedHolder", hostsMatchedHolder)); + ConfigBag predicateConfig = new ConfigBag(); + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE, hostAndPortPredicate); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + Assert.assertTrue(hostsMatchedHolder.contains(ALLOWED_HOST_AND_PORT.getHostText())); + } + + protected static final AtomicInteger TEST_INIT_DEFAULT_CONSTRUCTOR_COUNTER = new AtomicInteger(0); + @Test + public void testInitDefaultConstructor() throws Exception { + Assert.assertEquals(TEST_INIT_DEFAULT_CONSTRUCTOR_COUNTER.get(), 0); + ConfigBag predicateConfig = new ConfigBag(); + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE, RecordingReachabilityCheck.class); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + Assert.assertEquals(TEST_INIT_DEFAULT_CONSTRUCTOR_COUNTER.get(), 1); + } + + protected static final AtomicInteger TEST_INIT_MAP_CONSTRUCTOR_COUNTER = new AtomicInteger(0); + @Test + public void testInitMapConstructor() { + Assert.assertEquals(TEST_INIT_MAP_CONSTRUCTOR_COUNTER.get(), 0); + ConfigBag predicateConfig = new ConfigBag(); + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE, RecordingReachabilityCheckMapConstructor.class); + predicateConfig.putStringKey(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".key1", "val1"); + predicateConfig.putStringKey(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".key2", "val2"); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + Assert.assertEquals(TEST_INIT_MAP_CONSTRUCTOR_COUNTER.get(), 1); + } + + @Test + public void testInitMapConstructorWithFlags() { + ConfigBag predicateConfig = new ConfigBag(); + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE, RecordingReachabilityCheckMapAndFlagsConstructor.class); + List<String> hostsMatchedHolder = new ArrayList<>(); + predicateConfig.putStringKey(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE.getName() + ".hostsMatchedHolder", hostsMatchedHolder); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + Assert.assertTrue(hostsMatchedHolder.contains(ALLOWED_HOST_AND_PORT.getHostText())); + } + + protected static final AtomicInteger TEST_INIT_MAP_AND_EMPTY_CONSTRUCTOR_COUNTER = new AtomicInteger(0); + @Test + public void testInitEmptyConstructor() { + Assert.assertEquals(TEST_INIT_MAP_AND_EMPTY_CONSTRUCTOR_COUNTER.get(), 0); + ConfigBag predicateConfig = new ConfigBag(); + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE, RecordingReachabilityCheckProtectedMapConstructor.class); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + Assert.assertEquals(TEST_INIT_MAP_AND_EMPTY_CONSTRUCTOR_COUNTER.get(), 1); + } + + @Test + public void testNoSuitableConstructorFound() { + ConfigBag predicateConfig = new ConfigBag(); + + try { + predicateConfig.put(CloudLocationConfig.POLL_FOR_FIRST_REACHABLE_ADDRESS_PREDICATE_TYPE, NoSuitableConstructorPredicate.class); + jcloudsLocation.getFirstReachableAddress(node, predicateConfig); + } catch (RuntimeException e) { + Assert.assertTrue(InstantiationException.class.isAssignableFrom(e.getCause().getClass())); + } + } + + static class NoSuitableConstructorPredicate implements Predicate<HostAndPort> { + public NoSuitableConstructorPredicate(NoSuitableConstructorPredicate a) {} + + @Override + public boolean apply(@Nullable HostAndPort input) { + return false; + } + } + + // Needs to be static in order to be instantiated dynamically + public static class RecordingReachabilityCheck implements Predicate<HostAndPort> { + @Override + public boolean apply(@Nullable HostAndPort input) { + TEST_INIT_DEFAULT_CONSTRUCTOR_COUNTER.incrementAndGet(); + return true; + } + } + + public static class RecordingReachabilityCheckMapConstructor implements Predicate<HostAndPort> { + private Map<String, Object> flags; + + public RecordingReachabilityCheckMapConstructor(Map<String, Object> flags) { + this.flags = flags; + } + + @Override + public boolean apply(@Nullable HostAndPort input) { + // TODO bad practice to assert in thread which doesn't pass Assert exceptions + Assert.assertEquals(flags.get("key1"), "val1"); + Assert.assertEquals(flags.get("key2"), "val2"); + TEST_INIT_MAP_CONSTRUCTOR_COUNTER.getAndIncrement(); + return true; + } + } + + public static class RecordingReachabilityCheckMapAndFlagsConstructor implements Predicate<HostAndPort> { + private Map<String, Object> flags; + + public RecordingReachabilityCheckMapAndFlagsConstructor(Map<String, Object> flags) { + this.flags = flags; + } + + @Override + public boolean apply(@Nullable HostAndPort input) { + ((List<String>)flags.get("hostsMatchedHolder")).add(input.getHostText()); + return true; + } + } + + public static class RecordingReachabilityCheckProtectedMapConstructor implements Predicate<HostAndPort> { + public RecordingReachabilityCheckProtectedMapConstructor() { + TEST_INIT_MAP_AND_EMPTY_CONSTRUCTOR_COUNTER.getAndIncrement(); + } + + protected RecordingReachabilityCheckProtectedMapConstructor(Map<String, Object> flags) {} + + @Override + public boolean apply(@Nullable HostAndPort input) { + return true; + } + } + + public static class BailOutJcloudsLocation extends JcloudsLocation { + public BailOutJcloudsLocation() { + super(); + } + + @Override + public String getFirstReachableAddress(NodeMetadata nodeMetada, ConfigBag setup) { + return super.getFirstReachableAddress(nodeMetada, setup); + } + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java index 5d3c69f..fde462e 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/Networking.java @@ -38,6 +38,7 @@ import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.regex.Pattern; +import com.google.common.base.Predicate; import com.google.common.collect.Range; import com.google.common.collect.RangeSet; import com.google.common.collect.TreeRangeSet; @@ -538,6 +539,12 @@ public class Networking { } } + public static class IsReachablePredicate implements Predicate<HostAndPort> { + @Override public boolean apply(HostAndPort input) { + return Networking.isReachable(input); + } + }; + public static void closeQuietly(Socket s) { if (s != null) { try { http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/c2b04ca1/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java ---------------------------------------------------------------------- diff --git a/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java b/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java index 716ad72..72fb71c 100644 --- a/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java +++ b/utils/common/src/main/java/org/apache/brooklyn/util/net/ReachableSocketFinder.java @@ -91,7 +91,7 @@ public class ReachableSocketFinder { .backoffTo(Duration.FIVE_SECONDS) .until(new Callable<Boolean>() { public Boolean call() { - Optional<HostAndPort> reachableSocket = tryReachable(sockets, Duration.seconds(2)); + Optional<HostAndPort> reachableSocket = tryReachable(sockets, Duration.FIVE_SECONDS); if (reachableSocket.isPresent()) { result.compareAndSet(null, reachableSocket.get()); return true;