/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright (c) 2006-2011 Oracle and/or its affiliates. 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_1_1.html * or packager/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 packager/legal/LICENSE.txt. * * GPL Classpath Exception: * Oracle designates this particular file as subject to the "Classpath" * exception as provided by Oracle in the GPL Version 2 section of the License * file that accompanied this code. * * Modifications: * If applicable, add the following below the License Header, with the fields * enclosed by brackets [] replaced by your own identifying information: * "Portions Copyright [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.enterprise.naming.impl; 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.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.config.serverbeans.Cluster; import java.net.Inet4Address; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import org.glassfish.internal.api.ORBLocator; /** * The list of endpoints are randomized the very first time. * This happens only once( when called from the static block * of SerialInitContextFactory 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 { // Each SocketInfo.type() must either start with SSL, or be CLEAR_TEXT private static final String SSL = "SSL" ; private static final String CLEAR_TEXT = "CLEAR_TEXT" ; private static final Logger _logger = LogDomains.getLogger( RoundRobinPolicy.class, LogDomains.JNDI_LOGGER); private static java.util.Random rand = new java.util.Random(); private List endpointsList = new LinkedList(); private int totalWeight = 0; private List resolvedEndpoints ; private static final int default_weight = 10; private static void warnLog( String fmt, Object... args ) { doLog( Level.WARNING, fmt, args ) ; } private static void infoLog( String fmt, Object... args ) { doLog( Level.INFO, fmt, args ) ; } private static void fineLog( String fmt, Object... args ) { doLog( Level.FINE, fmt, args ) ; } private static void doLog( Level level, String fmt, Object... args ) { if (_logger.isLoggable( level )) { _logger.log(level, fmt, args ); } } //called during bootstrapping public RoundRobinPolicy(List list) { setClusterInstanceInfoFromString(list); } // Copy list, changing any type that does not start with SSL to CLEAR_TEXT. private List filterSocketInfos( List sis ) { final List result = new ArrayList() ; for (SocketInfo si : sis) { final String newType = si.type().startsWith(SSL) ? si.type() : CLEAR_TEXT ; final SocketInfo siCopy = new SocketInfo( newType, si.host(), si.port() ) ; result.add( siCopy ) ; } return result ; } private boolean isWeighted() { String policy = System.getProperty( SerialInitContextFactory.LOAD_BALANCING_PROPERTY, SerialInitContextFactory.IC_BASED ); if (!policy.equals(SerialInitContextFactory.IC_BASED)) { warnLog("loadbalancing.polibeforecy.incorrect"); } final boolean isw = policy.equals( SerialInitContextFactory.IC_BASED_WEIGHTED); return isw ; } private List filterClusterInfo( List info ) { boolean isw = isWeighted() ; ArrayList newList = new ArrayList() ; totalWeight = 0 ; for (ClusterInstanceInfo clinfo : info) { final int newWeight = isw ? clinfo.weight() : default_weight ; final List newEndpoints = filterSocketInfos( clinfo.endpoints() ) ; final ClusterInstanceInfo newClinfo = new ClusterInstanceInfo( clinfo.name(), newWeight, newEndpoints ) ; newList.add( newClinfo ) ; totalWeight += newWeight ; } return newList ; } private boolean containsMatchingAddress( List list, String host, int port ) { for (ClusterInstanceInfo info : list) { for (SocketInfo si : info.endpoints()) { if (si.type().equals( CLEAR_TEXT )) { if (si.host().equals( host) && si.port() == port) { return true ; } } } } return false ; } // Add those elements of the second list that do not contain a clear // text address that appears in the first list. private List merge( List first, List second ) { List result = new ArrayList() ; result.addAll( first ) ; for (ClusterInstanceInfo info : second) { for (SocketInfo si : info.endpoints()) { if (!containsMatchingAddress(first, si.host(), si.port() )) { result.add( info ) ; } } } return result ; } private List fromHostPortStrings( List list ) { List result = new LinkedList (); for( String elem : list) { ClusterInstanceInfo info = makeClusterInstanceInfo( elem, default_weight ) ; result.add( info ); } return result ; } //will be called after dynamic reconfig // used in GroupInfoServiceObserverImpl synchronized final void setClusterInstanceInfo( List list) { fineLog( "setClusterInstanceInfo: list={0}", list ) ; List filtered = filterClusterInfo(list) ; List resolved = fromHostPortStrings( resolvedEndpoints ) ; endpointsList = merge( filtered, resolved ) ; } // Note: regard any addresses supplied here as a permanent part of the // cluster. synchronized final void setClusterInstanceInfoFromString( List list) { fineLog( "setClusterInstanceInfoFromString: list={0}", list ) ; List newList = list; if (newList.isEmpty()) { newList = getEndpointForProviderURL( System.getProperty(ORBLocator.JNDI_PROVIDER_URL_PROPERTY)); } //randomize the list before adding it to linked list if (!newList.isEmpty()) { List newList2 = randomize(newList); resolvedEndpoints = new ArrayList( newList2 ) ; endpointsList = fromHostPortStrings(newList2) ; // Put in a default for total weight; any update will correct this. totalWeight = 10*endpointsList.size() ; } else { fineLog( "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], Integer.parseInt( host_port[1]) ); List sil = new ArrayList(1) ; sil.add( socketInfo ) ; ClusterInstanceInfo instanceInfo = new ClusterInstanceInfo( server_identifier, weight, sil ) ; return instanceInfo; } /* * 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 * SerialInitContextFactory.getInitialContext() */ public List getEndpointForProviderURL(String providerURLString) { if (providerURLString != null) { try { final IiopUrl providerURL = new IiopUrl(providerURLString); final List newList = getAddressPortList(providerURL); warnLog( "no.endpoints.selected.provider", providerURLString ); return newList ; } catch (MalformedURLException me) { warnLog( "provider.exception", me.getMessage(), providerURLString); } } return new ArrayList() ; } /** * randomize the list. Note: this empties its argument. */ private List randomize( List list ) { List result = new ArrayList( list.size() ) ; while (!list.isEmpty()) { int random = rand.nextInt( list.size() ) ; String elem = list.remove( random ) ; result.add( elem ) ; } fineLog( "Randomized list {0}", result ) ; return result ; } /* * 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 List 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. // fineLog( "RoundRobinPolicy.getNextRotation -> sumOfAllWeights = {0}", // totalWeight); while( random == 0) { random = rand.nextInt(totalWeight); if ( random != 0) { break; } } // fineLog( "getNextRotation : random # = {0} sum of all weights = {1}", // new Object[]{random, totalWeight}); int i = 0; for (ClusterInstanceInfo endpoint : endpointsList) { int upperLimit = lowerLimit + endpoint.weight(); // fineLog( "upperLimit = {0}", upperLimit); if (random > lowerLimit && random <= upperLimit) { List instanceInfo = new LinkedList(); //add the sublist at index 0 instanceInfo.addAll(0, endpointsList.subList(i, endpointsList.size())); //add the remaining list instanceInfo.addAll(endpointsList.subList(0, i)); endpointsList = instanceInfo ; //print the contents... fineLog( "getNextRotation: result={0}", instanceInfo.toString()); return convertIntoCorbaloc(instanceInfo); } lowerLimit = upperLimit; // fineLog( "lowerLimit = {0}", lowerLimit); i++; } warnLog("Could not find an endpoint to send request to!"); return new ArrayList() ; } private List convertIntoCorbaloc(List list) { List host_port = new ArrayList(); for (ClusterInstanceInfo endpoint : list) { List sinfos = endpoint.endpoints(); for (SocketInfo si : sinfos ) { // XXX this needs to be revised if we ever do a secure // bootstrap protocol for the initial corbaloc URL resolution if (si.type().equals( CLEAR_TEXT )) { String element = si.host().trim() + ":" + si.port() ; if (!host_port.contains( element )) { host_port.add( element ) ; } } } } return host_port ; } /** * following methods (over-loaded) for getting all IP addresses * corresponding to a particular host. * (multi-homed hosts). */ private List getAddressPortList(List hostPortList) { // The list is assumed to contain : values List addressPortVector = new ArrayList(); for (String str : hostPortList) { try { IiopUrl url = new IiopUrl("iiop://" + str); List apList = getAddressPortList(url); addressPortVector.addAll(apList); } catch (MalformedURLException me) { warnLog( "bad.host.port", str, me.getMessage() ); } } return addressPortVector ; } private List 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 : values return getAddressPortList(host, port); } public List getAddressPortList(String host, String port) { // Get the ip addresses corresponding to the host. // XXX this currently does NOT support IPv6. try { InetAddress [] addresses = InetAddress.getAllByName(host); List addrs = new ArrayList() ; for (InetAddress addr : addresses) { if (addr instanceof Inet4Address) { addrs.add( addr ) ; } } List ret = new ArrayList() ; for (InetAddress addr : addrs) { ret.add( addr.getHostAddress() + ":" + port ) ; } // We return a list of : values return ret; } catch (UnknownHostException ukhe) { warnLog( "unknown.host", host, ukhe.getMessage() ); return new ArrayList() ; } } @Override public String toString() { StringBuilder sb = new StringBuilder() ; sb.append( "RoundRobinPolicy[") ; boolean first = true ; for (ClusterInstanceInfo endpoint : endpointsList ) { if (first) { first = false ; } else { sb.append( ' ' ) ; } sb.append( endpoint.toString() ) ; } sb.append( ']' ) ; return sb.toString() ; } public List getHostPortList() { return resolvedEndpoints ; } }