Package tigase.cluster

Source Code of tigase.cluster.ClusterConnectionManager

/*
* Tigase Jabber/XMPP Server
* Copyright (C) 2004-2007 "Artur Hefczyc" <artur.hefczyc@tigase.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. Look for COPYING file in the top folder.
* If not, see http://www.gnu.org/licenses/.
*
* $Rev: 1315 $
* Last modified by $Author: kobit $
* $Date: 2008-12-15 18:12:22 +0000 (Mon, 15 Dec 2008) $
*/

package tigase.cluster;

import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import java.util.Map;
import java.util.List;
import java.util.Queue;
import java.util.LinkedHashMap;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.net.UnknownHostException;

import tigase.net.ConnectionType;
//import tigase.net.IOService;
import tigase.net.SocketType;
import tigase.server.ConnectionManager;
import tigase.server.Packet;
import tigase.disco.XMPPService;
import tigase.disco.ServiceEntity;
import tigase.disco.ServiceIdentity;
import tigase.util.Algorithms;
import tigase.util.JIDUtils;
import tigase.util.DNSResolver;
import tigase.xml.Element;
import tigase.xmpp.StanzaType;
import tigase.xmpp.Authorization;
import tigase.xmpp.XMPPIOService;
import tigase.xmpp.PacketErrorTypeException;
import tigase.stats.StatRecord;
import tigase.util.TimeUtils;

/**
* Class ClusterConnectionManager
*
* Created: Tue Nov 22 07:07:11 2005
*
* @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a>
* @version $Rev: 1315 $
*/
public class ClusterConnectionManager extends ConnectionManager<XMPPIOService>
  implements XMPPService {

  /**
   * Variable <code>log</code> is a class logger.
   */
  private static final Logger log =
    Logger.getLogger("tigase.cluster.ClusterConnectionManager");

  public int[] PORTS = {5277};
  public static final String SECRET_PROP_KEY = "secret";
  public String SECRET_PROP_VAL =  "someSecret";
  public static final String PORT_LOCAL_HOST_PROP_KEY = "local-host";
  public String PORT_LOCAL_HOST_PROP_VAL = "localhost";
  public String PORT_REMOTE_HOST_PROP_VAL = "comp-1.localhost";
  public static final String PORT_ROUTING_TABLE_PROP_KEY = "routing-table";
  public String[] PORT_IFC_PROP_VAL = {"*"};
  public static final String RETURN_SERVICE_DISCO_KEY = "service-disco";
  public static final boolean RETURN_SERVICE_DISCO_VAL = true;
  public static final String IDENTITY_TYPE_KEY = "identity-type";
  public static final String IDENTITY_TYPE_VAL = "generic";
  public static final String CONNECT_ALL_PAR = "--cluster-connect-all";
  public static final String CONNECT_ALL_PROP_KEY = "connect-all";
  public static final String CLUSTER_CONTR_ID_PROP_KEY = "cluster-controller-id";
  public static final boolean CONNECT_ALL_PROP_VAL = false;
  public static final String XMLNS = "tigase:cluster";

  private ServiceEntity serviceEntity = null;
  //private boolean service_disco = RETURN_SERVICE_DISCO_VAL;
  private String identity_type = IDENTITY_TYPE_VAL;
  private boolean connect_all = CONNECT_ALL_PROP_VAL;
  //  private boolean notify_admins = NOTIFY_ADMINS_PROP_VAL;
  //  private String[] admins = new String[] {};
  private String cluster_controller_id = null;
//   private LinkedHashMap<String, LinkedHashMap<Long, Packet>> waiting_packs =
//     new LinkedHashMap<String, LinkedHashMap<Long, Packet>>();
//   private LinkedHashMap<String, Long> sent_rids = new LinkedHashMap<String, Long>();
//   private LinkedHashMap<String, Long> recieved_rids =
//     new LinkedHashMap<String, Long>();
//   private LinkedHashMap<String, Long> recieved_acks =
//     new LinkedHashMap<String, Long>();

  private long totalNodeDisconnects = 0;
  private long[] lastDay = new long[24];
  private long[] lastHour = new long[60];
  private int lastDayIdx = 0;
  private int lastHourIdx = 0;

  @Override
  public void processPacket(Packet packet) {
    if (log.isLoggable(Level.FINEST)) {
      log.finest("Processing packet: " + packet.getStringData());
    }
    if (packet.getElemTo() != null
      && packet.getElemTo().equals(getComponentId())) {
      try {
        addOutPacket(
          Authorization.FEATURE_NOT_IMPLEMENTED.getResponseMessage(packet,
            "Not implemented", true));
      } catch (PacketErrorTypeException e) {
        log.warning("Packet processing exception: " + e);
      }
      return;
    }
    writePacketToSocket(packet.packRouted());
//     if (packet.getElemName() == ClusterElement.CLUSTER_EL_NAME) {
//       writePacketToSocket(packet);
//     } else {
//       writePacketToSocket(packet.packRouted());
//     }
  }

  protected void writePacketToSocket(Packet p) {
//     long rid = ++send_rid;
//     p.getElement().setAttribute("rid", ""+rid);
//     synchronized (waiting_packs) {
//       LinkedHashMap<Long, Packet> waiting_packets =
//         waiting_packs.get(getServiceId(p));
//       if (waiting_packets == null) {
//         waiting_packets = new LinkedHashMap<Long, Packet>();
//         waiting_ack.put(getServiceId(p), waiting_packets);
//       }
//       waiting_packets.put(rid, p);
//     }
    super.writePacketToSocket(p);
  }

  public Queue<Packet> processSocketData(XMPPIOService serv) {
    Packet p = null;
    while ((p = serv.getReceivedPackets().poll()) != null) {
      if (log.isLoggable(Level.FINEST)) {
        log.finest("Processing socket data: " + p.getStringData());
      }
      if (p.getElemName().equals("handshake")) {
        processHandshake(p, serv);
      } else {
        if (p.isRouted()) {
//           processReceivedRid(p, serv);
//           processReceivedAck(p, serv);
          p = p.unpackRouted();
        } // end of if (p.isRouted())
        addOutPacket(p);
      }
    } // end of while ()
    return null;
  }

//   private void processReceivedAck(Packet packet, XMPPIOService serv) {
//     String ack_str = packet.getAttribute("ack");
//     if (ack_str == null) {
//       log.warning("ack attribute is null for packet: " + packet.toString()
//         + ", please update all cluster nodes.");
//     } else {
//       try {
//         long r_ack = Long.parseLong(ack_str);
//         synchronized (waiting_packs) {
//           LinkedHashMap<Long, Packet> waiting_packets =
//             waiting_packs.get(serv.getRemoteAddress());
//           if (waiting_packets == null) {
//             log.warning("Checking ACK and waiting_packets is null for packet: " +
//               packet);
//             return;
//           }
//           long last_ack = received_acks.get(serv.getRemoteAddress());
//           if (r_ack == (++last_ack)) {
//             received_acks.put(serv.getRemoteAddress(), r_ack);
//             Packet p = waiting_packets.remove(r_ack);
//             if (p == null) {
//               log.warning("Packet for r_ack = " + r_ack + " not found...");
//             }
//           } else {
           
//           }
//         }
//       } catch (NumberFormatException e) {
//         log.warning("Incorrect ack value in packet: " + packet.toString());
//       }
//     }
//   }

//   private void processReceivedRid(Packet packet, XMPPIOService serv) {
//     String rid_str = packet.getAttribute("rid");
//     if (rid_str == null) {
//       log.warning("rid attribute is null for packet: " + packet.toString()
//         + ", please update all cluster nodes.");
//     } else {
//       try {
//         long r_rid = Long.parseLong(rid_str);
//       } catch (NumberFormatException e) {
//         log.warning("Incorrect rid value in packet: " + packet.toString());
//       }
//     }
//   }

  private void processHandshake(Packet p, XMPPIOService serv) {
    switch (serv.connectionType()) {
    case connect: {
      String data = p.getElemCData();
      if (data == null) {
        serviceConnected(serv);
      } else {
        log.warning("Incorrect packet received: " + p.getStringData());
      }
      break;
    }
    case accept: {
      String digest = p.getElemCData();
      String id =
        (String)serv.getSessionData().get(XMPPIOService.SESSION_ID_KEY);
      String secret =
        (String)serv.getSessionData().get(SECRET_PROP_KEY);
      try {
        String loc_digest = Algorithms.hexDigest(id, secret, "SHA");
        log.finest("Calculating digest: id="+id+", secret="+secret
          +", digest="+loc_digest);
        if (digest != null && digest.equals(loc_digest)) {
          Packet resp = new Packet(new Element("handshake"));
          writePacketToSocket(serv, resp);
          serviceConnected(serv);
        } else {
          log.warning("Handshaking password doesn't match, disconnecting...");
          serv.stop();
        }
      } catch (Exception e) {
        log.log(Level.SEVERE, "Handshaking error.", e);
      }
      break;
    }
    default:
      // Do nothing, more data should come soon...
      break;
    } // end of switch (service.connectionType())
  }

  protected void serviceConnected(XMPPIOService serv) {
    String[] routings =
      (String[])serv.getSessionData().get(PORT_ROUTING_TABLE_PROP_KEY);
    updateRoutings(routings, true);
    String addr =
      (String)serv.getSessionData().get(PORT_REMOTE_HOST_PROP_KEY);
    log.info("Connected to: " + addr);
    updateServiceDiscovery(addr, XMLNS + " connected");
    Map<String, String> method_params = new LinkedHashMap<String, String>();
    method_params.put("connected", addr);
    addOutPacket(new Packet(ClusterElement.createClusterMethodCall(
          getComponentId(), cluster_controller_id,
          StanzaType.set, ClusterMethods.UPDATE_NODES.toString(),
          method_params).getClusterElement()));
//     synchronized (waiting_packs) {
//       LinkedHashMap<Long, Packet> waiting_packets =
//         waiting_packs.get(serv.getRemoteAddress());
//       if (waiting_packets == null) {
//         waiting_packets = new LinkedHashMap<Long, Packet>();
//         waiting_ack.put(serv.getRemoteAddress(), waiting_packets);
//       }
//     }
  }

  @Override
  public void setProperties(Map<String, Object> props) {
    super.setProperties(props);
    //service_disco = (Boolean)props.get(RETURN_SERVICE_DISCO_KEY);
    identity_type = (String)props.get(IDENTITY_TYPE_KEY);

    //serviceEntity = new ServiceEntity(getName(), "external", "XEP-0114");
    serviceEntity = new ServiceEntity(XMLNS + " " + getName(), null, XMLNS);
    serviceEntity.addIdentities(
      new ServiceIdentity("component", identity_type, XMLNS + " " + getName()));
    connect_all = (Boolean)props.get(CONNECT_ALL_PROP_KEY);
    cluster_controller_id = (String)props.get(CLUSTER_CONTR_ID_PROP_KEY);
//     notify_admins = (Boolean)props.get(NOTIFY_ADMINS_PROP_KEY);
//     admins = (String[])props.get(ADMINS_PROP_KEY);
    connectionDelay = 5*SECOND;
    String[] cl_nodes = (String[])props.get(CLUSTER_NODES_PROP_KEY);
    if (cl_nodes != null) {
      for (String node: cl_nodes) {
        String host = JIDUtils.getNodeHost(node);
        log.config("Found cluster node host: " + host);
        if (!host.equals(getDefHostName())
          && (host.hashCode() > getDefHostName().hashCode() || connect_all)) {
          log.config("Trying to connect to cluster node: " + host);
          Map<String, Object> port_props = new LinkedHashMap<String, Object>();
          port_props.put(SECRET_PROP_KEY, SECRET_PROP_VAL);
          port_props.put(PORT_LOCAL_HOST_PROP_KEY, getDefHostName());
          port_props.put(PORT_TYPE_PROP_KEY, ConnectionType.connect);
          port_props.put(PORT_SOCKET_PROP_KEY, SocketType.plain);
          port_props.put(PORT_REMOTE_HOST_PROP_KEY, host);
          port_props.put(PORT_IFC_PROP_KEY, new String[] {host});
          port_props.put(MAX_RECONNECTS_PROP_KEY, 99999999);
          port_props.put(PORT_KEY, PORTS[0]);
          addWaitingTask(port_props);
          //reconnectService(port_props, connectionDelay);
        }
      }
    }
  }

  @Override
  public Map<String, Object> getDefaults(Map<String, Object> params) {
    Map<String, Object> props = super.getDefaults(params);
    props.put(RETURN_SERVICE_DISCO_KEY, RETURN_SERVICE_DISCO_VAL);
    props.put(IDENTITY_TYPE_KEY, IDENTITY_TYPE_VAL);
    if (params.get(CONNECT_ALL_PAR) == null
      || !((String)params.get(CONNECT_ALL_PAR)).equals("true")) {
      props.put(CONNECT_ALL_PROP_KEY, false);
    } else {
      props.put(CONNECT_ALL_PROP_KEY, true);
    }
    if (params.get(CLUSTER_NODES) != null) {
      String[] cl_nodes = ((String)params.get(CLUSTER_NODES)).split(",");
      for (int i = 0; i < cl_nodes.length; i++) {
        cl_nodes[i] = JIDUtils.getNodeHost(cl_nodes[i]);
      }
      props.put(CLUSTER_NODES_PROP_KEY, cl_nodes);
    } else {
      props.put(CLUSTER_NODES_PROP_KEY, new String[] {getDefHostName()});
    }
    props.put(CLUSTER_CONTR_ID_PROP_KEY,
      DEF_CLUST_CONTR_NAME + "@" + getDefHostName());
//     props.put(NOTIFY_ADMINS_PROP_KEY, NOTIFY_ADMINS_PROP_VAL);
//     if (params.get(GEN_ADMINS) != null) {
//       admins = ((String)params.get(GEN_ADMINS)).split(",");
//     } else {
//       admins = new String[] { "admin@localhost" };
//     }
//     props.put(ADMINS_PROP_KEY, admins);
    return props;
  }

  protected Map<String, Object> getParamsForPort(int port) {
    Map<String, Object> defs = new LinkedHashMap<String, Object>();
    defs.put(SECRET_PROP_KEY, SECRET_PROP_VAL);
    defs.put(PORT_TYPE_PROP_KEY, ConnectionType.accept);
    defs.put(PORT_SOCKET_PROP_KEY, SocketType.plain);
    defs.put(PORT_IFC_PROP_KEY, PORT_IFC_PROP_VAL);
    return defs;
  }

  protected int[] getDefPlainPorts() {
    return PORTS;
  }

  protected String getUniqueId(XMPPIOService serv) {
    //    return (String)serv.getSessionData().get(PORT_REMOTE_HOST_PROP_KEY);
    return serv.getRemoteAddress();
  }

  public void serviceStopped(XMPPIOService service) {
    super.serviceStopped(service);
    Map<String, Object> sessionData = service.getSessionData();
    String[] routings = (String[])sessionData.get(PORT_ROUTING_TABLE_PROP_KEY);
    if (routings != null) {
      updateRoutings(routings, false);
    }
    ConnectionType type = service.connectionType();
    if (type == ConnectionType.connect) {
      addWaitingTask(sessionData);
      //reconnectService(sessionData, connectionDelay);
    } // end of if (type == ConnectionType.connect)
    //    removeRouting(serv.getRemoteHost());
    String addr = (String)sessionData.get(PORT_REMOTE_HOST_PROP_KEY);
    log.info("Disonnected from: " + addr);
    updateServiceDiscovery(addr, XMLNS + " disconnected");
    Map<String, String> method_params = new LinkedHashMap<String, String>();
    method_params.put("disconnected", addr);
    addOutPacket(new Packet(ClusterElement.createClusterMethodCall(
          getComponentId(), cluster_controller_id,
          StanzaType.set, ClusterMethods.UPDATE_NODES.toString(),
          method_params).getClusterElement()));
    ++totalNodeDisconnects;
    int hour = TimeUtils.getHourNow();
    if (lastDayIdx != hour) {
      lastDayIdx = hour;
      lastDay[hour] = 0;
    }
    ++lastDay[hour];
    int minute = TimeUtils.getMinuteNow();
    if (lastHourIdx != minute) {
      lastHourIdx = minute;
      lastHour[minute] = 0;
    }
    ++lastHour[minute];
  }

  @Override
  protected String getServiceId(Packet packet) {
    try {
      return DNSResolver.getHostIP(JIDUtils.getNodeHost(packet.getTo()));
    } catch (UnknownHostException e) {
      log.warning("Uknown host exception for address: "
        + JIDUtils.getNodeHost(packet.getTo()));
      return JIDUtils.getNodeHost(packet.getTo());
    }
  }

  @Override
  public void serviceStarted(XMPPIOService serv) {
    super.serviceStarted(serv);
    log.info("cluster connection opened: " + serv.getRemoteAddress()
      + ", type: " + serv.connectionType().toString()
      + ", id=" + serv.getUniqueId());
//     String addr =
//       (String)service.getSessionData().get(PORT_REMOTE_HOST_PROP_KEY);
//     addRouting(addr);
    //    addRouting(serv.getRemoteHost());
    switch (serv.connectionType()) {
    case connect:
      // Send init xmpp stream here
      //XMPPIOService serv = (XMPPIOService)service;
      String remote_host =
        (String)serv.getSessionData().get(PORT_REMOTE_HOST_PROP_KEY);
      serv.getSessionData().put(XMPPIOService.HOSTNAME_KEY, remote_host);
      serv.getSessionData().put(PORT_ROUTING_TABLE_PROP_KEY,
        new String[] {remote_host, ".*@" + remote_host, ".*\\." + remote_host});
      String data =
        "<stream:stream"
        + " xmlns='" + XMLNS + "'"
        + " xmlns:stream='http://etherx.jabber.org/streams'"
        + " from='" + getDefHostName() + "'"
        + " to='" + remote_host + "'"
        + ">";
      log.info("cid: " + (String)serv.getSessionData().get("cid")
        + ", sending: " + data);
      serv.xmppStreamOpen(data);
      break;
    default:
      // Do nothing, more data should come soon...
      break;
    } // end of switch (service.connectionType())
  }

  public String xmppStreamOpened(XMPPIOService service,
    Map<String, String> attribs) {

    log.info("Stream opened: " + attribs.toString());

    switch (service.connectionType()) {
    case connect: {
      String id = attribs.get("id");
      service.getSessionData().put(XMPPIOService.SESSION_ID_KEY, id);
      String secret =
        (String)service.getSessionData().get(SECRET_PROP_KEY);
      try {
        String digest = Algorithms.hexDigest(id, secret, "SHA");
        log.finest("Calculating digest: id="+id+", secret="+secret
          +", digest="+digest);
        return "<handshake>" + digest + "</handshake>";
      } catch (NoSuchAlgorithmException e) {
        log.log(Level.SEVERE, "Can not generate digest for pass phrase.", e);
        return null;
      }
    }
    case accept: {
      String remote_host = attribs.get("from");
      service.getSessionData().put(XMPPIOService.HOSTNAME_KEY, remote_host);
      service.getSessionData().put(PORT_REMOTE_HOST_PROP_KEY, remote_host);
      service.getSessionData().put(PORT_ROUTING_TABLE_PROP_KEY,
        new String[] {remote_host, ".*@" + remote_host, ".*\\." + remote_host});
      String id = UUID.randomUUID().toString();
      service.getSessionData().put(XMPPIOService.SESSION_ID_KEY, id);
      return "<stream:stream"
        + " xmlns='" + XMLNS + "'"
        + " xmlns:stream='http://etherx.jabber.org/streams'"
        + " from='" + getDefHostName() + "'"
        + " to='" + remote_host + "'"
        + " id='" + id + "'"
        + ">";
    }
    default:
      // Do nothing, more data should come soon...
      break;
    } // end of switch (service.connectionType())
    return null;
  }

  public void xmppStreamClosed(XMPPIOService serv) {
    log.info("Stream closed.");
  }

  /**
   * Method <code>getMaxInactiveTime</code> returns max keep-alive time
   * for inactive connection. we shoulnd not really close external component
   * connection at all, so let's say something like: 1000 days...
   *
   * @return a <code>long</code> value
   */
  protected long getMaxInactiveTime() {
    return 1000*24*HOUR;
  }

  private void updateRoutings(String[] routings, boolean add) {
    if (add) {
      for (String route: routings) {
        try {
          addRegexRouting(route);
        } catch (Exception e) {
          log.warning("Can not add regex routing '" + route + "' : " + e);
        }
      }
    } else {
      for (String route: routings) {
        try {
          removeRegexRouting(route);
        } catch (Exception e) {
          log.warning("Can not remove regex routing '" + route + "' : " + e);
        }
      }
    }
  }

  private void updateServiceDiscovery(String jid, String name) {
    ServiceEntity item = new ServiceEntity(jid, null, name);
    //item.addIdentities(new ServiceIdentity("component", identity_type, name));
    log.info("Modifing service-discovery info: " + item.toString());
    serviceEntity.addItems(item);
  }

  public Element getDiscoInfo(String node, String jid) {
    if (jid != null && getName().equals(JIDUtils.getNodeNick(jid))) {
      return serviceEntity.getDiscoInfo(node);
    }
    return null;
  }

  public   List<Element> getDiscoFeatures() { return null; }

  public List<Element> getDiscoItems(String node, String jid) {
    if (getName().equals(JIDUtils.getNodeNick(jid))) {
      return serviceEntity.getDiscoItems(node, null);
    } else {
       return Arrays.asList(serviceEntity.getDiscoItem(null,
          JIDUtils.getNodeID(getName(), jid)));
    }
  }

  protected XMPPIOService getXMPPIOServiceInstance() {
    return new XMPPIOService();
  }

  public List<StatRecord> getStatistics() {
    List<StatRecord> stats = super.getStatistics();
    stats.add(new StatRecord(getName(), "Total disconnects", "long",
        totalNodeDisconnects, Level.INFO));

    stats.add(new StatRecord(getName(), "Last day disconnects", "array",
        Arrays.toString(lastDay), Level.FINE));
    stats.add(new StatRecord(getName(), "Last hour disconnects", "array",
        Arrays.toString(lastHour), Level.INFO));
    return stats;
  }

}
TOP

Related Classes of tigase.cluster.ClusterConnectionManager

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.