blob: ca2d950a23b2bf50e84a67c27c4b266010f4361a [file] [log] [blame]
/*
* Copyright (c) 2006, 2018 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.enterprise.iiop.impl;
import static org.glassfish.api.naming.NamingClusterInfo.IC_BASED;
import static org.glassfish.api.naming.NamingClusterInfo.IC_BASED_WEIGHTED;
import static org.glassfish.api.naming.NamingClusterInfo.LOAD_BALANCING_PROPERTY;
import static org.glassfish.enterprise.iiop.impl.NamingClusterInfoImpl.logger;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.UnknownHostException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Level;
import org.glassfish.internal.api.ORBLocator;
import org.glassfish.jndi.cosnaming.IiopUrl;
import org.glassfish.logging.annotation.LogMessageInfo;
import com.sun.corba.ee.spi.folb.ClusterInstanceInfo;
import com.sun.corba.ee.spi.folb.SocketInfo;
/**
* The list of endpoints are randomized the very first time.
* This happens only once( when called from the static block
* of SerialInitContextFactory class).
*
* <p>
* 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 will have the following structure:
*
* <pre>
* {@code
* - server_identifier (a stringified name for the machine)
* - weight- list of SocketInfo {type (type = CLEAR_TEXT or SSL) +
* IP address + port }
* }
* </pre>
*
* <p>
* The above structure supports multi-homed machines
* i.e. one machine hosting multiple IP addresses.
* The <code>RoundRobinPolicy</code> 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 the endpoints specified by the user in "com.sun.appserv.iiop.endpoints"
* property. We can assume a default weight for these endpoints (e.g 10).
*
* <p>
* This list will be used to make the first lookup call. During the first
* lookup call, the actual list of endpoints will be provided back.
* Then on, whenever there is any change in the clustershape,
* the listener will get the updated list of endpoints from the server.
* The implementation for choosing the endpoint from the list of endpoints
* is as follows: Let's assume 4 endpoints: <code>A(wt=10)</code>, <code>B(wt=30)</code>, <code>C(wt=40)</code>,
* <code>D(wt=20)</code>.
*
* <p>
* 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:
*
* <pre>
* {@code
* 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)
* }
* </pre>
*
* Here's the psuedo code for deciding where to send the request:
*
* <pre>
* {@code
* 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;}
* }
* </pre>
*
* <p>
* For simple Round Robin, we can assume the same weight for all endpoints and
* perform the above.
*
* @author Sheetal Vartak
**/
public class RoundRobinPolicy {
@LogMessageInfo(message = "Could not find an endpoint to send request to.")
public static final String COULD_NOT_FIND_ENDPOINT = "AS-ORB-00004";
@LogMessageInfo(message = "Unknown host: {0} Exception thrown : {1}")
public static final String UNKNOWN_HOST = "AS-ORB-00005";
@LogMessageInfo(message = "No Endpoints selected in com.sun.appserv.iiop.endpoints property. Using JNDI Provider URL {0} instead")
public static final String NO_ENDPOINTS_SELECTED_PROVIDER = "AS-ORB-00006";
@LogMessageInfo(message = "Exception : {0} thrown for bad provider URL String: {1}")
public static final String PROVIDER_EXCEPTION = "AS-ORB-00007";
// 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 SecureRandom rand = new SecureRandom();
private List<ClusterInstanceInfo> endpointsList = new LinkedList<ClusterInstanceInfo>();
private int totalWeight;
private List<String> resolvedEndpoints;
private static final int default_weight = 10;
// called during bootstrapping
public RoundRobinPolicy(List<String> list) {
setClusterInstanceInfoFromString(list);
}
// Copy list, changing any type that does not start with SSL to CLEAR_TEXT.
private List<SocketInfo> filterSocketInfos(List<SocketInfo> sis) {
final List<SocketInfo> result = new ArrayList<SocketInfo>();
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(LOAD_BALANCING_PROPERTY, IC_BASED);
return policy.equals(IC_BASED_WEIGHTED);
}
private List<ClusterInstanceInfo> filterClusterInfo(List<ClusterInstanceInfo> info) {
boolean isw = isWeighted();
ArrayList<ClusterInstanceInfo> newList = new ArrayList<ClusterInstanceInfo>();
totalWeight = 0;
for (ClusterInstanceInfo clinfo : info) {
final int newWeight = isw ? clinfo.weight() : default_weight;
final List<SocketInfo> newEndpoints = filterSocketInfos(clinfo.endpoints());
final ClusterInstanceInfo newClinfo = new ClusterInstanceInfo(clinfo.name(), newWeight, newEndpoints);
newList.add(newClinfo);
totalWeight += newWeight;
}
return newList;
}
private boolean containsMatchingAddress(List<ClusterInstanceInfo> 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<ClusterInstanceInfo> merge(List<ClusterInstanceInfo> first, List<ClusterInstanceInfo> second) {
List<ClusterInstanceInfo> result = new ArrayList<ClusterInstanceInfo>();
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<ClusterInstanceInfo> fromHostPortStrings(List<String> list) {
List<ClusterInstanceInfo> result = new LinkedList<ClusterInstanceInfo>();
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<ClusterInstanceInfo> list) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "setClusterInstanceInfo: list={0}", list);
}
List<ClusterInstanceInfo> filtered = filterClusterInfo(list);
List<ClusterInstanceInfo> 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<String> list) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "setClusterInstanceInfoFromString: list={0}", list);
}
List<String> 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<String> newList2 = randomize(newList);
resolvedEndpoints = new ArrayList<String>(newList2);
endpointsList = fromHostPortStrings(newList2);
// Put in a default for total weight; any update will correct this.
totalWeight = 10 * endpointsList.size();
} else {
logger.log(Level.FINE, "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) {
// support IPV6 literal address
String[] host_port = new String[2];
int i = str.lastIndexOf(':');
host_port[0] = str.substring(0, i);
host_port[1] = str.substring(i + 1);
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<SocketInfo> sil = new ArrayList<SocketInfo>(1);
sil.add(socketInfo);
return new ClusterInstanceInfo(server_identifier, weight, sil);
}
/*
* 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<String> getEndpointForProviderURL(String providerURLString) {
if (providerURLString != null) {
try {
final IiopUrl providerURL = new IiopUrl(providerURLString);
final List<String> newList = getAddressPortList(providerURL);
logger.log(Level.WARNING, NO_ENDPOINTS_SELECTED_PROVIDER, providerURLString);
return newList;
} catch (MalformedURLException me) {
logger.log(Level.WARNING, PROVIDER_EXCEPTION, new Object[] { me, providerURLString });
}
}
return new ArrayList<String>();
}
/**
* randomize the list. Note: this empties its argument.
*/
private List<String> randomize(List<String> list) {
List<String> result = new ArrayList<String>(list.size());
while (!list.isEmpty()) {
int random = rand.nextInt(list.size());
String elem = list.remove(random);
result.add(elem);
}
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "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<String> 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<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));
endpointsList = instanceInfo;
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "getNextRotation: result={0}", instanceInfo);
}
return convertIntoCorbaloc(instanceInfo);
}
lowerLimit = upperLimit;
// fineLog( "lowerLimit = {0}", lowerLimit);
i++;
}
logger.log(Level.WARNING, COULD_NOT_FIND_ENDPOINT);
return new ArrayList<String>();
}
private List<String> convertIntoCorbaloc(List<ClusterInstanceInfo> list) {
List<String> host_port = new ArrayList<String>();
for (ClusterInstanceInfo endpoint : list) {
List<SocketInfo> 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<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 List<String> 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<InetAddress> addrs = new ArrayList<InetAddress>();
for (InetAddress addr : addresses) {
if (addr instanceof Inet4Address || addr instanceof Inet6Address) {
addrs.add(addr);
}
}
List<String> ret = new ArrayList<String>();
for (InetAddress addr : addrs) {
ret.add(addr.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 });
return new ArrayList<String>();
}
}
@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 synchronized List<String> getHostPortList() {
return resolvedEndpoints;
}
}