/* * 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.geode.CancelCriterion; import org.apache.geode.CancelException; import org.apache.geode.GemFireConfigException; import org.apache.geode.cache.GatewayConfigurationException; import org.apache.geode.cache.client.ServerRefusedConnectionException; import org.apache.geode.cache.client.internal.ServerBlackList.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.Acceptor; import org.apache.geode.internal.cache.tier.sockets.CacheClientUpdater; import org.apache.geode.internal.cache.tier.sockets.ClientProxyMembershipID; import org.apache.geode.internal.cache.tier.sockets.HandShake; import org.apache.geode.internal.i18n.LocalizedStrings; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.logging.log4j.LocalizedMessage; import org.apache.geode.internal.net.SocketCreator; import org.apache.geode.internal.net.SocketCreatorFactory; import org.apache.geode.internal.security.SecurableCommunicationChannel; import org.apache.geode.security.GemFireSecurityException; import org.apache.logging.log4j.Logger; /** * 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 HandShake handshake; private final int socketBufferSize; private final int handShakeTimeout; private final boolean usedByGateway; private final ServerBlackList blackList; private final CancelCriterion cancelCriterion; private final SocketCreator socketCreator; private ConnectionSource source; private int readTimeout; private InternalDistributedSystem ds; private EndpointManager endpointManager; private GatewaySender gatewaySender; private PoolImpl pool; /** * Test hook for client version support * * @since GemFire 5.7 */ public static boolean testFailedConnectionToServer = false; public 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.handshake = new HandShake(proxyId, sys); this.handshake.setClientReadTimeout(readTimeout); this.source = source; this.endpointManager = endpointManager; this.ds = sys; this.socketBufferSize = socketBufferSize; this.handShakeTimeout = handShakeTimeout; this.handshake.setMultiuserSecureMode(multiuserSecureMode); this.readTimeout = readTimeout; this.usedByGateway = usedByGateway; this.gatewaySender = sender; this.blackList = new ServerBlackList(pingInterval); this.cancelCriterion = cancelCriterion; this.pool = pool; if (this.usedByGateway || (this.gatewaySender != null)) { this.socketCreator = SocketCreatorFactory.getSocketCreatorForComponent(SecurableCommunicationChannel.GATEWAY); if (sender != null && !sender.getGatewayTransportFilters().isEmpty()) { this.socketCreator.initializeTransportFilterClientSocketFactory(sender); } } else { // If configured use SSL properties for cache-server this.socketCreator = SocketCreatorFactory.getSocketCreatorForComponent(SecurableCommunicationChannel.SERVER); } } public void start(ScheduledExecutorService background) { blackList.start(background); } private byte getCommMode(boolean forQueue) { if (this.usedByGateway || (this.gatewaySender != null)) { return Acceptor.GATEWAY_TO_GATEWAY; } else if (forQueue) { return Acceptor.CLIENT_TO_SERVER_FOR_QUEUE; } else { return Acceptor.CLIENT_TO_SERVER; } } public ServerBlackList getBlackList() { return blackList; } public Connection createClientToServerConnection(ServerLocation location, boolean forQueue) throws GemFireSecurityException { ConnectionImpl connection = new ConnectionImpl(this.ds, this.cancelCriterion); FailureTracker failureTracker = blackList.getFailureTracker(location); boolean initialized = false; try { HandShake connHandShake = new HandShake(handshake); connection.connect(endpointManager, location, connHandShake, socketBufferSize, handShakeTimeout, readTimeout, getCommMode(forQueue), this.gatewaySender, this.socketCreator); failureTracker.reset(); connection.setHandShake(connHandShake); authenticateIfRequired(connection); initialized = true; } catch (GemFireConfigException e) { throw e; } catch (CancelException e) { // propagate this up, don't retry throw e; } catch (GemFireSecurityException e) { // propagate this up, don't retry throw e; } catch (GatewayConfigurationException e) { // propagate this up, don't retry throw e; } catch (ServerRefusedConnectionException src) { // propagate this up, don't retry logger.warn(LocalizedMessage.create( LocalizedStrings.AutoConnectionSourceImpl_COULD_NOT_CREATE_A_NEW_CONNECTION_TO_SERVER_0, 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 {// print a warning with the exception stack trace logger.warn(LocalizedMessage .create(LocalizedStrings.ConnectException_COULD_NOT_CONNECT_TO_0, location), e); } testFailedConnectionToServer = true; } finally { if (!initialized) { connection.destroy(); failureTracker.addFailure(); connection = null; } } 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); } } } } } public ServerLocation findBestServer(ServerLocation currentServer, Set excludedServers) { if (currentServer != null && source.isBalanced()) { return currentServer; } final Set origExcludedServers = excludedServers; excludedServers = new HashSet(excludedServers); Set blackListedServers = blackList.getBadServers(); excludedServers.addAll(blackListedServers); ServerLocation server = source.findReplacementServer(currentServer, excludedServers); if (server == null) { // Nothing worked! Let's try without the blacklist. if (excludedServers.size() > origExcludedServers.size()) { // We had some guys black listed 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; } public Connection createClientToServerConnection(Set excludedServers) throws GemFireSecurityException { final Set origExcludedServers = excludedServers; excludedServers = new HashSet(excludedServers); Set blackListedServers = blackList.getBadServers(); excludedServers.addAll(blackListedServers); Connection conn = null; // long startTime = System.currentTimeMillis(); RuntimeException fatalException = null; boolean tryBlackList = true; do { ServerLocation server = source.findServer(excludedServers); if (server == null) { if (tryBlackList) { // Nothing worked! Let's try without the blacklist. tryBlackList = false; int size = excludedServers.size(); excludedServers.removeAll(blackListedServers); // 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 e) { // propagate this up immediately throw e; } catch (GemFireSecurityException e) { // propagate this up immediately throw e; } catch (GatewayConfigurationException e) { // propagate this up immediately throw e; } catch (ServerRefusedConnectionException srce) { fatalException = srce; if (logger.isDebugEnabled()) { logger.debug("ServerRefusedConnectionException attempting to connect to {}", server, srce); } } catch (Exception e) { logger.warn(LocalizedMessage .create(LocalizedStrings.ConnectException_COULD_NOT_CONNECT_TO_0, server), e); } excludedServers.add(server); } while (conn == null); // if(conn == null) { // logger.fine("Unable to create a connection in the allowed time."); // // if(fatalException!=null) { // throw fatalException; // } // } return conn; } 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); } // Launch the thread CacheClientUpdater updater = new CacheClientUpdater(clientUpdateName, endpoint.getLocation(), isPrimary, ds, new HandShake(this.handshake), qManager, endpointManager, endpoint, handShakeTimeout, this.socketCreator); if (!updater.isConnected()) { return null; } updater.setFailedUpdater(failedUpdater); updater.start(); // Wait for the client update thread to be ready // if (!updater.waitForInitialization()) { // Yogesh : This doesn't wait for notify if the updater // thread exits from the run in case of Exception in CCU thread // Yogesh : fix for 36690 // because when CCU thread gets a ConnectException, it comes out of run method // and when a thread is no more running it notifies all the waiting threads on the thread // object. // so above wait will come out irrelevant of notify from CCU thread, when CCU thread has got an // exception // To avoid this problem we check isAlive before returning from this method. // if (logger != null && logger.infoEnabled()) { // logger.info(LocalizedStrings.AutoConnectionSourceImpl_0_NOT_STARTED_1, new Object[] {this, // clientUpdateName}); // } // return null; // }else { // if (logger != null && logger.infoEnabled()) { // logger.info(LocalizedStrings.AutoConnectionSourceImpl_0_STARTED_1, new Object[] {this, // clientUpdateName}); // } // } return updater; } }