/* * JBoss, Home of Professional Open Source * Copyright 2008, Red Hat Middleware LLC, and individual contributors * by the @authors tag. See the copyright.txt in the distribution for a * full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.mobicents.tools.sip.balancer; import java.net.InetAddress; import java.rmi.AlreadyBoundException; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.rmi.server.UnicastRemoteObject; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Level; import java.util.logging.Logger; /** * <p> * This is the placeholder for maintening information about alive nodes and * the relation between a Call-Id and its attributed node. * </p> * * @author M. Ranganathan * @author baranowb * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ public class NodeRegisterImpl implements NodeRegister { private static Logger logger = Logger.getLogger(NodeRegisterImpl.class.getCanonicalName()); public static final int POINTER_START = 0; private long nodeInfoExpirationTaskInterval = 5000; private long nodeExpiration = 5100; private Registry registry; private Timer taskTimer = new Timer(); private TimerTask nodeExpirationTask = null; private AtomicInteger pointer; private List<SIPNode> nodes; private ConcurrentHashMap<String, SIPNode> jvmRouteToSipNode; private ConcurrentHashMap<String, SIPNode> gluedSessions; private InetAddress serverAddress = null; public NodeRegisterImpl(InetAddress serverAddress) throws RemoteException { super(); this.serverAddress = serverAddress; } /** * {@inheritDoc} */ public List<SIPNode> getNodes() { return this.nodes; } /** * {@inheritDoc} */ public boolean startRegistry(int rmiRegistryPort) { if(logger.isLoggable(Level.INFO)) { logger.info("Node registry starting..."); } try { nodes = new CopyOnWriteArrayList<SIPNode>(); gluedSessions = new ConcurrentHashMap<String, SIPNode>(); jvmRouteToSipNode = new ConcurrentHashMap<String, SIPNode>(); pointer = new AtomicInteger(POINTER_START); register(serverAddress, rmiRegistryPort); this.nodeExpirationTask = new NodeExpirationTimerTask(); this.taskTimer.scheduleAtFixedRate(this.nodeExpirationTask, this.nodeInfoExpirationTaskInterval, this.nodeInfoExpirationTaskInterval); if(logger.isLoggable(Level.INFO)) { logger.info("Node expiration task created"); logger.info("Node registry started"); } } catch (Exception e) { logger.log(Level.SEVERE, "Unexpected exception while starting the registry", e); return false; } return true; } /** * {@inheritDoc} */ public boolean stopRegistry() { if(logger.isLoggable(Level.INFO)) { logger.info("Stopping node registry..."); } boolean isDeregistered = deregister(serverAddress); boolean taskCancelled = nodeExpirationTask.cancel(); if(logger.isLoggable(Level.INFO)) { logger.info("Node Expiration Task cancelled " + taskCancelled); } nodes.clear(); nodes = null; gluedSessions.clear(); gluedSessions = null; pointer = new AtomicInteger(POINTER_START); if(logger.isLoggable(Level.INFO)) { logger.info("Node registry stopped."); } return isDeregistered; } // ********* CLASS TO BE EXPOSED VIA RMI private class RegisterRMIStub extends UnicastRemoteObject implements NodeRegisterRMIStub { protected RegisterRMIStub() throws RemoteException { super(); } /* * (non-Javadoc) * @see org.mobicents.tools.sip.balancer.NodeRegisterRMIStub#handlePing(java.util.ArrayList) */ public void handlePing(ArrayList<SIPNode> ping) throws RemoteException { handlePingInRegister(ping); } /* * (non-Javadoc) * @see org.mobicents.tools.sip.balancer.NodeRegisterRMIStub#forceRemoval(java.util.ArrayList) */ public void forceRemoval(ArrayList<SIPNode> ping) throws RemoteException { forceRemovalInRegister(ping); } public void switchover(String fromJvmRoute, String toJvmRoute) throws RemoteException { jvmRouteSwitchover(fromJvmRoute, toJvmRoute); } } // ***** SOME PRIVATE HELPERS private void register(InetAddress serverAddress, int rmiRegistryPort) { try { registry = LocateRegistry.createRegistry(rmiRegistryPort); registry.bind("SIPBalancer", new RegisterRMIStub()); } catch (RemoteException e) { throw new RuntimeException("Failed to bind due to:", e); } catch (AlreadyBoundException e) { throw new RuntimeException("Failed to bind due to:", e); } } private boolean deregister(InetAddress serverAddress) { try { registry.unbind("SIPBalancer"); return UnicastRemoteObject.unexportObject(registry, false); } catch (RemoteException e) { throw new RuntimeException("Failed to unbind due to", e); } catch (NotBoundException e) { throw new RuntimeException("Failed to unbind due to", e); } } // ***** NODE MGMT METHODS /** * {@inheritDoc} */ public void unStickSessionFromNode(String callID) { SIPNode node = gluedSessions.remove(callID); if(logger.isLoggable(Level.FINEST)) { logger.finest("unsticked CallId " + callID + " from node " + node); } } /** * {@inheritDoc} */ public SIPNode getNextNode() { int nodesSize = nodes.size(); if(nodesSize < 1) { return null; } int index = pointer.getAndIncrement() % nodesSize; return this.nodes.get(index); } /** * {@inheritDoc} */ public SIPNode stickSessionToNode(String callID, SIPNode sipNode) { SIPNode node = null; if(sipNode != null) { node = gluedSessions.putIfAbsent(callID, sipNode); if(node == null) { node = sipNode; } } else { node = gluedSessions.get(callID); } // Issue 308 (http://code.google.com/p/mobicents/issues/detail?id=308) // if we already stick a request to this node, but the server crashed and this is a retransmission // we need to check if the node is still alive and pick another one if not if(node != null && !isSIPNodePresent(node.getIp(), node.getPort(), node.getTransports()[0])) { node = null; } if(node == null) { SIPNode newStickyNode = this.getNextNode(); if (newStickyNode != null) { node = gluedSessions.putIfAbsent(callID, newStickyNode); if(node == null) { node = newStickyNode; } } } if(logger.isLoggable(Level.FINEST)) { logger.finest("sticking CallId " + callID + " to node " + node); } return node; } /** * {@inheritDoc} */ public SIPNode getGluedNode(String callID) { SIPNode sipNode = this.gluedSessions.get(callID);; if(logger.isLoggable(Level.FINEST)) { logger.finest("glueued node " + sipNode + " for CallId " + callID); } return sipNode; } /** * {@inheritDoc} */ public boolean isSIPNodePresent(String host, int port, String transport) { if(getNode(host, port, transport) != null) { return true; } return false; } /** * {@inheritDoc} */ public SIPNode getNode(String host, int port, String transport) { for (SIPNode node : nodes) { if(logger.isLoggable(Level.FINEST)) { logger.finest("node to check against " + node); } if(node.getIp().equals(host) && node.getPort() == port) { String[] nodeTransports = node.getTransports(); if(nodeTransports.length > 0) { for(String nodeTransport : nodeTransports) { if(nodeTransport.equalsIgnoreCase(transport)) { if(logger.isLoggable(Level.FINEST)) { logger.finest("checking if the node is still alive for " + host + ":" + port + "/" + transport + " : true"); } return node; } } } else { if(logger.isLoggable(Level.FINEST)) { logger.finest("checking if the node is still alive for " + host + ":" + port + "/" + transport + " : true"); } return node; } } } if(logger.isLoggable(Level.FINEST)) { logger.finest("checking if the node is still alive for " + host + ":" + port + "/" + transport + " : false"); } return null; } class NodeExpirationTimerTask extends TimerTask { public void run() { if(logger.isLoggable(Level.FINEST)) { logger.finest("NodeExpirationTimerTask Running"); } for (SIPNode node : nodes) { if (node.getTimeStamp() + nodeExpiration < System .currentTimeMillis()) { nodes.remove(node); if(logger.isLoggable(Level.INFO)) { logger.info("NodeExpirationTimerTask Run NSync[" + node + "] removed"); } } else { if(logger.isLoggable(Level.FINEST)) { logger.finest("node time stamp : " + (node.getTimeStamp() + nodeExpiration) + " , current time : " + System.currentTimeMillis()); } } } if(logger.isLoggable(Level.FINEST)) { logger.finest("NodeExpirationTimerTask Done"); } } } /** * {@inheritDoc} */ public void handlePingInRegister(ArrayList<SIPNode> ping) { for (SIPNode pingNode : ping) { if(pingNode.getJvmRoute() != null) { // Let it leak, we will have 10-100 nodes, not a big deal if it leaks. // We need info about inative nodes to do the failover jvmRouteToSipNode.put(pingNode.getJvmRoute(), pingNode); } if(nodes.size() < 1) { nodes.add(pingNode); if(logger.isLoggable(Level.INFO)) { logger.info("NodeExpirationTimerTask Run NSync[" + pingNode + "] added"); } return ; } SIPNode nodePresent = null; Iterator<SIPNode> nodesIterator = nodes.iterator(); while (nodesIterator.hasNext() && nodePresent == null) { SIPNode node = (SIPNode) nodesIterator.next(); if (node.equals(pingNode)) { nodePresent = node; } } // adding done afterwards to avoid ConcurrentModificationException when adding the node while going through the iterator if(nodePresent != null) { nodePresent.updateTimerStamp(); } else { nodes.add(pingNode); if(logger.isLoggable(Level.INFO)) { logger.info("NodeExpirationTimerTask Run NSync[" + pingNode + "] added"); } } } } /** * {@inheritDoc} */ public void forceRemovalInRegister(ArrayList<SIPNode> ping) { for (SIPNode pingNode : ping) { if(nodes.size() < 1) { nodes.remove(pingNode); if(logger.isLoggable(Level.INFO)) { logger.info("NodeExpirationTimerTask Run NSync[" + pingNode + "] forcibly removed due to a clean shutdown of a node"); } return ; } boolean nodePresent = false; Iterator<SIPNode> nodesIterator = nodes.iterator(); while (nodesIterator.hasNext() && !nodePresent) { SIPNode node = (SIPNode) nodesIterator.next(); if (node.equals(pingNode)) { nodePresent = true; } } // removal done afterwards to avoid ConcurrentModificationException when removing the node while goign through the iterator if(nodePresent) { nodes.remove(pingNode); if(logger.isLoggable(Level.INFO)) { logger.info("NodeExpirationTimerTask Run NSync[" + pingNode + "] forcibly removed due to a clean shutdown of a node. Numbers of nodes present in the balancer : " + nodes.size()); } } } } /** * {@inheritDoc} */ public InetAddress getAddress() { return this.serverAddress; } /** * {@inheritDoc} */ public long getNodeExpiration() { return this.nodeExpiration; } /** * {@inheritDoc} */ public long getNodeExpirationTaskInterval() { return this.nodeInfoExpirationTaskInterval; } /** * {@inheritDoc} */ public void setNodeExpiration(long value) throws IllegalArgumentException { if (value < 150) throw new IllegalArgumentException("Value cant be less than 150"); this.nodeExpiration = value; } /** * {@inheritDoc} */ public void setNodeExpirationTaskInterval(long value) { if (value < 150) throw new IllegalArgumentException("Value cant be less than 150"); this.nodeInfoExpirationTaskInterval = value; } /** * {@inheritDoc} */ public Map<String, SIPNode> getGluedSessions() { return gluedSessions; } public SIPNode[] getAllNodes() { return this.nodes.toArray(new SIPNode[]{}); } public void jvmRouteSwitchover(String fromJvmRoute, String toJvmRoute) { SIPNode oldNode = this.jvmRouteToSipNode.get(fromJvmRoute); SIPNode newNode = this.jvmRouteToSipNode.get(toJvmRoute); if(oldNode != null && newNode != null) { int updatedRoutes = 0; for(String key : gluedSessions.keySet()) { SIPNode n = gluedSessions.get(key); if(n.equals(oldNode)) { gluedSessions.replace(key, newNode); updatedRoutes++; } } if(logger.isLoggable(Level.INFO)) { logger.info("Switchover occured where fromJvmRoute=" + fromJvmRoute + " and toJvmRoute=" + toJvmRoute + " with " + updatedRoutes + " updated routes."); } } else { if(logger.isLoggable(Level.INFO)) { logger.info("Switchover failed where fromJvmRoute=" + fromJvmRoute + " and toJvmRoute=" + toJvmRoute); } } } }