/* * 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.servlet.sip.startup.failover; import java.net.InetAddress; import java.net.UnknownHostException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.ConcurrentHashMap; import org.apache.catalina.Container; import org.apache.catalina.Engine; import org.apache.catalina.LifecycleException; import org.apache.catalina.connector.Connector; import org.apache.coyote.ProtocolHandler; import org.apache.log4j.Logger; import org.mobicents.servlet.sip.startup.SipProtocolHandler; import org.mobicents.servlet.sip.startup.SipStandardService; import org.mobicents.servlet.sip.utils.Inet6Util; import org.mobicents.tools.sip.balancer.NodeRegisterRMIStub; import org.mobicents.tools.sip.balancer.SIPNode; /** * <p>Sip Servlet implementation of the <code>Service</code> interface.</p> * * <p>This implementation extends the <code>SipStandardService</code> (that allows Tomcat to become a converged container) * with the failover features.<br/> * This implementation will send heartbeats and health information to the sip balancers configured for this service * (configured in the server.xml as balancers attribute fo the Service Tag)</p> * * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ public class SipStandardBalancerNodeService extends SipStandardService implements SipBalancerNodeService { private static final String BALANCER_SIP_PORT_CHAR_SEPARATOR = ":"; private static final String BALANCERS_CHAR_SEPARATOR = ";"; private static final int DEFAULT_LB_SIP_PORT = 5065; //the logger private static transient Logger logger = Logger.getLogger(SipStandardBalancerNodeService.class); /** * The descriptive information string for this implementation. */ private static final String info = "org.mobicents.servlet.sip.startup.failover.SipStandardBalancerNodeService/1.0"; //the balancers to send heartbeat to and our health info private String balancers; //the balancers names to send heartbeat to and our health info private Map<String, BalancerDescription> register = new ConcurrentHashMap<String, BalancerDescription>(); //heartbeat interval, can be modified through JMX private long heartBeatInterval = 5000; private Timer heartBeatTimer = new Timer(); private TimerTask hearBeatTaskToRun = null; private boolean started = false; private boolean displayBalancerWarining = true; private boolean displayBalancerFound = true; @Override public String getInfo() { return (info); } @Override public void initialize() throws LifecycleException { super.initialize(); } @Override public void start() throws LifecycleException { super.start(); if (!started) { if (balancers != null && balancers.length() > 0) { String[] balancerDescriptions = balancers.split(BALANCERS_CHAR_SEPARATOR); for (String balancerDescription : balancerDescriptions) { String balancerAddress = balancerDescription; int sipPort = DEFAULT_LB_SIP_PORT; if(balancerDescription.indexOf(BALANCER_SIP_PORT_CHAR_SEPARATOR) != -1) { String[] balancerDescriptionSplitted = balancerDescription.split(BALANCER_SIP_PORT_CHAR_SEPARATOR); balancerAddress = balancerDescriptionSplitted[0]; try { sipPort = Integer.parseInt(balancerDescriptionSplitted[1]); } catch (NumberFormatException e) { throw new LifecycleException("Impossible to parse the following sip balancer port " + balancerDescriptionSplitted[1], e); } } if(Inet6Util.isValidIP6Address(balancerAddress) || Inet6Util.isValidIPV4Address(balancerAddress)) { try { this.addBalancer(InetAddress.getByName(balancerAddress).getHostAddress(), sipPort); } catch (UnknownHostException e) { throw new LifecycleException("Impossible to parse the following sip balancer address " + balancerAddress, e); } } else { this.addBalancer(balancerAddress, sipPort, 0); } } } started = true; } this.hearBeatTaskToRun = new BalancerPingTimerTask(); this.heartBeatTimer.scheduleAtFixedRate(this.hearBeatTaskToRun, 0, this.heartBeatInterval); if(logger.isDebugEnabled()) { logger.debug("Created and scheduled tasks for sending heartbeats to the sip balancer."); } } @Override public void stop() throws LifecycleException { // Force removal from load balancer upon shutdown // added for Issue 308 (http://code.google.com/p/mobicents/issues/detail?id=308) ArrayList<SIPNode> info = getConnectorsAsSIPNode(); removeNodesFromBalancers(info); //cleaning // balancerNames.clear(); register.clear(); if(hearBeatTaskToRun != null) { this.hearBeatTaskToRun.cancel(); } this.hearBeatTaskToRun = null; started = false; super.stop(); } /** * {@inheritDoc} */ public long getHeartBeatInterval() { return heartBeatInterval; } /** * {@inheritDoc} */ public void setHeartBeatInterval(long heartBeatInterval) { if (heartBeatInterval < 100) return; this.heartBeatInterval = heartBeatInterval; this.hearBeatTaskToRun.cancel(); this.hearBeatTaskToRun = new BalancerPingTimerTask(); this.heartBeatTimer.scheduleAtFixedRate(this.hearBeatTaskToRun, 0, this.heartBeatInterval); } /** * * @param hostName * @param index * @return */ private InetAddress fetchHostAddress(String hostName, int index) { if (hostName == null) throw new NullPointerException("Host name cant be null!!!"); InetAddress[] hostAddr = null; try { hostAddr = InetAddress.getAllByName(hostName); } catch (UnknownHostException uhe) { throw new IllegalArgumentException( "HostName is not a valid host name or it doesnt exists in DNS", uhe); } if (index < 0 || index >= hostAddr.length) { throw new IllegalArgumentException( "Index in host address array is wrong, it should be [0]<x<[" + hostAddr.length + "] and it is [" + index + "]"); } InetAddress address = hostAddr[index]; return address; } /** * {@inheritDoc} */ public String[] getBalancers() { return this.register.keySet().toArray(new String[register.keySet().size()]); } /** * {@inheritDoc} */ public boolean addBalancer(String addr, int sipPort) { if (addr == null) throw new NullPointerException("addr cant be null!!!"); InetAddress address = null; try { address = InetAddress.getByName(addr); } catch (UnknownHostException e) { throw new IllegalArgumentException( "Somethign wrong with host creation.", e); } String balancerName = address.getCanonicalHostName(); if (register.get(balancerName) != null) { logger.info("Sip balancer " + balancerName + " already present, not added"); return false; } if(logger.isDebugEnabled()) { logger.debug("Adding following balancer name : " + balancerName +"/address:"+ addr); } BalancerDescription balancerDescription = new BalancerDescription(address, sipPort); register.put(balancerName, balancerDescription); //notify the sip factory if(sipApplicationDispatcher.getSipFactory().getLoadBalancerToUse() == null) { sipApplicationDispatcher.getSipFactory().setLoadBalancerToUse(balancerDescription); } return true; } /** * {@inheritDoc} */ public boolean addBalancer(String hostName, int sipPort, int index) { return this.addBalancer(fetchHostAddress(hostName, index) .getHostAddress(), sipPort); } /** * {@inheritDoc} */ public boolean removeBalancer(String addr, int sipPort) { if (addr == null) throw new NullPointerException("addr cant be null!!!"); InetAddress address = null; try { address = InetAddress.getByName(addr); } catch (UnknownHostException e) { throw new IllegalArgumentException( "Something wrong with host creation.", e); } BalancerDescription balancerDescription = new BalancerDescription(address, sipPort); String keyToRemove = null; Iterator<String> keyIterator = register.keySet().iterator(); while (keyIterator.hasNext() && keyToRemove ==null) { String key = keyIterator.next(); if(register.get(key).equals(balancerDescription)) { keyToRemove = key; } } if(keyToRemove !=null ) { if(logger.isDebugEnabled()) { logger.debug("Removing following balancer name : " + keyToRemove +"/address:"+ addr); } register.remove(keyToRemove); if(sipApplicationDispatcher.getSipFactory().getLoadBalancerToUse() != null && sipApplicationDispatcher.getSipFactory().getLoadBalancerToUse().equals(balancerDescription)) { sipApplicationDispatcher.getSipFactory().setLoadBalancerToUse(null); } return true; } return false; } /** * {@inheritDoc} */ public boolean removeBalancer(String hostName, int sipPort, int index) { InetAddress[] hostAddr = null; try { hostAddr = InetAddress.getAllByName(hostName); } catch (UnknownHostException uhe) { throw new IllegalArgumentException( "HostName is not a valid host name or it doesnt exists in DNS", uhe); } if (index < 0 || index >= hostAddr.length) { throw new IllegalArgumentException( "Index in host address array is wrong, it should be [0]<x<[" + hostAddr.length + "] and it is [" + index + "]"); } InetAddress address = hostAddr[index]; return this.removeBalancer(address.getHostAddress(), sipPort); } private ArrayList<SIPNode> getConnectorsAsSIPNode() { ArrayList<SIPNode> info = new ArrayList<SIPNode>(); // Gathering info about server' sip listening points for (Connector connector : connectors) { ProtocolHandler protocolHandler = connector.getProtocolHandler(); if(protocolHandler instanceof SipProtocolHandler) { SipProtocolHandler sipProtocolHandler = (SipProtocolHandler) protocolHandler; String address = sipProtocolHandler.getIpAddress(); // From Vladimir: for some reason I get "localhost" here instead of IP and this confiuses the LB if(address.equals("localhost")) address = "127.0.0.1"; int port = sipProtocolHandler.getPort(); String transport = sipProtocolHandler.getSignalingTransport(); String[] transports = new String[] {transport}; String hostName = null; try { InetAddress[] aArray = InetAddress .getAllByName(address); if (aArray != null && aArray.length > 0) { // Damn it, which one we should pick? hostName = aArray[0].getCanonicalHostName(); } } catch (UnknownHostException e) { logger.error("An exception occurred while trying to retrieve the hostname of a sip connector", e); } Engine e = null; for (Container c = connector.getContainer(); e == null && c != null; c = c.getParent()) { if (c != null && c instanceof Engine) { e = (Engine) c; } } String jvmRoute = null; if(e != null) { jvmRoute = e.getJvmRoute(); } SIPNode node = new SIPNode(hostName, address, port, transports, jvmRoute); info.add(node); } } return info; } /** * @param info */ private void sendKeepAliveToBalancers(ArrayList<SIPNode> info) { for(BalancerDescription balancerDescription:new HashSet<BalancerDescription>(register.values())) { try { Registry registry = LocateRegistry.getRegistry(balancerDescription.getAddress().getHostAddress(),2000); NodeRegisterRMIStub reg=(NodeRegisterRMIStub) registry.lookup("SIPBalancer"); reg.handlePing(info); displayBalancerWarining = true; if(displayBalancerFound) { logger.info("SIP Load Balancer Found!"); displayBalancerFound = false; } } catch (Exception e) { if(displayBalancerWarining) { logger.warn("Cannot access the SIP load balancer RMI registry: " + e.getMessage() + "\nIf you need a cluster configuration make sure the SIP load balancer is running."); logger.error("Cannot access the SIP load balancer RMI registry: " , e); displayBalancerWarining = false; } displayBalancerFound = true; } } if(logger.isDebugEnabled()) { logger.debug("Finished gathering"); logger.debug("Gathered info[" + info + "]"); } } /** * @param info */ public void sendSwitchoverInstruction(String fromJvmRoute, String toJvmRoute) { logger.info("switching over from " + fromJvmRoute + " to " + toJvmRoute); if(fromJvmRoute == null || toJvmRoute == null) { return; } for(BalancerDescription balancerDescription:new HashSet<BalancerDescription>(register.values())) { try { Registry registry = LocateRegistry.getRegistry(balancerDescription.getAddress().getHostAddress(),2000); NodeRegisterRMIStub reg=(NodeRegisterRMIStub) registry.lookup("SIPBalancer"); reg.switchover(fromJvmRoute, toJvmRoute); displayBalancerWarining = true; if(displayBalancerFound) { logger.info("SIP Load Balancer Found!"); displayBalancerFound = false; } } catch (Exception e) { if(displayBalancerWarining) { logger.warn("Cannot access the SIP load balancer RMI registry: " + e.getMessage() + "\nIf you need a cluster configuration make sure the SIP load balancer is running."); logger.error("Cannot access the SIP load balancer RMI registry: " , e); displayBalancerWarining = false; } displayBalancerFound = true; } } if(logger.isDebugEnabled()) { logger.debug("Finished gathering"); logger.debug("Gathered info[" + info + "]"); } } /** * @param info */ private void removeNodesFromBalancers(ArrayList<SIPNode> info) { for(BalancerDescription balancerDescription:new HashSet<BalancerDescription>(register.values())) { try { Registry registry = LocateRegistry.getRegistry(balancerDescription.getAddress().getHostAddress(),2000); NodeRegisterRMIStub reg=(NodeRegisterRMIStub) registry.lookup("SIPBalancer"); reg.forceRemoval(info); displayBalancerWarining = true; if(displayBalancerFound) { logger.info("SIP Load Balancer Found!"); displayBalancerFound = false; } } catch (Exception e) { if(displayBalancerWarining) { logger.warn("Cannot access the SIP load balancer RMI registry: " + e.getMessage() + "\nIf you need a cluster configuration make sure the SIP load balancer is running."); logger.error("Cannot access the SIP load balancer RMI registry: " , e); displayBalancerWarining = false; } displayBalancerFound = true; } } if(logger.isDebugEnabled()) { logger.debug("Finished gathering"); logger.debug("Gathered info[" + info + "]"); } } /** * * @author <A HREF="mailto:jean.deruelle@gmail.com">Jean Deruelle</A> * */ class BalancerPingTimerTask extends TimerTask { @SuppressWarnings("unchecked") @Override public void run() { if(logger.isDebugEnabled()) { logger.debug("Start"); } ArrayList<SIPNode> info = getConnectorsAsSIPNode(); sendKeepAliveToBalancers(info); } } /** * @param balancers the balancers to set */ public void setBalancers(String balancers) { this.balancers = balancers; } }