org.jclouds.compute.util.ConcurrentOpenSocketFinder Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of jclouds-compute Show documentation
Show all versions of jclouds-compute Show documentation
jclouds components to access compute providers
/*
* 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.jclouds.compute.util;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.base.Predicates.or;
import static com.google.common.base.Throwables.propagate;
import static com.google.common.util.concurrent.Atomics.newReference;
import static com.google.common.util.concurrent.MoreExecutors.listeningDecorator;
import static java.lang.String.format;
import static org.jclouds.Constants.PROPERTY_USER_THREADS;
import static org.jclouds.compute.config.ComputeServiceProperties.TIMEOUT_NODE_RUNNING;
import static org.jclouds.compute.config.ComputeServiceProperties.SOCKET_FINDER_ALLOWED_INTERFACES;
import static org.jclouds.util.Predicates2.retry;
import java.util.NoSuchElementException;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.annotation.Resource;
import javax.inject.Named;
import org.jclouds.compute.domain.NodeMetadata;
import org.jclouds.compute.reference.ComputeServiceConstants;
import org.jclouds.logging.Logger;
import org.jclouds.predicates.SocketOpen;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;
import com.google.common.collect.ImmutableSet;
import com.google.common.net.HostAndPort;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.inject.Inject;
public class ConcurrentOpenSocketFinder implements OpenSocketFinder {
@Resource
@Named(ComputeServiceConstants.COMPUTE_LOGGER)
private Logger logger = Logger.NULL;
private final SocketOpen socketTester;
private final Predicate> nodeRunning;
private final ListeningExecutorService userExecutor;
@Inject(optional = true)
@Named(SOCKET_FINDER_ALLOWED_INTERFACES)
private AllowedInterfaces allowedInterfaces = AllowedInterfaces.ALL;
@Inject
@VisibleForTesting
ConcurrentOpenSocketFinder(SocketOpen socketTester,
@Named(TIMEOUT_NODE_RUNNING) Predicate> nodeRunning,
@Named(PROPERTY_USER_THREADS) ListeningExecutorService userExecutor) {
this.socketTester = checkNotNull(socketTester, "socketTester");
this.nodeRunning = checkNotNull(nodeRunning, "nodeRunning");
this.userExecutor = listeningDecorator(checkNotNull(userExecutor, "userExecutor"));
}
@Override
public HostAndPort findOpenSocketOnNode(NodeMetadata node, final int port, long timeout, TimeUnit timeUnits) {
ImmutableSet sockets = checkNodeHasIps(node, allowedInterfaces).transform(new Function() {
@Override
public HostAndPort apply(String from) {
return HostAndPort.fromParts(from, port);
}
}).toSet();
// Specify a retry period of 1s, expressed in the same time units.
long period = timeUnits.convert(1, TimeUnit.SECONDS);
// For retrieving the socket found (if any)
AtomicReference result = newReference();
Predicate> findOrBreak = or(updateRefOnSocketOpen(result), throwISEIfNoLongerRunning(node));
logger.debug(">> blocking on sockets %s for %d %s", sockets, timeout, timeUnits);
boolean passed = retryPredicate(findOrBreak, timeout, period, timeUnits).apply(sockets);
if (passed) {
logger.debug("<< socket %s opened", result);
assert result.get() != null;
return result.get();
} else {
logger.warn("<< sockets %s didn't open after %d %s", sockets, timeout, timeUnits);
throw new NoSuchElementException(format("could not connect to any ip address port %d on node %s", port, node));
}
}
@VisibleForTesting
protected Predicate retryPredicate(Predicate findOrBreak, long timeout, long period, TimeUnit timeUnits) {
return retry(findOrBreak, timeout, period, timeUnits);
}
/**
* Checks if any any of the given HostAndPorts are reachable. It checks them
* all concurrently, and sets reference to a {@link HostAndPort} if found or
* returns false;
*/
private Predicate> updateRefOnSocketOpen(final AtomicReference reachableSocket) {
return new Predicate>() {
@Override
public boolean apply(Iterable input) {
Builder> futures = ImmutableList.builder();
for (final HostAndPort socket : input) {
futures.add(userExecutor.submit(new Runnable() {
@Override
public void run() {
try {
if (socketTester.apply(socket)) {
// only set if the this socket was found first
reachableSocket.compareAndSet(null, socket);
}
} catch (RuntimeException e) {
logger.warn(e, "Error checking reachability of ip:port %s", socket);
}
}
}));
}
blockOn(futures.build());
return reachableSocket.get() != null;
}
@Override
public String toString() {
return "setAndReturnTrueIfSocketFound()";
}
};
}
/**
* Add this via
* {@code Predicates.or(condition, throwISEIfNoLongerRunning(node))} to
* short-circuit {@link RetryablePredicate} looping when the node is no
* longer running.
*/
private Predicate throwISEIfNoLongerRunning(final NodeMetadata node) {
return new Predicate() {
@Override
public boolean apply(T input) {
if (!nodeRunning.apply(newReference(node))) {
throw new IllegalStateException(node.getId() + " is no longer running; aborting socket open loop");
}
return false;
}
@Override
public String toString() {
return "throwISEIfNoLongerRunning(" + node.getId() + ")";
}
};
}
@VisibleForTesting
static FluentIterable checkNodeHasIps(NodeMetadata node, AllowedInterfaces allowedInterfaces) {
ImmutableSet.Builder ipsBuilder = ImmutableSet.builder();
if (allowedInterfaces.scanPublic) {
ipsBuilder.addAll(node.getPublicAddresses());
}
if (allowedInterfaces.scanPrivate) {
ipsBuilder.addAll(node.getPrivateAddresses());
}
ImmutableSet ips = ipsBuilder.build();
checkState(!ips.isEmpty(), "node does not have IP addresses configured: %s", node);
return FluentIterable.from(ips);
}
private static void blockOn(Iterable> immutableList) {
try {
Futures.allAsList(immutableList).get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw propagate(e);
} catch (ExecutionException e) {
throw propagate(e);
}
}
@VisibleForTesting
enum AllowedInterfaces {
ALL(true, true),
PUBLIC(true, false),
PRIVATE(false, true);
private final boolean scanPublic;
private final boolean scanPrivate;
private AllowedInterfaces(boolean scanPublic, boolean scanPrivate) {
this.scanPublic = scanPublic;
this.scanPrivate = scanPrivate;
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy