LoadBalancer.java

/*
 * GovWay - A customizable API Gateway
 * https://govway.org
 * 
 * Copyright (c) 2005-2024 Link.it srl (https://link.it). 
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 3, as published by
 * the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 */

package org.openspcoop2.pdd.core.behaviour.built_in.load_balance;

import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.openspcoop2.pdd.core.PdDContext;
import org.openspcoop2.pdd.core.behaviour.BehaviourException;

/**
 * LoadBalancer
 *
 * @author Andrea Poli (apoli@link.it)
 * @author $Author$
 * @version $Rev$, $Date$
 */
public class LoadBalancer {

	private static java.util.Random _rnd = null;
	private static synchronized void initRandomInstance() {
		if(_rnd==null) {
			_rnd = new SecureRandom();
		}
	}
	public static java.util.Random getRandomInstance() {
		if(_rnd==null) {
			initRandomInstance();
		}
		return _rnd;
	}
	
	private PdDContext pddContext;
	private LoadBalancerPool pool;
	private LoadBalancerType type;
	
	public LoadBalancer(LoadBalancerType type, LoadBalancerPool pool, PdDContext pddContext) {
		this.pddContext = pddContext;
		this.type = type;
		this.pool = pool;
	}
	
	public String selectConnector() throws BehaviourException {
		switch (this.type) {
		case ROUND_ROBIN:
			return getRoundRobin();
		case WEIGHT_ROUND_ROBIN:
			return getWeightRoundRobin();
		case RANDOM:
			return getRandom();
		case WEIGHT_RANDOM:
			return getWeightRandom();
		case IP_HASH:
			return getIpSourceHash();
		case LEAST_CONNECTIONS:
			return getLeastConnections();
		}
		
		throw new BehaviourException("Type '"+this.type+"' unknown");
	}

	private String getRoundRobin() throws BehaviourException {
		Set<String> servers = this.pool.getConnectorNames(false); // il passive health check viene effettuato dentro il metodo nextPosition
		List<String> serverList = new ArrayList<>();
		serverList.addAll(servers);
		int position = this.pool.getNextPosition(false);
		String target = serverList.get(position);
		return target;
	}
	private String getWeightRoundRobin() throws BehaviourException {
		List<String> serverList = this.pool.getWeightList(false); // il passive health check viene effettuato dentro il metodo nextPosition
		int position = this.pool.getNextPosition(true);
		String target = serverList.get(position);
		return target;
	}


	private String getRandom() throws BehaviourException {
		Set<String> servers = this.pool.getConnectorNames(true);
		if(servers.isEmpty()) {
			throw new BehaviourException("Nessun connettore selezionabile (passive health check)");
		}
		List<String> serverList = new ArrayList<>();
		serverList.addAll(servers);
		int randomIndex = getRandomInstance().nextInt(serverList.size());
		String target = serverList.get(randomIndex);
		return target;
	}
	private String getWeightRandom() throws BehaviourException {
		List<String> serverList = this.pool.getWeightList(true);
		Integer index = getRandomInstance().nextInt(serverList.size());
		String target = serverList.get(index);
		return target;
	}

	public static String getIpSourceFromContet(PdDContext pddContext) {
		Object oIpAddressRemote = pddContext.getObject(org.openspcoop2.core.constants.Costanti.CLIENT_IP_REMOTE_ADDRESS);
		String ipAddressRemote = null;
		if(oIpAddressRemote!=null && (oIpAddressRemote instanceof String)){
			ipAddressRemote = (String)oIpAddressRemote;
		}
		if (ipAddressRemote == null) {
			ipAddressRemote = "127.0.0.1";
		}
		
		Object oIpAddressTransport = pddContext.getObject(org.openspcoop2.core.constants.Costanti.CLIENT_IP_TRANSPORT_ADDRESS);
		String ipAddressTransport = null;
		if(oIpAddressTransport!=null && (oIpAddressTransport instanceof String)){
			ipAddressTransport = (String)oIpAddressTransport;
		}
		if (ipAddressTransport == null) {
			ipAddressTransport = "-";
		}
		
		String clientIp = ipAddressRemote+" "+ipAddressTransport;
		
		return clientIp;
	}
	
	private String getIpSourceHash() throws BehaviourException {
		
		String clientIp = getIpSourceFromContet(this.pddContext);
		
		Set<String> servers = this.pool.getConnectorNames(false);
		List<String> serverList = new ArrayList<>();
		serverList.addAll(servers);
		String remoteId = clientIp;
		int hashCodeCalcolato = remoteId.hashCode();
		if(hashCodeCalcolato == Integer.MIN_VALUE) {
			hashCodeCalcolato = Integer.MIN_VALUE+1; // altrimenti viene negativo l'abs
		}
		int absoluteHashCode = java.lang.Math.abs(hashCodeCalcolato);
		Integer index = absoluteHashCode % serverList.size();
		String target = serverList.get(index);
		
		if(this.pool.isPassiveHealthCheck()) {
			
			Set<String> setAfterPassiveHealthCheck = this.pool.getConnectorNames(true);
			
			// prima verifica
			if(setAfterPassiveHealthCheck.contains(target)) {
				return target;
			}
			
			// controllo prossime posizioni fino a tornare a quella attuale
			int nextPos = index.intValue()+1;
			if(nextPos==serverList.size()) {
				nextPos = 0;
			}
			while(nextPos!=index.intValue()) {
				target = serverList.get(nextPos);
				if(setAfterPassiveHealthCheck.contains(target)) {
					return target;
				}
				nextPos++;
				if(nextPos==serverList.size()) {
					nextPos = 0;
				}
			}
			
			throw new BehaviourException("Nessun connettore selezionabile (passive health check)");
			
		}
		
		return target;
	}
	
	private String getLeastConnections() {
		return this.pool.getNextConnectorLeastConnections();
	}

}