org.roqmessaging.management.HostConfigManager.java Source code

Java tutorial

Introduction

Here is the source code for org.roqmessaging.management.HostConfigManager.java

Source

/**
 * Copyright 2012 EURANOVA
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
    
   http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package org.roqmessaging.management;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import junit.framework.Assert;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.log4j.Logger;
import org.roqmessaging.core.Exchange;
import org.roqmessaging.core.Monitor;
import org.roqmessaging.core.RoQConstant;
import org.roqmessaging.core.ShutDownMonitor;
import org.roqmessaging.core.interfaces.IStoppable;
import org.roqmessaging.core.launcher.ExchangeLauncher;
import org.roqmessaging.core.launcher.MonitorLauncher;
import org.roqmessaging.core.utils.RoQSerializationUtils;
import org.roqmessaging.core.utils.RoQUtils;
import org.roqmessaging.management.config.internal.FileConfigurationReader;
import org.roqmessaging.management.config.internal.HostConfigDAO;
import org.roqmessaging.management.launcher.hook.ShutDownSender;
import org.roqmessaging.scaling.ScalingProcess;
import org.roqmessaging.scaling.launcher.ScalingProcessLauncher;
import org.zeromq.ZMQ;

/**
 * Class HostConfigManager
 * <p>
 * Description: Responsible for the local management of the queue elements. For
 * each host it will track the list of monitors and exchanges. Notice that the
 * host config manager implement a server pattern that exposes services to the
 * monitor management.
 * 
 * @author sskhiri
 */
public class HostConfigManager implements Runnable, IStoppable {
    // ZMQ config
    private ZMQ.Socket clientReqSocket = null;
    private ZMQ.Socket globalConfigSocket = null;
    private ZMQ.Context context;
    // Logger
    private Logger logger = Logger.getLogger(HostConfigManager.class);
    // Host manager config
    private volatile boolean running = false;
    // The host configuration manager properties
    private HostConfigDAO properties = null;
    // Local configuration maintained by the host manager
    // [qName, the monitor]
    private HashMap<String, String> qMonitorMap = null;
    // [qName, monitor stat server address]
    private HashMap<String, String> qMonitorStatMap = null;
    // [qName, list of Xchanges]
    private HashMap<String, List<String>> qExchangeMap = null;
    //[qName, Scaling process shutdown port (on the same machine as host)] 
    //TODO starting the process, register it and deleting it when stoping
    private HashMap<String, Integer> qScalingProcessAddr = null;
    //The shutdown monitor
    private ShutDownMonitor shutDownMonitor = null;
    //The lock to avoid any race condition
    private Lock lockRemoveQ = new ReentrantLock();
    //Network & IP address Configuration
    private boolean useNif = false;

    private RoQSerializationUtils serializationUtils = null;

    //cvoicu: Addresses of the all other exchanges to be used as seed.
    private String seeds = "";
    private String propertyFile = "";

    /**
     * Constructor
     * @param propertyFile the location of the property file
     */
    public HostConfigManager(String propertyFile) {
        try {
            //cvoicu:
            this.propertyFile = propertyFile;

            serializationUtils = new RoQSerializationUtils();
            // Global init
            FileConfigurationReader reader = new FileConfigurationReader();
            this.properties = reader.loadHCMConfiguration(propertyFile);
            if (this.properties.getNetworkInterface() == null)
                useNif = false;
            else {
                useNif = true;
            }
            logger.info(this.properties.toString());
            // ZMQ Init
            this.context = ZMQ.context(1);
            this.clientReqSocket = context.socket(ZMQ.REP);
            this.clientReqSocket.bind("tcp://*:5100");
            this.globalConfigSocket = context.socket(ZMQ.REQ);
            this.globalConfigSocket.connect("tcp://" + this.properties.getGcmAddress() + ":5000");
            // Init the map
            this.qExchangeMap = new HashMap<String, List<String>>();
            this.qMonitorMap = new HashMap<String, String>();
            this.qMonitorStatMap = new HashMap<String, String>();
            this.qScalingProcessAddr = new HashMap<String, Integer>();
            // Init the shutdown monitor
            this.shutDownMonitor = new ShutDownMonitor(5101, this);
            new Thread(this.shutDownMonitor).start();
        } catch (ConfigurationException e) {
            logger.error("Error while reading configuration in " + propertyFile, e);
        }
    }

    /**
     * @see java.lang.Runnable#run()
     */
    public void run() {
        this.running = true;
        //1. Register to the global configuration
        registerHost();
        // ZMQ init
        ZMQ.Poller items = new ZMQ.Poller(1);
        items.register(this.clientReqSocket);

        // 2. Start the main run of the monitor
        while (this.running) {
            items.poll(100);
            if (items.pollin(0)) { // Comes from a client
                logger.debug("Receiving Incoming request @host...");
                String[] info = new String(clientReqSocket.recv(0)).split(",");
                int infoCode = Integer.parseInt(info[0]);
                logger.debug("Start analysing info code = " + infoCode);
                switch (infoCode) {

                // Receive a create queue request on the local host manager
                // (likely from the LogicalQFactory
                case RoQConstant.CONFIG_CREATE_QUEUE:
                    logger.debug("Recieveing create Q request from a client ");
                    if (info.length == 2) {
                        String qName = info[1];
                        logger.debug("The request format is valid with 2 parts, Q to create:  " + qName);

                        // 1. Start the monitor
                        String monitorAddress = startNewMonitorProcess(qName);

                        // 2. Start the exchange
                        // 2.1. Getting the monitor stat address
                        // 2.2. Start the exchange
                        boolean xChangeOK = true;//startNewExchangeProcess(qName, this.qMonitorMap.get(qName), this.qMonitorStatMap.get(qName));

                        //2.3. Start the scaling process
                        boolean scalingOK = startNewScalingProcess(qName);

                        // if OK send OK
                        if (monitorAddress != null & xChangeOK && scalingOK) {
                            logger.info("Successfully created new Q for " + qName + "@" + monitorAddress);
                            this.clientReqSocket.send(
                                    (Integer.toString(RoQConstant.CONFIG_CREATE_QUEUE_OK) + "," + monitorAddress)
                                            .getBytes(),
                                    0);

                        } else {
                            logger.error(
                                    "The create queue request has failed at the monitor host,check log (when starting launching scripts");
                            this.clientReqSocket.send(
                                    (Integer.toString(RoQConstant.CONFIG_CREATE_QUEUE_FAIL) + "," + monitorAddress)
                                            .getBytes(),
                                    0);
                        }
                    } else {
                        logger.error(
                                "The create queue request sent does not contain 3 part: ID, quName, Monitor host");
                        this.clientReqSocket.send(
                                (Integer.toString(RoQConstant.CONFIG_CREATE_QUEUE_FAIL) + ", ").getBytes(), 0);
                    }
                    //start 3 hosts with exchanges on them:

                    globalConfigSocket.send(new Integer(1234503).toString() + ", 3");
                    String rsp = new String(globalConfigSocket.recv());
                    logger.info("cvoicu: reponse of gcm at launching init hosts: " + rsp);

                    String raspuns = "";
                    while (raspuns.compareTo("1234505") != 0) {
                        globalConfigSocket.send(new Integer(1234505).toString() + ", 3");
                        raspuns = new String(globalConfigSocket.recv());
                        logger.info("cvoicu: reponse of gcm at launching init hosts: " + raspuns);
                        try {
                            Thread.sleep(3000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }

                    globalConfigSocket.send(new Integer(RoQConstant.GET_HOSTS).toString() + ", " + info[1]);
                    String hsts = new String(globalConfigSocket.recv());
                    String[] hostsList = hsts.split("#");
                    int i = 0;
                    LogicalQFactory qFactory = new LogicalQFactory(this.properties.getGcmAddress());

                    while (i < hostsList.length) {
                        if (hostsList[i].compareTo(RoQUtils.getInstance().getLocalIP()) != 0) {
                            qFactory.createExchange(info[1], hostsList[i]);
                        }
                        i++;
                    }

                    break;

                case RoQConstant.CONFIG_REMOVE_QUEUE:
                    logger.debug("Recieveing remove Q request from a client ");
                    if (info.length == 2) {
                        String qName = info[1];
                        removingQueue(qName);
                        // Removing Q information
                        this.qExchangeMap.remove(qName);
                        this.qMonitorMap.remove(qName);
                        this.qMonitorStatMap.remove(qName);
                        this.qScalingProcessAddr.remove(qName);
                        this.clientReqSocket.send((Integer.toString(RoQConstant.OK) + ", ").getBytes(), 0);
                    } else {
                        logger.error("The remove queue request sent does not contain 2 part: ID, quName");
                        this.clientReqSocket.send(
                                (Integer.toString(RoQConstant.CONFIG_CREATE_QUEUE_FAIL) + ", ").getBytes(), 0);
                    }
                    break;

                case RoQConstant.CONFIG_CREATE_EXCHANGE:
                    logger.debug("Recieveing create XChange request from a client ");
                    if (info.length == 4) {
                        String qName = info[1];
                        // Qname, monitorhost, monitorstat host
                        if (startNewExchangeProcess(qName, info[2], info[3])) {
                            this.clientReqSocket.send((Integer.toString(RoQConstant.OK)).getBytes(), 0);
                        } else {
                            this.clientReqSocket.send((Integer.toString(RoQConstant.FAIL)).getBytes(), 0);
                        }
                    } else {
                        logger.error(
                                "The create new exchange does not contain 4 parts: ID, Qname, monitor, monitor host");
                        this.clientReqSocket
                                .send((Integer.toString(RoQConstant.CONFIG_CREATE_QUEUE_FAIL)).getBytes(), 0);
                    }
                    break;
                case RoQConstant.CONFIG_INFO_EXCHANGE:
                    logger.debug("Recieveing  get XChange INFO from a client ");
                    try {
                        //Answer in3 parts
                        //[OK or FAIL], [Number of exchange on host], [max limit of exchange defined in property]
                        this.clientReqSocket.send((Integer.toString(RoQConstant.OK)).getBytes(), ZMQ.SNDMORE);
                        this.clientReqSocket.send((Integer.toString(this.getExchangeNumber())).getBytes(),
                                ZMQ.SNDMORE);
                        this.clientReqSocket
                                .send((Integer.toString(this.properties.getMaxNumberEchanges())).getBytes(), 0);
                    } catch (Exception e) {
                        this.clientReqSocket.send((Integer.toString(RoQConstant.FAIL)).getBytes(), 0);
                    }

                    break;
                }
            }
        }
        stopAllRunningQueueOnHost();
        unregisterHostFromConfig();
        logger.info("Closing the client & global config sockets.");
        this.clientReqSocket.setLinger(0);
        this.globalConfigSocket.setLinger(0);
        this.clientReqSocket.close();
        this.globalConfigSocket.close();
    }

    /**
     * @param qName the name of queue for which we need to create the scaling process
     * @param port the listener port on wich the sclaing process will scubscribe to configuration update
     * @return true if the creation was OK
     */
    private boolean startNewScalingProcess(String qName) {
        if (this.qMonitorStatMap.containsKey(qName)) {
            //1. Compute the stat monitor port+2
            int basePort = this.serializationUtils.extractBasePort(this.qMonitorStatMap.get(qName));
            basePort += 2;
            //2. Check wether we need to launch it locally or in its own process
            if (this.properties.isQueueInHcmVm()) {
                logger.info("Starting the scaling process  for queue " + qName + ", using a listener port= "
                        + basePort + ", GCM =" + this.properties.getGcmAddress());
                //Local startup in the same VM as the host config Monitor
                ScalingProcess scalingProcess = new ScalingProcess(this.properties.getGcmAddress(), qName,
                        basePort);
                //Here is the problem we still do not have registred the queue at the GCM
                //scalingProcess.subscribe();
                // Launch the thread
                new Thread(scalingProcess).start();
            } else {
                //Start in its own VM
                // 2. Launch script
                try {
                    ProcessBuilder pb = new ProcessBuilder("java",
                            "-Djava.library.path=" + System.getProperty("java.library.path"), "-cp",
                            System.getProperty("java.class.path"), ScalingProcessLauncher.class.getCanonicalName(),
                            this.properties.getGcmAddress(), qName, new Integer((basePort)).toString());
                    logger.debug("Starting: " + pb.command());
                    final Process process = pb.start();
                    pipe(process.getErrorStream(), System.err);
                    pipe(process.getInputStream(), System.out);
                } catch (IOException e) {
                    logger.error("Error while executing script", e);
                    return false;
                }
            }
            //4. Add the configuration information
            logger.debug("Storing scaling process information");
            this.qScalingProcessAddr.put(qName, (basePort + 1));
        } else {
            return false;
        }
        return true;
    }

    /**
     * Remove all queues delcared on this host. This operation is part of the cleaning 
     * house before closing the host.
     */
    private void stopAllRunningQueueOnHost() {
        List<String> toRemove = new ArrayList<String>(this.qMonitorMap.keySet());
        for (String qName : this.qMonitorMap.keySet()) {
            logger.info("Cleaning host - removing  " + qName);
            this.removingQueue(qName);
        }
        for (String qName : toRemove) {
            // Removing Q information
            this.qExchangeMap.remove(qName);
            this.qMonitorMap.remove(qName);
            this.qMonitorStatMap.remove(qName);
            this.qScalingProcessAddr.remove(qName);
        }

    }

    /**
     * @return the total number of exchanges on the host
     */
    private int getExchangeNumber() {
        int total = 0;
        for (String queue : this.qExchangeMap.keySet()) {
            total += this.qExchangeMap.get(queue).size();
        }
        return total;
    }

    /**
     * Unregister the host from the configuration management when shutdown.
     */
    private void unregisterHostFromConfig() {
        logger.info("UN-Registration process started");
        if (useNif)
            Assert.assertNotNull(this.properties.getNetworkInterface());
        this.globalConfigSocket
                .send((new Integer(RoQConstant.CONFIG_REMOVE_HOST).toString() + ","
                        + (!(useNif) ? RoQUtils.getInstance().getLocalIP()
                                : RoQUtils.getInstance().getLocalIP(this.properties.getNetworkInterface())))
                                        .getBytes(),
                        0);
        String info[] = new String(this.globalConfigSocket.recv(0)).split(",");
        int infoCode = Integer.parseInt(info[0]);
        logger.debug("Start analysing info code = " + infoCode);
        if (infoCode != RoQConstant.OK) {
            throw new IllegalStateException("The global config manager cannot register us ..");
        }
        logger.info("UN-Registration process sucessfull");
    }

    /**
     * Register the host config manager to the global configration
     */
    private void registerHost() throws IllegalStateException {
        logger.info("Registration process started");
        if (useNif)
            Assert.assertNotNull(this.properties.getNetworkInterface());
        this.globalConfigSocket
                .send((new Integer(RoQConstant.CONFIG_ADD_HOST).toString() + ","
                        + (!(useNif) ? RoQUtils.getInstance().getLocalIP()
                                : RoQUtils.getInstance().getLocalIP(this.properties.getNetworkInterface())))
                                        .getBytes(),
                        0);
        String info[] = new String(this.globalConfigSocket.recv(0)).split(",");
        int infoCode = Integer.parseInt(info[0]);
        logger.debug("Start analysing info code = " + infoCode);
        if (infoCode != RoQConstant.OK) {
            throw new IllegalStateException("The global config manager cannot register us ..");
        }
        logger.info("Registration process sucessfull");

    }

    /**
     * Remove a complete queue: 1. Sends a shut down request to the
     * corresponding monitor 2. The monitor will send a shut down request to all
     * exchanges that it knows
     * 
     * @param qName
     *            the logical Q name to remove
     */
    private void removingQueue(String qName) {
        try {
            this.lockRemoveQ.lock();
            logger.debug("Removing Q  " + qName);
            String monitorAddress = this.qMonitorMap.get(qName);
            // The address is the address of the base monitor, we need to
            // extract
            // the port and make +5
            // to get the shutdown monitor thread
            int basePort = this.serializationUtils.extractBasePort(monitorAddress);
            String portOff = monitorAddress.substring(0, monitorAddress.length() - "xxxx".length());
            logger.info("Sending Remove Q request to " + portOff + (basePort + 5));
            // 2. Send the remove message to the monitor
            // The monitor will stop all the exchanges during its shut down
            ShutDownSender shutDownSender = new ShutDownSender(portOff + (basePort + 5));
            shutDownSender.shutdown();
            //3. Stopping the scaling process
            if (this.qScalingProcessAddr.containsKey(qName)) {
                shutDownSender.setAddress(portOff + this.qScalingProcessAddr.get(qName).toString());
                shutDownSender.shutdown();
            }
            //The caller must remove the queue.
        } finally {
            this.lockRemoveQ.unlock();
        }
    }

    /**
     * Start a new exchange process
     * <p>
     * 1. Check the number of local xChange present in the host 2. Start a new
     * xChange with port config + nchange
     * 
     * @param qName
     *            the name of the queue to create
     * @return true if the creation process worked well
     */
    private boolean startNewExchangeProcess(String qName, String monitorAddress, String monitorStatAddress) {
        if (monitorAddress == null || monitorStatAddress == null) {
            logger.error("The monitor or the monitor stat server is null", new IllegalStateException());
            return false;
        }
        // 1. Get the number of installed queues on this host
        int number = 0;
        for (String q_i : this.qExchangeMap.keySet()) {
            List<String> xChanges = this.qExchangeMap.get(q_i);
            number += xChanges.size();
        }
        // 2. Assigns a front port and a back port
        logger.debug(" This host contains already " + number + " Exchanges");
        //x4 = Front, back, Shutdown, prod request
        int frontPort = this.properties.getExchangeFrontEndPort() + number * 4;
        // 3 because there is the front, back and the shut down
        int backPort = frontPort + 1;
        String ip = RoQUtils.getInstance().getLocalIP();

        int logPort = frontPort + 10000;
        seeds = seeds + ip + ":" + logPort + "#";
        globalConfigSocket.send(new Integer(RoQConstant.GET_SEEDS).toString() + ", asks for seeds");
        seeds = seeds + new String(globalConfigSocket.recv());
        //System.out.print(seeds+" "+useLog);

        if (this.properties.isExchangeInHcmVm()) {
            // We must start the thread in the same process
            Exchange exchange = new Exchange(frontPort, backPort, monitorAddress, monitorStatAddress,
                    ip + ":" + logPort, seeds, propertyFile);
            // Launch the thread
            new Thread(exchange).start();
        } else {
            // We start the exchange in its own process
            // Launch script
            try {
                ProcessBuilder pb = new ProcessBuilder("java",
                        "-Djava.library.path=" + System.getProperty("java.library.path"), "-cp",
                        System.getProperty("java.class.path"), "-Xmx" + this.properties.getExchangeHeap() + "m",
                        "-XX:+UseConcMarkSweepGC", ExchangeLauncher.class.getCanonicalName(),
                        new Integer(frontPort).toString(), new Integer(backPort).toString(), monitorAddress,
                        monitorStatAddress, ip + ":" + logPort, seeds, propertyFile);
                logger.info("Starting: " + pb.command());
                final Process process = pb.start();
                pipe(process.getErrorStream(), System.err);
                pipe(process.getInputStream(), System.out);
                globalConfigSocket.send(new Integer(RoQConstant.ADD_SEED).toString() + "," + ip + ":" + logPort);
                globalConfigSocket.recv();
            } catch (IOException e) {
                logger.error("Error while executing script", e);
                return false;
            }
        }

        if (this.qExchangeMap.containsKey(qName)) {
            this.qExchangeMap.get(qName).add("tcp://" + ip + ":" + frontPort);
            logger.debug("Storing Xchange info: " + "tcp://" + ip + ":" + frontPort);
        } else {
            List<String> xChange = new ArrayList<String>();
            xChange.add("tcp://" + ip + ":" + frontPort);
            this.qExchangeMap.put(qName, xChange);
            logger.debug("Storing Xchange info: " + "tcp://" + ip + ":" + frontPort);
        }

        return true;
    }

    /**
     * @return the monitor port
     */
    private int getMonitorPort() {
        return (this.properties.getMonitorBasePort() + this.qMonitorMap.size() * 6);
    }

    /**
     * @return the monitor stat port
     */
    private int getStatMonitorPort() {
        //By for because the stat monitor starts on port, its shutdown on port+1, the scaling process on 
        //port+2 and its shuto down process on port +3.
        return (this.properties.getStatMonitorBasePort() + this.qMonitorMap.size() * 4);
    }

    /**
     * Start a new Monitor process
     * <p>
     * 1. Check the number of local monitor present in the host 2. Start a new
     * monitor with port config + nMonitor*4 because the monitor needs to book 4
     * ports + stat
     * 
     * @param qName
     *            the name of the queue to create
     * @return the monitor address as tcp://IP:port of the newly created monitor
     *         +"," tcp://IP: statport
     */
    private String startNewMonitorProcess(String qName) {
        // 1. Get the number of installed queues on this host
        int frontPort = getMonitorPort();
        int statPort = getStatMonitorPort();
        logger.debug(" This host contains already " + this.qMonitorMap.size() + " Monitor");
        String argument = frontPort + " " + statPort;
        logger.debug("Starting monitor process by script launch on " + argument);
        //Monitor configuration
        String monitorAddress = "tcp://" + RoQUtils.getInstance().getLocalIP() + ":" + frontPort;
        String statAddress = "tcp://" + RoQUtils.getInstance().getLocalIP() + ":" + statPort;
        //Checking whether we must create the monitor, stat monitor and the scaling process in the same VM
        if (this.properties.isQueueInHcmVm()) {
            logger.info("Creating the Monitor of the " + qName + " on the same VM as the local HCM");
            Monitor monitor = new Monitor(frontPort, statPort, qName,
                    new Integer(this.properties.getStatPeriod()).toString());
            new Thread(monitor).start();
        } else {
            // 2. Launch script in its onw VM
            // ProcessBuilder pb = new ProcessBuilder(this.monitorScript,
            // argument);
            ProcessBuilder pb = new ProcessBuilder("java",
                    "-Djava.library.path=" + System.getProperty("java.library.path"), "-cp",
                    System.getProperty("java.class.path"), MonitorLauncher.class.getCanonicalName(),
                    new Integer(frontPort).toString(), new Integer(statPort).toString(), qName,
                    new Integer(this.properties.getStatPeriod()).toString());
            try {
                logger.debug("Starting: " + pb.command());
                final Process process = pb.start();
                pipe(process.getErrorStream(), System.err);
                pipe(process.getInputStream(), System.out);
            } catch (IOException e) {
                logger.error("Error while executing script", e);
                return null;
            }
        }
        //add the monitor configuration
        this.qMonitorMap.put(qName, (monitorAddress));
        this.qMonitorStatMap.put(qName, statAddress);
        return monitorAddress + "," + statAddress;
    }

    private static void pipe(final InputStream src, final PrintStream dest) {
        new Thread(new Runnable() {
            public void run() {
                try {
                    byte[] buffer = new byte[1024];
                    for (int n = 0; n != -1; n = src.read(buffer)) {
                        dest.write(buffer, 0, n);
                    }
                } catch (IOException e) { // just exit
                }
            }
        }).start();
    }

    /**
     * 
     */
    public void shutDown() {
        this.running = false;
    }

    /**
     * @see org.roqmessaging.core.interfaces.IStoppable#getName()
     */
    public String getName() {
        return "Host config manager " + RoQUtils.getInstance().getLocalIP();
    }

    /**
     * @return the qMonitorMap
     */
    public HashMap<String, String> getqMonitorMap() {
        return qMonitorMap;
    }

    /**
     * @param qMonitorMap
     *            the qMonitorMap to set
     */
    public void setqMonitorMap(HashMap<String, String> qMonitorMap) {
        this.qMonitorMap = qMonitorMap;
    }

    /**
     * Use the encapsulation to let the shutdown monitor manage all shutdown 
     * related actions.
     * @return the shutDownMonitor
     */
    public ShutDownMonitor getShutDownMonitor() {
        return shutDownMonitor;
    }

}