FileDocCategorySizeDatePackage
RoundRobinPolicy.javaAPI DocGlassfish v2 API16106Fri May 04 22:32:54 BST 2007com.sun.appserv.naming

RoundRobinPolicy.java

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 * 
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 * 
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 * 
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 * 
 * Contributor(s):
 * 
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

package com.sun.appserv.naming;

import java.util.logging.Level;
import com.sun.logging.LogDomains;
import java.util.logging.Logger;

import java.util.LinkedList;
import java.util.List;
import java.util.Vector;
import java.net.InetAddress;

import java.net.MalformedURLException;
import java.net.UnknownHostException;

import com.sun.jndi.cosnaming.IiopUrl;
import com.sun.corba.ee.spi.folb.ClusterInstanceInfo;
import com.sun.corba.ee.spi.folb.SocketInfo;
import com.sun.enterprise.util.ORBManager;

/**
 * The list of endpoints are randomized the very first time.
 *  This happens only once( when called from the static block
 * of S1ASCtxFactory class).
 * Simple RoundRobin is a special case of Weighted Round Robin where the
 * weight per endpoint is equal.With the dynamic reconfiguration 
 * implementation, the endpoints list willhave the following structure:
 * - server_identifier (a stringified name for the machine)
 * - weight- list of SocketInfo {type (type = CLEAR_TEXT or SSL) + 
 *         IP address + port }
 * The above structure supports multi-homed machines 
 * i.e. one machinehosting multiple IP addresses.
 * The RoundRobinPolicy class can be the class that is also implementing
 * the Listener interface for listening to events generated whenever there
 * is a change in the cluster shape. The listener/event design is still
 * under construction.This list of endpoints will have to be created during 
 * bootstrapping(i.e. when the client first starts up.) This list will comprise
 * of theendpoints specified by the user in "com.sun.appserv.iiop.endpoints"
 * property. We can assume a default weight for these endpoints (e.g 10).
 * This list will be used to make the first lookup call. During the first 
 * lookup call, the actual list of endpoints will beprovided back. 
 * Then on, whenever there is any change in the clustershape, 
 * the listener will get the updated list of endpoints from theserver.
 * The implementation for choosing the endpoint from the list of endpoints
 * is as follows:Let's assume 4 endpoints:A(wt=10), B(wt=30), C(wt=40), 
 * D(wt=20). 
 * Using the Random API, generate a random number between 1 and10+30+40+20.
 * Let's assume that the above list is randomized. Based on the weights, we
 * have intervals as follows:
 * 1-----10 (A's weight)
 * 11----40 (A's weight + B's weight)
 * 41----80 (A's weight + B's weight + C's weight)
 * 81----100(A's weight + B's weight + C's weight + C's weight)
 * Here's the psuedo code for deciding where to send the request:
 * if (random_number between 1 & 10) {send request to A;}
 * else if (random_number between 11 & 40) {send request to B;}
 * else if (random_number between 41 & 80) {send request to C;}
 * else if (random_number between 81 & 100) {send request to D;}
 * For simple Round Robin, we can assume the same weight for all endpointsand 
 * perform the above.
 * @author Sheetal Vartak
 * @date 8/2/05
 **/

public class RoundRobinPolicy {

     private List<ClusterInstanceInfo> endpointsList = 
	 new LinkedList<ClusterInstanceInfo>();

    private static Logger _logger = LogDomains.getLogger(
				    LogDomains.JNDI_LOGGER);

    private static java.util.Random rand = new java.util.Random();

    private int sumOfAllWeights = 0;

    private static final int default_weight = 10;

    //called during bootstrapping
    public RoundRobinPolicy(String[] list) {        
	setClusterInstanceInfo(list);
    }

    //will be called after dynamic reconfig
    public synchronized void setClusterInstanceInfo(List<ClusterInstanceInfo> list) {
	sumOfAllWeights = 0;
	endpointsList = list;

	String policy = System.getProperty(S1ASCtxFactory.LOAD_BALANCING_PROPERTY);
	boolean isWeighted;
	if (policy == null) {
	    //default is ic-based
	    policy = S1ASCtxFactory.IC_BASED;
	}
	if (policy.equals(S1ASCtxFactory.IC_BASED_WEIGHTED)) {
	    isWeighted = true;
	} else if (policy.equals(S1ASCtxFactory.IC_BASED)) {
	    isWeighted = false;
	} else {
	    isWeighted = false;
	    _logger.warning("loadbalancing.policy.incorrect");
	}	
	_logger.fine("isWeighted = " + isWeighted);
	//make the weights of all endpoints = default_weight
	for (ClusterInstanceInfo endpoint : endpointsList) {
	    if (! isWeighted) {
		endpoint.weight = default_weight;
	    }
	    _logger.info("endpoint.weight after checking isWeight = " + endpoint.weight);
	    sumOfAllWeights += endpoint.weight;
	} 
	_logger.info("sumOfAllWeights = " + sumOfAllWeights);
    }
    
    /**
     * add a string array of endpoints to list
     */
    public synchronized void setClusterInstanceInfo(String[] list) {
        String[] newList = null;
	
	//if no endpoints are specified as a system property,
        //then look for JNDI provider url
        //we are not looking for the host:port sys property
        //because ORBManager assumes default values of localhost 
	//and 3700
        //localhost is an issue since it gives back both IPv4 and
	// IPv6 addresses
        //3700 is a problem since it is specifically used as a DAS port. 
        //If resources are deployed to a cluster, they are not available
	// to DAS. 
        //So if the DAS host:port is used, 
        //then it will definitely result in a NameNotFoundException
	if (list != null && list.length > 0) {
	    newList = getAddressPortList(list);
	} else {
	    newList = getEndpointForProviderURL(
	  System.getProperty(ORBManager.JNDI_PROVIDER_URL_PROPERTY));
	}
	//randomize the list before adding it to linked list
	if (newList != null && newList.length > 0) {
	    String[] new_list = randomize(newList);
	    List<ClusterInstanceInfo> targetServerList = 
		new LinkedList <ClusterInstanceInfo> ();
	    for (int i = 0; i < new_list.length; i++) {
		if (notDuplicate(new_list[i])) {
		    targetServerList.add(
					 makeClusterInstanceInfo(
								 new_list[i], 
								 default_weight));
		}
	    }
	    if (!targetServerList.isEmpty()) {
		targetServerList.addAll(endpointsList);
		setClusterInstanceInfo(targetServerList); 
	    }
	} else {
	    _logger.log(Level.WARNING,"no.endpoints");	 
	}
    }   

    /**
     * during bootstrapping, weight is assumed "10" for all endpoints
     * then on, whenever server sends updates list,
     * create the list again here with right weights
     */
    private ClusterInstanceInfo makeClusterInstanceInfo(String str, int weight) {
	String[] host_port = str.split(":");
	String server_identifier = ""; //for bootstrapping, can be ""
	String type = "CLEAR_TEXT"; //will be clear_text for bootstrapping
	SocketInfo socketInfo = new SocketInfo(type,
					       host_port[0],
					       new Integer(host_port[1]).intValue());
	ClusterInstanceInfo instanceInfo = 
	    new ClusterInstanceInfo(server_identifier, 
				    weight,
				    new SocketInfo[]{socketInfo});
	return instanceInfo;
	
    }

    /**
    * find whether the string argument is already 
    * present in the endpointsList vector
    */
    private synchronized boolean notDuplicate(String str) {
	String[] host_port = str.split(":");
	for (ClusterInstanceInfo endpoint : endpointsList) {
	    ClusterInstanceInfo instanceInfo = endpoint;
	    for(int j = 0; j < instanceInfo.endpoints.length; j++) {
		if (instanceInfo.endpoints[j].host.equals(host_port[0]) && 
		    instanceInfo.endpoints[j].port == 
		    new Integer(host_port[1]).intValue()) {
		    return false;
		}
	    }
	}
	return true;
    }
    
    /**
     * This method checks for other ways of specifying endpoints
     * namely JNDI provider url 
     * orb host:port is used only if even env passed into 
     * getInitialContext is empty. This check is performed in 
     * S1ASCtxFactory.getInitialContext()
     */
    public String[] getEndpointForProviderURL(String providerURLString) {
     	String[] newList = null;
	if (providerURLString != null) {
	    try {
		IiopUrl providerURL = new IiopUrl(providerURLString);
		newList = getAddressPortList(providerURL);	
		_logger.log(Level.WARNING, "no.endpoints.selected.provider", 
			    new Object[] {providerURLString});
	    } catch (MalformedURLException me) {
		_logger.log(Level.WARNING, "provider.exception", 
			    new Object[] {me.getMessage(), 
					  providerURLString});
	    }	    
	} 
	return newList;
    }
    
    /**
     * randomize the list
     */
    public String[] randomize(String[] list) {
        //randomise the list to enable loadbalancing
	String[] randomizedList = new String[list.length];
        for (int i = 0; i < list.length; i++) {
	    int random;
	    do {		
	        random = rand.nextInt(list.length);
		_logger.fine("random ==> " + random);
	    } while (list[random] == null);
	    randomizedList[i] = list[random];
	    _logger.fine("randomisedList[" + i + "] ==> " + 
			 randomizedList[i]);
	    list[random] = null;
	}
	return randomizedList;
    }

   /**
    * return true of endpoints list is empty
    */
      public synchronized boolean isEmpty() {
	if (endpointsList.size() == 0)
	    return true;
	else return false;
      }


    /**
     * get a new shape of the endpoints
     * For e.g. if list contains A,B,C
     * if the logic below chooses B as the endpoint to send the req to
     * then return B,C,A.
     * logic used is as described in Class description comments
     */
    public synchronized Object[] getNextRotation() {
	int lowerLimit = 0; //lowerLimit
	int random = 0;
	//make sure that the random # is not 0
	//Random API gives a number between 0 and sumOfAllWeights
	//But our range intervals are from 1-upperLimit, 
	//11-upperLimit and so
	//on. Hence we dont want random # to be 0.
	_logger.fine("RoundRobinPolicy.getNextRotation -> sumOfAllWeights = " + sumOfAllWeights);
	while( random == 0) {
	    random = rand.nextInt(sumOfAllWeights);
	    if ( random != 0) {
		break;
	    }
	}
	_logger.fine("getNextRotation : random # = " + random + 
		     " sum of all weights = " + sumOfAllWeights);
	int i = 0;
	for (ClusterInstanceInfo endpoint : endpointsList) {
	    int upperLimit = lowerLimit + endpoint.weight;	    
	    _logger.fine("upperLimit = " + upperLimit);
	    if (random > lowerLimit && random <= upperLimit) {
		List<ClusterInstanceInfo> instanceInfo = 
		    new LinkedList<ClusterInstanceInfo>();
		
		//add the sublist at index 0 
		instanceInfo.addAll(0, 
				    endpointsList.subList(i, 
							  endpointsList.size()));
		//add the remaining list
		instanceInfo.addAll(endpointsList.subList(0, i));
		
		//print the contents...
		_logger.fine("returning the following list..." + 
			     instanceInfo.toString());
		
		return convertIntoCorbaloc(instanceInfo);
	    }
	    lowerLimit = upperLimit;
	    _logger.fine("lowerLimit = " + lowerLimit);	
	    i++;    
	}
	_logger.warning("Could not find an endpoint to send request to!");
	return null;
    }
    
    private Object[] convertIntoCorbaloc(List<ClusterInstanceInfo> list) {
	List host_port = new LinkedList();
	for (ClusterInstanceInfo endpoint : list) {
	    SocketInfo[] socketInfo = endpoint.endpoints;
	    for (int j = 0; j < socketInfo.length; j++) {
		if (!host_port.contains(socketInfo[j].host.trim() +
					":" + socketInfo[j].port)) {
		    host_port.add(socketInfo[j].host.trim() + 
				  ":" + socketInfo[j].port);
		}
	    }
	}
	return host_port.toArray();
    }


 

  /**
   * following methods (over-loaded) for getting all IP addresses 
   * corresponding to a particular host.
   * (multi-homed hosts). 
   */
    private String [] getAddressPortList(String [] hostPortList) {
        // The list is assumed to contain <HOST NAME>:<PORT> values
        Vector addressPortVector = new Vector();
        for (int i=0; i<hostPortList.length; i++) {
            try {
                IiopUrl url = new IiopUrl("iiop://"+hostPortList[i]);
                String [] apList = getAddressPortList(url);
                for (int j=0; j<apList.length; j++) {
                    addressPortVector.addElement(apList[j]);
                }
            } catch (MalformedURLException me) {
                _logger.log(Level.WARNING, "bad.host.port", 
			    new Object[] {hostPortList[i],
					  me.getMessage()});
            }
        }
        String [] ret = new String[addressPortVector.size()];
        for (int i=0; i<ret.length; i++) {
            ret[i] = (String)addressPortVector.elementAt(i);
        }
        // We return a list of <IP ADDRESS>:<PORT> values
        return ret;
    }
    
    private String [] getAddressPortList(IiopUrl iiopUrl) {
        // Pull out the host name and port
        IiopUrl.Address iiopUrlAddress = 
                (IiopUrl.Address)(iiopUrl.getAddresses().elementAt(0));
        String host = iiopUrlAddress.host;
        int portNumber = iiopUrlAddress.port;
        String port = Integer.toString(portNumber);
        // We return a list of <IP ADDRESS>:<PORT> values
        return getAddressPortList(host, port);        
    }
    
    public String [] getAddressPortList(String host, String port) {
        // Get the ip addresses corresponding to the host
        try {
            InetAddress [] addresses = InetAddress.getAllByName(host);
            String[] ret = new String[addresses.length];
            for (int i = 0; i < addresses.length; i++) {
                ret[i] = addresses[i].getHostAddress() + ":" + port;
            }
            // We return a list of <IP ADDRESS>:<PORT> values
            return ret;
        } catch (UnknownHostException ukhe) {
            _logger.log(Level.WARNING, "unknown.host",
			new Object[] {host, ukhe.getMessage()});
            return null;
        }
    }

  /**
   * log list contents
   */
    public synchronized void print() {
        _logger.fine("List contents ==> ");
	int i = 0;
	for (ClusterInstanceInfo endpoint : endpointsList) {
	    ClusterInstanceInfo instanceInfo = endpoint;
	    
	    _logger.fine("endpoint[" + i + "] ==> name =" + 
			 instanceInfo.name + " weight = " + 
			 instanceInfo.weight);
	    for (int j = 0; j < instanceInfo.endpoints.length; j++ ) {
		_logger.fine("IP addresses = " + 
			     instanceInfo.endpoints[j].host + ":" + 
			     instanceInfo.endpoints[j].port  + 
			     " type = " + instanceInfo.endpoints[j].type);
	    }
	    i++;
	}
    }
}