/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2009 Sun Microsystems, Inc. All rights reserved.
* Copyright (c) Ericsson AB, 2004-2008. 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 org.jvnet.glassfish.comms.clb.core.common.chr;
import com.ericsson.ssa.sip.SipFactoryImpl;
import com.ericsson.ssa.sip.dns.DnsResolver;
import org.glassfish.comms.api.datacentric.DcrPlugin;
import org.jvnet.glassfish.comms.clb.core.CLBConstants;
import org.jvnet.glassfish.comms.clb.core.ConsistentHashRequest;
import org.jvnet.glassfish.comms.clb.core.Controller;
import org.jvnet.glassfish.comms.clb.core.DCRFileUpdateEventListener;
import org.jvnet.glassfish.comms.clb.core.Router;
import org.jvnet.glassfish.comms.clb.core.ServerCluster;
import org.jvnet.glassfish.comms.clb.core.ServerInstance;
import org.jvnet.glassfish.comms.clb.core.common.chr.DcrPluginLoader.DcrPluginLoaderException;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrConfigurableHashKeyExtractor;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrRulesException;
import org.jvnet.glassfish.comms.clb.core.common.chr.dcr.DcrUtils;
import org.jvnet.glassfish.comms.clb.core.util.ConsistentHash;
import org.jvnet.glassfish.comms.clb.proxy.http.util.HttpRequest;
import org.jvnet.glassfish.comms.util.LogUtil;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.servlet.sip.Address;
import javax.servlet.sip.AuthInfo;
import javax.servlet.sip.Parameterable;
import javax.servlet.sip.ServletParseException;
import javax.servlet.sip.SipApplicationSession;
import javax.servlet.sip.SipFactory;
import javax.servlet.sip.SipServletRequest;
import javax.servlet.sip.SipURI;
import javax.servlet.sip.URI;
import org.jvnet.glassfish.comms.clb.core.util.LoadFactorTask;
import org.jvnet.glassfish.comms.clb.core.util.LoadFactorManager;
import org.jvnet.glassfish.comms.clb.core.util.LoadFactorChangeEventListener;
/**
* This is a router (common for HTTP and SIP) that implements the logic for
* server instance selection based on DCR. It can be used both for cluster (then
* it shall be set up by calling {@link #setClusterRouter(boolean)} with the
* argument true) and for request groups (default).
* <p>
* Server selection is performed in several steps depending of configuration:
*
* <pre>
* Extract hash key from request using configurable hash key extractor
* IF NOT configured as a cluster router:
* First select server using a consistent hash which includes all servers in all
* clusters (and assumes that they are all healthy): the "Consistent Hash for
* the Ideal Configuration"
* Check that selected server is healthy, and if it is use it.
* Otherwise (if the selected server is faulty), select server using the "Cluster
* Router" of the cluster of the failed server
* ENDIF
* If no server was found above, select server using a router which contains only
* the healthy servers of all clusters: the "Consistent hash for the Actual
* Configuration"
* Finally, if no servers were found at all return null.
* </pre>
*/
public class ConsistentHashRouter implements Router,
DCRFileUpdateEventListener, LoadFactorChangeEventListener {
/** Parameter used to setup consistent hashes */
private static final int POINTS_PER_NODE = 1024;
private static Logger logger = LogUtil.CLB_LOGGER.getLogger();
/**
* Consistent hash for the ideal configuration (not updated at server
* failure). Contains all servers in all clusters.
*/
protected ConsistentHash<String, ServerInstance> idealConsistentHash;
/**
* Consistent hash for the ideal configuration (updated at server
* failure/recovery). Contains all healthy servers in all clusters.
*/
private ConsistentHash<String, ServerInstance> actualConsistentHash;
/**
* All clusters that are associated with this router and that are used for
* routing at fail-over.
*/
private List<ServerCluster> associatedClusters = new ArrayList<ServerCluster>();
/** The composite hash key extractor. */
private CompositeHashKeyExtractor hashKeyExtractor = new CompositeHashKeyExtractor();
/** The XML file for DCR configuration. */
private File dcrFile;
protected boolean requireIdealHash;
private Controller controller;
private HashMap<String, ServerInstance> instancesMap;
private static final LoadFactorManager repository =
LoadFactorManager.getInstance();
/**
* @param associatedClusters
*/
public ConsistentHashRouter(List<ServerCluster> associatedClusters, boolean activeRouting) {
this.associatedClusters = associatedClusters;
//No need to maintain ideal hash for active routing
if (!activeRouting) {
requireIdealHash = true;
} else {
requireIdealHash = false;
}
//requireIdealHash = !activeRouting;
instancesMap = new HashMap<String, ServerInstance>();
}
/**
* <b>Note</b>, this method assumes that the specified request is an
* instance of {@link ConsistentHashRequest}.
* <p>
* Also note that the provided request will be updated, the hash key will be
* inserted into it.
*
* @see org.jvnet.glassfish.comms.clb.core.Router#selectInstance(Request)
*/
public ServerInstance selectInstance(ConsistentHashRequest request) {
// Must be a ConsistentHashRequest
if (request.getHashKey() == null) {
request.setHashKey(hashKeyExtractor.getHashKey(request));
}
// Then do routing based on the hash key...
return getServerInstance(request);
}
/**
* Gets the server instance for the specified request.
*
* @param req the request
* @return the server instance for the given hash key
*/
protected ServerInstance getServerInstance(ConsistentHashRequest req) {
return getActiveInstance(req);
}
protected ServerInstance getActiveInstance(ConsistentHashRequest req) {
if (req.getHashKey() == null) {
return null;
}
ServerInstance server;
server = actualConsistentHash.get(req.getHashKey());
return server;
}
/**
* Initializes this router.
*
* @see org.jvnet.glassfish.comms.clb.core.Router#initialize()
*/
public boolean initialize() {
repository.setProperties(controller.getProperties());
setupHashKeyExtractor();
if (requireIdealHash) {
// Only create this if it is a request group router
idealConsistentHash = new ConsistentHash<String, ServerInstance>(POINTS_PER_NODE, "CLB_IdealHash");
}
actualConsistentHash = new ConsistentHash<String, ServerInstance>(POINTS_PER_NODE, "CLB_ActualHash");
for (ServerCluster cluster : associatedClusters) {
for (ServerInstance server : cluster.getAllInstances()) {
instancesMap.put(server.toString(), server);
server.addRouter(this);
if (requireIdealHash) {
idealConsistentHash.addNode(server, false);
}
if (server.isHealthy() && server.isEnabled()) {
LoadFactorTask task = repository.getAddTask(server.toString());
if(task == null){
actualConsistentHash.addNode(server, false);
}else{
task.addListener(this);
actualConsistentHash.addNode(server,
task.getCurrFactor(), false);
addToRecoveringInstance(server);
}
}
}
}
if (requireIdealHash) {
idealConsistentHash.setup();
}
actualConsistentHash.setup();
return true;
}
/**
* Sets the DCR file.
*
* @param dcrFile the DCR file
*/
public void setDcrFile(File dcrFile) {
this.dcrFile = dcrFile;
}
/**
* Sets up the hash key extractor.
* <p>
* Pushes first a default hash key extractor and then a DCR extractor.
*/
protected void setupHashKeyExtractor() {
// First push the default hash key extractor...
DefaultHashKeyExtractor defaultHashKeyExtractor = new DefaultHashKeyExtractor();
pushHashKeyExtractor(defaultHashKeyExtractor);
// Then DCR hash key extractor...
DcrUtils.setup(DnsResolver.getInstance(), SipFactoryImpl.getInstance(), DnsResolver.getInstance());
// Only try to read DCR file if it exists; otherwise fall-back to Call-ID, from-tag (the default extractor)
if ((dcrFile != null) && dcrFile.exists()) {
// Either the file is a DCR-XML or a java-plugin
if (DcrPluginLoader.isSupported(dcrFile)) {
try {
DcrPluginLoader dcrPluginLoader = new DcrPluginLoader(dcrFile);
final DcrPlugin dcrPlugin = dcrPluginLoader.loadDcrPlugin();
if (dcrPlugin != null) {
pushHashKeyExtractor(new DcrPluginHashKeyExtractor(dcrPlugin));
logger.log(Level.INFO, "clb.success_load_dcr_plugin", dcrFile);
} else {
logger.log(Level.WARNING, "clb.failed_load_dcr_plugin_null", dcrFile);
}
} catch (DcrPluginLoaderException e) {
logger.log(Level.WARNING, "clb.failed_create_dcrplugin",
e.getMessage());
if(logger.isLoggable(Level.FINE)){
logger.log(Level.FINE, "clb.caught_an_exception", e);
}
}
} else {
if (dcrFile.getName().toLowerCase().indexOf(".xml") >= 0) {
try {
DcrConfigurableHashKeyExtractor dcrHashKeyExtractor = new DcrConfigurableHashKeyExtractor(dcrFile.getAbsolutePath());
pushHashKeyExtractor(dcrHashKeyExtractor);
} catch (DcrRulesException e) {
logger.log(Level.WARNING, "clb.failed_read_dcrxml",
e.getMessage());
if(logger.isLoggable(Level.FINE)){
logger.log(Level.FINE, "clb.caught_an_exception", e);
}
}
} else {
logger.log(Level.WARNING,
"clb.failed_load_dcr_plugin_unsupported", dcrFile);
}
}
}
pushHashKeyExtractor(new StickyHashKeyExtractor());
}
/**
* Pushes the specified hash key extractor on top of the others.
*
* @param hke the hash key extractor
*/
protected void pushHashKeyExtractor(HashKeyExtractor hke) {
hashKeyExtractor.push(hke);
}
public void handleDisableEvent(ServerInstance instance) {
if (instance.isHealthy()) {
removeFromActualConsistentHash(instance);
}
}
public void handleEnableEvent(ServerInstance instance) {
if (instance.isHealthy()) {
addToActualConsistentHash(instance);
}
}
public void handleFailureEvent(ServerInstance instance,
boolean isClusterShutdown) {
if (!isClusterShutdown && instance.isEnabled()) {
removeFromActualConsistentHash(instance);
}
}
public void handleRecoveryEvent(ServerInstance instance,
boolean isClusterStartup) {
if (instance.isEnabled()) {
if (!isClusterStartup){
if(createAddTask(instance)){
return;
}
}
addToActualConsistentHash(instance);
}
}
private void addToActualConsistentHash(final ServerInstance instance) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "clb.adding_to_active_list", new Object[] { instance.getName() });
}
actualConsistentHash.addNode(instance, true);
}
private void removeFromActualConsistentHash(final ServerInstance instance) {
if (logger.isLoggable(Level.FINER)) {
logger.log(Level.FINER, "clb.removing_from_active_list", new Object[] { instance.getName() });
}
cancelAddTask(instance);
actualConsistentHash.removeNode(instance, true);
}
public ServerInstance selectInstance(HttpRequest req) {
throw new UnsupportedOperationException("Not applicable for Consistent hash router.");
}
public void processDCRFileUpdateEvent() {
setDcrFile(new File(controller.getDCRFileName()));
setupHashKeyExtractor();
}
public void setController(Controller controller) {
this.controller = controller;
String dcrFileName = controller.getDCRFileName();
File dcrFile = null;
if (dcrFileName != null) {
dcrFile = new File(dcrFileName);
}
setDcrFile(dcrFile);
if (controller != null) {
controller.getDCRFileUpdateEventNotifier().addDCRFileUpdateEventListener(this);
}
}
public void cleanup() {
actualConsistentHash = null;
associatedClusters = null;
controller = null;
dcrFile = null;
hashKeyExtractor = null;
idealConsistentHash = null;
Iterator<ServerInstance> iter = instancesMap.values().iterator();
while (iter.hasNext()) {
cancelAddTask(iter.next());
}
instancesMap = null;
}
public boolean createAddTask(ServerInstance instance) {
LoadFactorTask task = repository.createAddTask(instance.toString());
if(task == null){
return false;
}
task.addListener(this);
addToRecoveringInstance(instance);
return true;
}
private void cancelAddTask(ServerInstance instance) {
LoadFactorTask task = repository.getAddTask(instance.toString());
if(task != null){
task.removeListener(this);
}
removeFromRecoveringInstance(instance);
}
protected void addToRecoveringInstance(ServerInstance serverInstance) {
}
protected void removeFromRecoveringInstance(ServerInstance serverInstance) {
}
public void updateLoadFactor(String instanceName,
double currFactor){
ServerInstance instance = instancesMap.get(instanceName);
if(instance != null){
actualConsistentHash.addNode(instance, currFactor, true);
}
if(currFactor >= 1.0){
removeFromRecoveringInstance(instance);
}
}
private static final class DcrPluginHashKeyExtractor implements HashKeyExtractor {
private final DcrPlugin dcrPlugin;
private DcrPluginHashKeyExtractor(DcrPlugin dcrPlugin) {
this.dcrPlugin = dcrPlugin;
}
public String getHashKey(ConsistentHashRequest request) {
if(request.isHttp()){
return getHashKey(request.getHttpRequest());
}
return getHashKey(request.getSipRequest());
}
public String getHashKey(HttpRequest request) {
return dcrPlugin.getKey(new HttpRequestAdapter(request), ReadOnlySipFactory.getInstance(), DnsResolver.getInstance(), DnsResolver.getInstance());
}
public String getHashKey(SipServletRequest req) {
return dcrPlugin.getKey(req, ReadOnlySipFactory.getInstance(), DnsResolver.getInstance(), DnsResolver.getInstance());
}
}
private static class ReadOnlySipFactory implements SipFactory {
private static ReadOnlySipFactory instance = new ReadOnlySipFactory();
private SipFactoryImpl delegate = SipFactoryImpl.getInstance();
private ReadOnlySipFactory() {}
static ReadOnlySipFactory getInstance() {
return instance;
}
public Address createAddress(String sipAddress) throws ServletParseException {
return delegate.createAddress(sipAddress);
}
public Address createAddress(URI uri) {
return delegate.createAddress(uri);
}
public Address createAddress(URI uri, String displayName) {
return delegate.createAddress(uri, displayName);
}
public SipApplicationSession createApplicationSession() {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public SipApplicationSession createApplicationSessionByKey(String sipApplicationKey) {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public AuthInfo createAuthInfo() {
return delegate.createAuthInfo();
}
public Parameterable createParameterable(String s) throws ServletParseException {
return delegate.createParameterable(s);
}
public SipServletRequest createRequest(SipApplicationSession sipAppSession, String method, Address from, Address to) {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public SipServletRequest createRequest(SipApplicationSession sipAppSession, String method, URI from, URI to) {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public SipServletRequest createRequest(SipApplicationSession sipAppSession, String method, String from, String to) throws ServletParseException {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public SipServletRequest createRequest(SipServletRequest origRequest, boolean sameCallId) {
throw new UnsupportedOperationException("Not supported in a DCR plugin!");
}
public SipURI createSipURI(String user, String host) {
return delegate.createSipURI(user, host);
}
public URI createURI(String s) throws ServletParseException {
return delegate.createURI(s);
}
}
}