/* * 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.synapse.endpoints; import org.apache.axis2.addressing.EndpointReference; import org.apache.axis2.clustering.ClusteringAgent; import org.apache.axis2.clustering.Member; import org.apache.axis2.clustering.management.DefaultGroupManagementAgent; import org.apache.axis2.context.ConfigurationContext; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.http.protocol.HTTP; import org.apache.synapse.MessageContext; import org.apache.synapse.SynapseConstants; import org.apache.synapse.SynapseException; import org.apache.synapse.aspects.ComponentType; import org.apache.synapse.aspects.flow.statistics.collectors.CloseEventCollector; import org.apache.synapse.aspects.flow.statistics.collectors.OpenEventCollector; import org.apache.synapse.aspects.flow.statistics.collectors.RuntimeStatisticCollector; import org.apache.synapse.core.LoadBalanceMembershipHandler; import org.apache.synapse.core.SynapseEnvironment; import org.apache.synapse.core.axis2.Axis2MessageContext; import org.apache.synapse.core.axis2.Axis2SynapseEnvironment; import org.apache.synapse.core.axis2.ServiceLoadBalanceMembershipHandler; import org.apache.synapse.endpoints.algorithms.LoadbalanceAlgorithm; import org.apache.synapse.endpoints.dispatch.SALSessions; import org.apache.synapse.endpoints.dispatch.SessionInformation; import org.apache.synapse.transport.nhttp.NhttpConstants; import java.util.Collections; import java.util.Map; import java.util.Set; /** * Represents a dynamic load balance endpoint. The application membership is not static, * but discovered through some mechanism such as using a GCF */ public class ServiceDynamicLoadbalanceEndpoint extends DynamicLoadbalanceEndpoint { private static final Log log = LogFactory.getLog(ServiceDynamicLoadbalanceEndpoint.class); /** * Axis2 based membership handler which handles members in multiple clustering domains */ private ServiceLoadBalanceMembershipHandler slbMembershipHandler; /** * Key - host, Value - domain */ private Map<String, String> hostDomainMap; @Override public void init(SynapseEnvironment synapseEnvironment) { if (!initialized) { super.init(synapseEnvironment); ConfigurationContext cfgCtx = ((Axis2SynapseEnvironment) synapseEnvironment).getAxis2ConfigurationContext(); ClusteringAgent clusteringAgent = cfgCtx.getAxisConfiguration().getClusteringAgent(); if (clusteringAgent == null) { throw new SynapseException("Axis2 ClusteringAgent not defined in axis2.xml"); } // Add the Axis2 GroupManagement agents for (String domain : hostDomainMap.values()) { if (clusteringAgent.getGroupManagementAgent(domain) == null) { clusteringAgent.addGroupManagementAgent(new DefaultGroupManagementAgent(), domain); } } slbMembershipHandler = new ServiceLoadBalanceMembershipHandler(hostDomainMap, getAlgorithm(), cfgCtx, isClusteringEnabled, getName()); // Initialize the SAL Sessions if already has not been initialized. SALSessions salSessions = SALSessions.getInstance(); if (!salSessions.isInitialized()) { salSessions.initialize(isClusteringEnabled, cfgCtx); } initialized = true; log.info("ServiceDynamicLoadbalanceEndpoint initialized"); } } public ServiceDynamicLoadbalanceEndpoint(Map<String, String> hostDomainMap, LoadbalanceAlgorithm algorithm) { this.hostDomainMap = hostDomainMap; setAlgorithm(algorithm); } public LoadBalanceMembershipHandler getLbMembershipHandler() { return slbMembershipHandler; } public Map<String, String> getHostDomainMap() { return Collections.unmodifiableMap(hostDomainMap); } public void send(MessageContext synCtx) { if (RuntimeStatisticCollector.isStatisticsEnabled()) { Integer currentIndex = null; boolean retry = (synCtx.getProperty(SynapseConstants.LAST_ENDPOINT) != null); if ((getDefinition() != null) && !retry) { currentIndex = OpenEventCollector.reportChildEntryEvent(synCtx, getReportingName(), ComponentType.ENDPOINT, getDefinition().getAspectConfiguration(), true); } try { sendMessage(synCtx); } finally { if (currentIndex != null) { CloseEventCollector.closeEntryEvent(synCtx, getReportingName(), ComponentType.MEDIATOR, currentIndex, false); } } } else { sendMessage(synCtx); } } private void sendMessage(MessageContext synCtx) { logSetter(); setCookieHeader(synCtx); //TODO: Refactor Session Aware LB dispatching code // Check whether a valid session for session aware dispatching is available Member currentMember = null; SessionInformation sessionInformation = null; if (isSessionAffinityBasedLB()) { // first check if this session is associated with a session. if so, get the endpoint // associated for that session. sessionInformation = (SessionInformation) synCtx.getProperty( SynapseConstants.PROP_SAL_CURRENT_SESSION_INFORMATION); currentMember = (Member) synCtx.getProperty( SynapseConstants.PROP_SAL_ENDPOINT_CURRENT_MEMBER); if (sessionInformation == null && currentMember == null) { sessionInformation = dispatcher.getSession(synCtx); if (sessionInformation != null) { if (log.isDebugEnabled()) { log.debug("Current session id : " + sessionInformation.getId()); } currentMember = sessionInformation.getMember(); synCtx.setProperty( SynapseConstants.PROP_SAL_ENDPOINT_CURRENT_MEMBER, currentMember); // This is for reliably recovery any session information if while response is getting , // session information has been removed by cleaner. // This will not be a cost as session information a not heavy data structure synCtx.setProperty( SynapseConstants.PROP_SAL_CURRENT_SESSION_INFORMATION, sessionInformation); } } } // Dispatch request the relevant member String targetHost = getTargetHost(synCtx); ConfigurationContext configCtx = ((Axis2MessageContext) synCtx).getAxis2MessageContext().getConfigurationContext(); if (slbMembershipHandler.getConfigurationContext() == null) { slbMembershipHandler.setConfigurationContext(configCtx); } ServiceDynamicLoadbalanceFaultHandlerImpl faultHandler = new ServiceDynamicLoadbalanceFaultHandlerImpl(); faultHandler.setHost(targetHost); setupTransportHeaders(synCtx); if (sessionInformation != null && currentMember != null) { //send message on current session sessionInformation.updateExpiryTime(); sendToApplicationMember(synCtx, currentMember, faultHandler, false); } else { // prepare for a new session currentMember = slbMembershipHandler.getNextApplicationMember(targetHost); if (currentMember == null) { String msg = "No application members available"; log.error(msg); throw new SynapseException(msg); } sendToApplicationMember(synCtx, currentMember, faultHandler, true); } } private String getTargetHost(MessageContext synCtx) { org.apache.axis2.context.MessageContext axis2MessageContext = ((Axis2MessageContext) synCtx).getAxis2MessageContext(); Map<String, String> headers = (Map<String, String>) axis2MessageContext. getProperty(org.apache.axis2.context.MessageContext.TRANSPORT_HEADERS); String address = headers.get(HTTP.TARGET_HOST); synCtx.setProperty("LB_REQUEST_HOST", address); // Need to set with the port if (address.contains(":")) { address = address.substring(0, address.indexOf(":")); } return address; } /** * This FaultHandler will try to resend the message to another member if an error occurs * while sending to some member. This is a failover mechanism */ private class ServiceDynamicLoadbalanceFaultHandlerImpl extends DynamicLoadbalanceFaultHandler { private EndpointReference to; private Member currentMember; private Endpoint currentEp; private String host; private static final int MAX_RETRY_COUNT = 5; // ThreadLocal variable to keep track of how many times this fault handler has been // called private ThreadLocal<Integer> callCount = new ThreadLocal<Integer>() { protected Integer initialValue() { return 0; } }; public void setHost(String host) { this.host = host; } public void setCurrentMember(Member currentMember) { this.currentMember = currentMember; } public void setTo(EndpointReference to) { this.to = to; } private ServiceDynamicLoadbalanceFaultHandlerImpl() { } public void onFault(MessageContext synCtx) { if (currentMember == null) { return; } currentMember.suspend(10000); // TODO: Make this configurable. log.info("Suspended member " + currentMember + " for 10s"); // Prevent infinite retrying to failed members callCount.set(callCount.get() + 1); if (callCount.get() >= MAX_RETRY_COUNT) { return; } //cleanup endpoint if exists if (currentEp != null) { currentEp.destroy(); } Integer errorCode = (Integer) synCtx.getProperty(SynapseConstants.ERROR_CODE); if (errorCode != null) { if (errorCode.equals(NhttpConstants.CONNECTION_FAILED) || errorCode.equals(NhttpConstants.CONNECT_CANCEL) || errorCode.equals(NhttpConstants.CONNECT_TIMEOUT)) { // Try to resend to another member Member newMember = slbMembershipHandler.getNextApplicationMember(host); if (newMember == null) { String msg = "No application members available"; log.error(msg); throw new SynapseException(msg); } log.info("Failed over to " + newMember); synCtx.setTo(to); if (isSessionAffinityBasedLB()) { //We are sending the this message on a new session, // hence we need to remove previous session information Set pros = synCtx.getPropertyKeySet(); if (pros != null) { pros.remove(SynapseConstants.PROP_SAL_CURRENT_SESSION_INFORMATION); } } try { Thread.sleep(1000); // Sleep for sometime before retrying } catch (InterruptedException ignored) { } sendToApplicationMember(synCtx, newMember, this, true); } else if (errorCode.equals(NhttpConstants.SND_IO_ERROR_SENDING) || errorCode.equals(NhttpConstants.CONNECTION_CLOSED)) { // TODO: Envelope is consumed } } // We cannot failover since we are using binary relay } public void setCurrentEp(Endpoint currentEp) { this.currentEp = currentEp; } } }