blob: 924eaef49ab1c86d2aaf572852b23ffd2333accc [file] [log] [blame]
/*
* 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.geode.cache.client.internal;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import org.apache.logging.log4j.Logger;
import org.apache.geode.CancelCriterion;
import org.apache.geode.CancelException;
import org.apache.geode.GemFireConfigException;
import org.apache.geode.annotations.internal.MutableForTesting;
import org.apache.geode.cache.GatewayConfigurationException;
import org.apache.geode.cache.client.ServerRefusedConnectionException;
import org.apache.geode.cache.client.internal.ServerDenyList.FailureTracker;
import org.apache.geode.cache.wan.GatewaySender;
import org.apache.geode.distributed.internal.InternalDistributedSystem;
import org.apache.geode.distributed.internal.ServerLocation;
import org.apache.geode.internal.cache.tier.sockets.CacheClientUpdater;
import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID;
import org.apache.geode.internal.net.SocketCreatorFactory;
import org.apache.geode.internal.security.SecurableCommunicationChannel;
import org.apache.geode.logging.internal.log4j.api.LogService;
import org.apache.geode.security.GemFireSecurityException;
/**
* Creates connections, using a connection source to determine which server to connect to.
*
* @since GemFire 5.7
*
*/
public class ConnectionFactoryImpl implements ConnectionFactory {
private static final Logger logger = LogService.getLogger();
// TODO - GEODE-1746, the handshake holds state. It seems like the code depends
// on all of the handshake operations happening in a single thread. I don't think we
// want that, need to refactor.
private final ServerDenyList denyList;
private ConnectionSource source;
private PoolImpl pool;
private final CancelCriterion cancelCriterion;
private final ConnectionConnector connectionConnector;
/**
* Test hook for client version support
*
* @since GemFire 5.7
*/
@MutableForTesting
public static boolean testFailedConnectionToServer = false;
ConnectionFactoryImpl(ConnectionSource source, EndpointManager endpointManager,
InternalDistributedSystem sys, int socketBufferSize, int handshakeTimeout, int readTimeout,
ClientProxyMembershipID proxyId, CancelCriterion cancelCriterion, boolean usedByGateway,
GatewaySender sender, long pingInterval, boolean multiuserSecureMode, PoolImpl pool) {
this(
new ConnectionConnector(endpointManager, sys, socketBufferSize, handshakeTimeout,
readTimeout, usedByGateway, sender,
(usedByGateway || sender != null) ? SocketCreatorFactory
.getSocketCreatorForComponent(SecurableCommunicationChannel.GATEWAY)
: SocketCreatorFactory
.getSocketCreatorForComponent(SecurableCommunicationChannel.SERVER),
new ClientSideHandshakeImpl(proxyId, sys, sys.getSecurityService(),
multiuserSecureMode)),
source, pingInterval, pool, cancelCriterion);
}
public ConnectionFactoryImpl(ConnectionConnector connectionConnector, ConnectionSource source,
long pingInterval, PoolImpl pool, CancelCriterion cancelCriterion) {
this.connectionConnector = connectionConnector;
this.source = source;
this.pool = pool;
this.cancelCriterion = cancelCriterion;
denyList = new ServerDenyList(pingInterval);
}
public void start(ScheduledExecutorService background) {
denyList.start(background);
}
@Override
public ServerDenyList getDenyList() {
return denyList;
}
@Override
public Connection createClientToServerConnection(ServerLocation location, boolean forQueue)
throws GemFireSecurityException {
FailureTracker failureTracker = denyList.getFailureTracker(location);
Connection connection = null;
try {
connection = connectionConnector.connectClientToServer(location, forQueue);
failureTracker.reset();
authenticateIfRequired(connection);
} catch (GemFireConfigException | CancelException | GemFireSecurityException
| GatewayConfigurationException e) {
throw e;
} catch (ServerRefusedConnectionException src) {
// propagate this up, don't retry
logger.warn("Could not create a new connection to server: {}",
src.getMessage());
testFailedConnectionToServer = true;
throw src;
} catch (Exception e) {
if (e.getMessage() != null && (e.getMessage().equals("Connection refused")
|| e.getMessage().equals("Connection reset"))) {
// this is the most common case, so don't print an exception
if (logger.isDebugEnabled()) {
logger.debug("Unable to connect to {}: connection refused", location);
}
} else {
logger.warn("Could not connect to: " + location, e);
}
testFailedConnectionToServer = true;
}
return connection;
}
private void authenticateIfRequired(Connection conn) {
cancelCriterion.checkCancelInProgress(null);
if (!pool.isUsedByGateway() && !pool.getMultiuserAuthentication()) {
ServerLocation server = conn.getServer();
if (server.getRequiresCredentials()) {
if (server.getUserId() == -1) {
Long uniqueID = (Long) AuthenticateUserOp.executeOn(conn, pool);
server.setUserId(uniqueID);
if (logger.isDebugEnabled()) {
logger.debug("CFI.authenticateIfRequired() Completed authentication on {}", conn);
}
}
}
}
}
@Override
public ServerLocation findBestServer(ServerLocation currentServer,
Set<ServerLocation> excludedServers) {
if (currentServer != null && source.isBalanced()) {
return currentServer;
}
final Set<ServerLocation> origExcludedServers = excludedServers;
excludedServers = new HashSet<>(excludedServers);
Set<ServerLocation> denyListedServers = denyList.getBadServers();
excludedServers.addAll(denyListedServers);
ServerLocation server = source.findReplacementServer(currentServer, excludedServers);
if (server == null) {
// Nothing worked! Let's try without the denylist.
if (excludedServers.size() > origExcludedServers.size()) {
// We had some servers denylisted so lets give this another whirl.
server = source.findReplacementServer(currentServer, origExcludedServers);
}
}
if (server == null && logger.isDebugEnabled()) {
logger.debug("Source was unable to findForReplacement any servers");
}
return server;
}
@Override
public Connection createClientToServerConnection(Set<ServerLocation> excludedServers)
throws GemFireSecurityException {
final Set<ServerLocation> origExcludedServers = excludedServers;
excludedServers = new HashSet<>(excludedServers);
Set<ServerLocation> denyListedServers = denyList.getBadServers();
excludedServers.addAll(denyListedServers);
Connection conn = null;
RuntimeException fatalException = null;
boolean tryDenyList = true;
do {
ServerLocation server = source.findServer(excludedServers);
if (server == null) {
if (tryDenyList) {
// Nothing worked! Let's try without the denylist.
tryDenyList = false;
int size = excludedServers.size();
excludedServers.removeAll(denyListedServers);
// make sure we didn't remove any of the ones that the caller set not to use
excludedServers.addAll(origExcludedServers);
if (excludedServers.size() < size) {
// We are able to remove some exclusions, so lets give this another whirl.
continue;
}
}
if (logger.isDebugEnabled()) {
logger.debug("Source was unable to locate any servers");
}
if (fatalException != null) {
throw fatalException;
}
return null;
}
try {
conn = createClientToServerConnection(server, false);
} catch (CancelException | GemFireSecurityException | GatewayConfigurationException e) {
throw e;
} catch (ServerRefusedConnectionException srce) {
fatalException = srce;
if (logger.isDebugEnabled()) {
logger.debug("ServerRefusedConnectionException attempting to connect to {}", server,
srce);
}
} catch (Exception e) {
logger.warn(String.format("Could not connect to: %s", server), e);
}
excludedServers.add(server);
} while (conn == null);
return conn;
}
@Override
public ClientUpdater createServerToClientConnection(Endpoint endpoint, QueueManager qManager,
boolean isPrimary, ClientUpdater failedUpdater) {
String clientUpdateName = CacheClientUpdater.CLIENT_UPDATER_THREAD_NAME + " on "
+ endpoint.getMemberId() + " port " + endpoint.getLocation().getPort();
if (logger.isDebugEnabled()) {
logger.debug("Establishing: {}", clientUpdateName);
}
return connectionConnector.connectServerToClient(endpoint, qManager, isPrimary, failedUpdater,
clientUpdateName);
}
}