Subversion Repositories DevTools

Rev

Rev 7033 | Blame | Compare with Previous | Last modification | View Log | RSS feed

package com.erggroup.buildtool.daemon;

import com.erggroup.buildtool.daemon.BuildDaemon;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.net.ServerSocket;
import java.net.SocketTimeoutException;
import java.net.Socket;
import java.net.SocketException;
import java.io.*;

//----------------------------------------------------------------------------
// CLASS              : NagiosThread
//
// DESCRIPTION        : A class to provide Nagios support, with supplemental commands
//                      This is not an interactive command session
//                      It is intended to take one command and execute it
//                      The primary use is to provide nagios status
//
//
public class NagiosThread extends Thread {
    // Server socket
    private ServerSocket srv;

    // BuildDaemon
    private BuildDaemon mBuildDaemon;

    // Flag that indicates whether the poller is running or not.
    private volatile boolean isRunning = true;

    // Flag that indicates a request for an immediate kill.
    private volatile boolean mustDie = false;

    // Indicate the start time of the daemon
    //  Not nanosecond accurate, just an indication of when it was started
    //  Used to indicate when the daemon was started
    private final long startTime = System.currentTimeMillis();
    private final String startString = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(Calendar.getInstance().getTime());

    /**Logger
     * @attribute
     */
    private static final Logger mLogger = LoggerFactory.getLogger(NagiosThread.class);
    
    // Constructor.
    public NagiosThread(ServerSocket srv, BuildDaemon bd) {
        this.isRunning = true;
        this.srv = srv;
        this.mBuildDaemon = bd;
    }

    // Method for terminating the listener
    public void terminate() {
        mLogger.error("NagiosThread terminate");
        this.isRunning = false;
        try  {
            srv.close();
        }
        catch (IOException e) {
            mLogger.error("NagiosThread terminate Exception on srv.close: " + e.getMessage());
        }
    }
   
    /** 
     * Sleep and handle exceptions
     * @param time - Sleep duration in milliseconds
     */
    private void sleep(int time) {
        try {
            Thread.sleep(time);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    /**
     * This method start the thread and performs all the operations.
     */
    @Override
    public void run() {
        mLogger.error("NagiosThread Run");
        setName("Nagios");

        // Wait for connection from client.
        // Process one command than then close the socket
        // This is not an interactive session
        //
        while (isRunning) {
            Socket socket;
            try {
                socket = srv.accept();
                mLogger.info("Nagios Socket Accepted");
                processSocketRequest(socket);
                try {
                    socket.close();
                } catch (IOException e) {
                    mLogger.error("NagiosThread Exception on socket opr: " + e.getMessage());
                    sleep(1000);
                }
            }

            catch (IOException e) {
                mLogger.error("NagiosThread Exception on srv.accept: " + e.getMessage());
                sleep(1000);
            }
        }
        
        //
        // Request to terminate this thread
        // If a mustDie request has been received, then wait 1 second and exit
        // This is a nasty thing to do
        //
        if (mustDie) {
            terminate();
            sleep(1000);
            System.exit(5);
        }
    }
   
/**
 * Process one request    
 * @param socket    - Socket with data to process
 */
    void processSocketRequest(Socket socket)
    {

        // Open a reader to receive possible input
        //      Set read Timeout on the socket to guard against misbehaving users
        //      Insert a dummy 'status' command if no input is received in 3 seconds
        //
        try {
            socket.setSoTimeout(3000);
        } catch (SocketException e) {
            mLogger.error("NagiosThread Exception on setSoTimeout: " + e.getMessage());
        }
        
        ;
        try {
            BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            processRequest(socket, rd);
            rd.close();
        } catch (IOException e) {
            mLogger.error("NagiosThread Exception on getInputStream: " + e.getMessage());
        }
    }
    
    /** Process the request
     *  Have a BufferedReader that will contain the socket data
     *  @param socket - socket for writing response
     *  @param rd - BufferedReader for the socket data
     */
    void processRequest(Socket socket, BufferedReader rd) {
        String request = null;
        String[] requestTokens;
        String requestType = null;
        
        try
        {
            request = rd.readLine();
            mLogger.warn("Nagios request:" + request);
            if (request == null) {
                return;
            }
            
            //
            //  Simple sanity check on request
            //  First 3 chars must by alphabetic
            //
            if (!request.matches("^[A-Za-z]{3}.*")) {
                mLogger.warn("Nagios request not sane");
                return;
            }
            
            //  Handle restful requests as http requests
            //  Expect rest type url http://server:port/request/arg1/arg2
            //
            if (request.matches(".*\\s+HTTP/\\d\\.\\d$"))
            {
                try {
                    requestTokens = request.split("\\s+");
                    requestType = requestTokens[0];
                    requestTokens = requestTokens[1].substring(1).split("/");
                    request = requestTokens[0];
                }
                catch (IndexOutOfBoundsException e1) {
                    mLogger.error("NagiosThread cannot parse HTTP header");
                    return; 
                }
            }
        }
        catch ( SocketTimeoutException s)
        {
            mLogger.warn("Nagios Socket read timeout");
            request = "status";
            
        } catch (IOException e) {
            mLogger.error("NagiosThread Exception on processRequest: " + e.getMessage());
            return;
        }
       
        //  Process the users request and generate a response
        
        StringBuilder resp = new StringBuilder();
        generateResponse(request, resp);

        //
        //  Generate the output
        //  Wrap in an HTTP header if required
        //  Only really valid for 'estatus' as the content type may not be accurate
        //
        try 
        {
            BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            if(requestType != null)
            {
                wr.write("HTTP/1.0 200 OK\n");
                wr.write("Content-Type: application/json\n");
                wr.write("Content-length: "+ resp.length()+"\n");
                wr.write("\n");
            }
            wr.write (resp.toString());
            
            //  Flush out the input stream
            wr.flush();
            wr.close();

        } catch (IOException e) {
            mLogger.error("NagiosThread caught Exception writing to port:" + e.getMessage() );
        }
    }
    
    /**
     * Generate a response to the Nagios (or user ) request 
     * 
     * @param request - Request to process
     * @param resp - Build response into this StringBuilder
     */
void generateResponse(String request, StringBuilder resp) {

    if (request.equals("status"))
    {
        // Write the status message to the output stream
        // Currently this is too simple, but it is a start
        BuildDaemon.NagiosInfo nagInfo = new BuildDaemon.NagiosInfo();
       
        mBuildDaemon.checkThreads(nagInfo);
        int reasonCount = nagInfo.extendedReason.size();
        if (  reasonCount <= 0) {
            resp.append("Build Daemon status: OK\n");
        } else {
            resp.append("Build Daemon status: NOT HAPPY\n");
            mLogger.error("NagiosThread: NOT HAPPY");
            
            // Extended information - Reasons for being not happy

            if (reasonCount > 0)
            {
                resp.append("Reasons:" + "\n");
                for( int ii = 0; ii < reasonCount ;ii++)
                {
                    resp.append("    " + nagInfo.extendedReason.get(ii) + "\n");
                }
            }
        }

        resp.append("Version: "+ this.getClass().getPackage().getImplementationVersion() + "\n");
        resp.append("HostName: "+ BuildDaemon.mHostname + "\n");
        resp.append("Started: "+ startString + "\n");
        resp.append("UpTime (S): "+ (System.currentTimeMillis() - startTime) / 1000 + "\n");
        resp.append("Threads:" + nagInfo.threadCount + "\n");
        resp.append("Alive:" + nagInfo.threadAliveCount + "\n");
        resp.append("Masters:" + nagInfo.masterCount + "\n");
        resp.append("Slaves:" + nagInfo.slaveCount + "\n");

        if (BuildDaemon.mShutDown) {
            resp.append("Shutdown Request in Progress\n");
        }
        
        File cwd = new File(".");
        resp.append( "Usable space: " + cwd.getUsableSpace() + "\n");

    }
    else if ( request.equals( "help") ) {
        resp.append("Build Daemon Command Interface: Supported commands\n");
        resp.append("    status   - Nagios status\n");
        resp.append("    estatus  - Extended status\n");
        resp.append("    shutdown - shutdown buildtool\n");
        resp.append("    kill     - Immediate exit\n");
        resp.append("    help     - This message\n");

    }
    else if ( request.equals( "estatus") ) {
        LinkedHashMap<String, Object> basicStatus = new LinkedHashMap<String, Object>();
        BuildDaemon.NagiosInfo nagInfo = new BuildDaemon.NagiosInfo();
        mBuildDaemon.checkThreads(nagInfo);
        
        basicStatus.put("Vesion", this.getClass().getPackage().getImplementationVersion());
        basicStatus.put("HostName", BuildDaemon.mHostname );
        basicStatus.put("Start Text", startString );
        basicStatus.put("Start Time", Long.valueOf(startTime) );
        basicStatus.put("UpTime ", Long.valueOf(System.currentTimeMillis() - startTime) );
        basicStatus.put("Threads" , Integer.valueOf(nagInfo.threadCount) );
        basicStatus.put("Alive" , Integer.valueOf(nagInfo.threadAliveCount));
        basicStatus.put("Masters", Integer.valueOf(nagInfo.masterCount));
        basicStatus.put("Slaves", Integer.valueOf(nagInfo.slaveCount));
        basicStatus.put("UnHappy" , Integer.valueOf(nagInfo.extendedReason.size()) );
        basicStatus.put("mShutDown", Boolean.valueOf(BuildDaemon.mShutDown));
                              
        LinkedHashMap<String, Object> dstatus = new LinkedHashMap<String, Object>();
        mBuildDaemon.extendedStatus(dstatus);
       
        LinkedHashMap<String, Object> status = new LinkedHashMap<String, Object>();
        status.put("Basic", basicStatus);
        status.put("Daemon", dstatus);
        
        String outBody = dumpJson(status, "");
        resp.append(outBody);
    }
    else if ( request.equals( "shutdown") ) {
        resp.append("Gracefull shutdown\n");
        mLogger.error("Gracefull shutdown requested");
        BuildDaemon.mShutDown = true;
    }
    else if ( request.equals( "kill") ) {
        resp.append("Will commit suicide\n");
        mLogger.error("Immediate program termination");
        mustDie = true;
        isRunning = false;
    }
    else {
        String eMgs = "Build Daemon Command Interface: Unknown command:" + request;
        mLogger.warn( eMgs );
        resp.append(eMgs);
        resp.append("\n");
    }
}

    /**Display a simple-ish hashmap as JSON
     * Only handles some basic types of data
     *      - Integers, Longs
     *      - Strings
     *      - Boolean
     *      - null
     *      - Hash Map (recursively)
     * 
     * @param estatus   Hash Map to display
     * @param indent    Indentation string
     * @return json encoded representation of the hashmap
     */
    @SuppressWarnings("unchecked")
    private String dumpJson(LinkedHashMap<String, Object> estatus, String indent)
    {
        StringBuilder s = new StringBuilder();
        String prefix = "";
        s.append(indent).append("{\n");
        
        for (Entry<String, Object> entry : estatus.entrySet()) {
            String key = entry.getKey();
            Object obj = entry.getValue();
            String objType = "";
            s.append(prefix);
            prefix = ",\n";
            
            s.append(indent).append('"').append(key).append('"').append(": ");
            if ( obj != null) {
                objType = obj.getClass().getName();
            }
            
            if ( obj == null ) {
                s.append("null");
            } else  if (objType.endsWith(".Integer") || objType.endsWith(".Long") ) {
                s.append(obj.toString());
                
            } else  if (objType.endsWith(".String") ) {
                s.append('"').append(obj.toString()).append('"');
                
            } else  if (objType.endsWith(".Boolean") ) {
                s.append(obj.toString());
                
            } else  if (objType.endsWith(".LinkedHashMap") ) {
                s.append(dumpJson((LinkedHashMap<String, Object>) obj, indent + "    "));
                
            } else {
                s.append('"').append("Unknown Class:").append(objType).append('"');
            }
        }
        s.append('\n').append(indent).append("}");
        return s.toString();
    }
}