Subversion Repositories DevTools

Rev

Go to most recent revision | Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
6914 dpurdie 1
package com.erggroup.buildtool.daemon;
2
 
3
import com.erggroup.buildtool.daemon.BuildDaemon;
4
 
5
import org.apache.log4j.Logger;
6
 
7
import java.text.SimpleDateFormat;
8
import java.util.Calendar;
9
import java.util.LinkedHashMap;
10
import java.util.Map.Entry;
11
import java.net.ServerSocket;
12
import java.net.SocketTimeoutException;
13
import java.net.Socket;
14
import java.net.SocketException;
15
import java.io.*;
16
 
17
//----------------------------------------------------------------------------
18
// CLASS              : NagiosThread
19
//
20
// DESCRIPTION        : A class to provide Nagios support, with supplemental commands
21
//                      This is not an interactive command session
22
//                      It is intended to take one command and execute it
23
//                      The primary use is to provide nagios status
24
//
25
//
26
public class NagiosThread extends Thread {
27
    // Server socket
28
    private ServerSocket srv;
29
 
30
    // BuildDaemon
31
    private BuildDaemon mBuildDaemon;
32
 
33
    // Flag that indicates whether the poller is running or not.
34
    private volatile boolean isRunning = true;
35
 
36
    // Flag that indicates a request for an immediate kill.
37
    private volatile boolean mustDie = false;
38
 
39
    // Indicate the start time of the daemon
40
    //  Not nanosecond accurate, just an indication of when it was started
41
    //  Used to indicate when the daemon was started
42
    private final long startTime = System.currentTimeMillis();
43
    private final String startString = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss").format(Calendar.getInstance().getTime());
44
 
45
    /**Logger
46
     * @attribute
47
     */
48
    private static final Logger mLogger = Logger.getLogger(NagiosThread.class);
49
 
50
    // Constructor.
51
    public NagiosThread(ServerSocket srv, BuildDaemon bd) {
52
        this.isRunning = true;
53
        this.srv = srv;
54
        this.mBuildDaemon = bd;
55
    }
56
 
57
    // Method for terminating the listener
58
    public void terminate() {
59
        mLogger.fatal("NagiosThread terminate");
60
        this.isRunning = false;
61
        try  {
62
            srv.close();
63
        }
64
        catch (IOException e) {
65
            mLogger.fatal("NagiosThread terminate Exception on srv.close: " + e.getMessage());
66
        }
67
    }
68
 
69
    /** 
70
     * Sleep and handle exceptions
71
     * @param time - Sleep duration in milliseconds
72
     */
73
    private void sleep(int time) {
74
        try {
75
            Thread.sleep(time);
76
        } catch (InterruptedException e) {
77
            Thread.currentThread().interrupt();
78
        }
79
    }
80
 
81
    /**
82
     * This method start the thread and performs all the operations.
83
     */
84
    @Override
85
    public void run() {
86
        mLogger.fatal("NagiosThread Run");
87
        setName("Nagios");
88
 
89
        // Wait for connection from client.
90
        // Process one command than then close the socket
91
        // This is not an interactive session
92
        //
93
        while (isRunning) {
94
            Socket socket;
95
            try {
96
                socket = srv.accept();
97
                mLogger.info("Nagios Socket Accepted");
98
                processSocketRequest(socket);
99
                try {
100
                    socket.close();
101
                } catch (IOException e) {
102
                    mLogger.fatal("NagiosThread Exception on socket opr: " + e.getMessage());
103
                    sleep(1000);
104
                }
105
            }
106
 
107
            catch (IOException e) {
108
                mLogger.fatal("NagiosThread Exception on srv.accept: " + e.getMessage());
109
                sleep(1000);
110
            }
111
        }
112
 
113
        //
114
        // Request to terminate this thread
115
        // If a mustDie request has been received, then wait 1 second and exit
116
        // This is a nasty thing to do
117
        //
118
        if (mustDie) {
119
            terminate();
120
            sleep(1000);
121
            System.exit(5);
122
        }
123
    }
124
 
125
/**
126
 * Process one request    
127
 * @param socket    - Socket with data to process
128
 */
129
    void processSocketRequest(Socket socket)
130
    {
131
 
132
        // Open a reader to receive possible input
133
        //      Set read Timeout on the socket to guard against misbehaving users
134
        //      Insert a dummy 'status' command if no input is received in 3 seconds
135
        //
136
        try {
137
            socket.setSoTimeout(3000);
138
        } catch (SocketException e) {
139
            mLogger.fatal("NagiosThread Exception on setSoTimeout: " + e.getMessage());
140
        }
141
 
142
        ;
143
        try {
144
            BufferedReader rd = new BufferedReader(new InputStreamReader(socket.getInputStream()));
145
            processRequest(socket, rd);
146
            rd.close();
147
        } catch (IOException e) {
148
            mLogger.fatal("NagiosThread Exception on getInputStream: " + e.getMessage());
149
        }
150
    }
151
 
152
    /** Process the request
153
     *  Have a BufferedReader that will contain the socket data
154
     *  @param socket - socket for writing response
155
     *  @param rd - BufferedReader for the socket data
156
     */
157
    void processRequest(Socket socket, BufferedReader rd) {
158
        String request = null;
159
        String[] requestTokens;
160
        String requestType = null;
161
 
162
        try
163
        {
164
            request = rd.readLine();
165
            mLogger.warn("Nagios request:" + request);
166
            if (request == null) {
167
                return;
168
            }
169
 
170
            //
171
            //  Simple sanity check on request
172
            //  First 3 chars must by alphabetic
173
            //
174
            if (!request.matches("^[A-Za-z]{3}.*")) {
175
                mLogger.warn("Nagios request not sane");
176
                return;
177
            }
178
 
179
            //  Handle restful requests as http requests
180
            //  Expect rest type url http://server:port/request/arg1/arg2
181
            //
182
            if (request.matches(".*\\s+HTTP/\\d\\.\\d$"))
183
            {
184
                try {
185
                    requestTokens = request.split("\\s+");
186
                    requestType = requestTokens[0];
187
                    requestTokens = requestTokens[1].substring(1).split("/");
188
                    request = requestTokens[0];
189
                }
190
                catch (IndexOutOfBoundsException e1) {
191
                    mLogger.fatal("NagiosThread cannot parse HTTP header");
192
                    return; 
193
                }
194
            }
195
        }
196
        catch ( SocketTimeoutException s)
197
        {
198
            mLogger.warn("Nagios Socket read timeout");
199
            request = "status";
200
 
201
        } catch (IOException e) {
202
            mLogger.fatal("NagiosThread Exception on processRequest: " + e.getMessage());
203
            return;
204
        }
205
 
206
        //  Process the users request and generate a response
207
 
208
        StringBuilder resp = new StringBuilder();
209
        generateResponse(request, resp);
210
 
211
        //
212
        //  Generate the output
213
        //  Wrap in an HTTP header if required
214
        //  Only really valid for 'estatus' as the content type may not be accurate
215
        //
216
        try 
217
        {
218
            BufferedWriter wr = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
219
            if(requestType != null)
220
            {
221
                wr.write("HTTP/1.0 200 OK\n");
222
                wr.write("Content-Type: application/json\n");
223
                wr.write("Content-length: "+ resp.length()+"\n");
224
                wr.write("\n");
225
            }
226
            wr.write (resp.toString());
227
 
228
            //  Flush out the input stream
229
            wr.flush();
230
            wr.close();
231
 
232
        } catch (IOException e) {
233
            mLogger.fatal("NagiosThread caught Exception writing to port:" + e.getMessage() );
234
        }
235
    }
236
 
237
    /**
238
     * Generate a response to the Nagios (or user ) request 
239
     * 
240
     * @param request - Request to process
241
     * @param resp - Build response into this StringBuilder
242
     */
243
void generateResponse(String request, StringBuilder resp) {
244
 
245
    if (request.equals("status"))
246
    {
247
        // Write the status message to the output stream
248
        // Currently this is too simple, but it is a start
249
        BuildDaemon.NagiosInfo nagInfo = new BuildDaemon.NagiosInfo();
250
 
251
        mBuildDaemon.checkThreads(nagInfo);
252
        int reasonCount = nagInfo.extendedReason.size();
253
        if (  reasonCount <= 0) {
254
            resp.append("Build Daemon status: OK\n");
255
        } else {
256
            resp.append("Build Daemon status: NOT HAPPY\n");
257
            mLogger.fatal("NagiosThread: NOT HAPPY");
258
 
259
            // Extended information - Reasons for being not happy
260
 
261
            if (reasonCount > 0)
262
            {
263
                resp.append("Reasons:" + "\n");
264
                for( int ii = 0; ii < reasonCount ;ii++)
265
                {
266
                    resp.append("    " + nagInfo.extendedReason.get(ii) + "\n");
267
                }
268
            }
269
        }
270
 
271
        resp.append("Version: "+ this.getClass().getPackage().getImplementationVersion() + "\n");
272
        resp.append("HostName: "+ BuildDaemon.mHostname + "\n");
273
        resp.append("Started: "+ startString + "\n");
274
        resp.append("UpTime (S): "+ (System.currentTimeMillis() - startTime) / 1000 + "\n");
275
        resp.append("Threads:" + nagInfo.threadCount + "\n");
276
        resp.append("Alive:" + nagInfo.threadAliveCount + "\n");
277
        resp.append("Masters:" + nagInfo.masterCount + "\n");
278
        resp.append("Slaves:" + nagInfo.slaveCount + "\n");
279
 
280
        if (BuildDaemon.mShutDown) {
281
            resp.append("Shutdown Request in Progress\n");
282
        }
283
 
284
        File cwd = new File(".");
285
        resp.append( "Usable space: " + cwd.getUsableSpace() + "\n");
286
 
287
    }
288
    else if ( request.equals( "help") ) {
289
        resp.append("Build Daemon Command Interface: Supported commands\n");
290
        resp.append("    status   - Nagios status\n");
291
        resp.append("    estatus  - Extended status\n");
292
        resp.append("    shutdown - shutdown buildtool\n");
293
        resp.append("    kill     - Immediate exit\n");
294
        resp.append("    help     - This message\n");
295
 
296
    }
297
    else if ( request.equals( "estatus") ) {
298
        LinkedHashMap<String, Object> basicStatus = new LinkedHashMap<String, Object>();
299
        BuildDaemon.NagiosInfo nagInfo = new BuildDaemon.NagiosInfo();
300
        mBuildDaemon.checkThreads(nagInfo);
301
 
302
        basicStatus.put("Vesion", this.getClass().getPackage().getImplementationVersion());
303
        basicStatus.put("HostName", BuildDaemon.mHostname );
304
        basicStatus.put("Start Text", startString );
305
        basicStatus.put("Start Time", Long.valueOf(startTime) );
306
        basicStatus.put("UpTime ", Long.valueOf(System.currentTimeMillis() - startTime) );
307
        basicStatus.put("Threads" , Integer.valueOf(nagInfo.threadCount) );
308
        basicStatus.put("Alive" , Integer.valueOf(nagInfo.threadAliveCount));
309
        basicStatus.put("Masters", Integer.valueOf(nagInfo.masterCount));
310
        basicStatus.put("Slaves", Integer.valueOf(nagInfo.slaveCount));
311
        basicStatus.put("UnHappy" , Integer.valueOf(nagInfo.extendedReason.size()) );
312
        basicStatus.put("mShutDown", Boolean.valueOf(BuildDaemon.mShutDown));
313
 
314
        LinkedHashMap<String, Object> dstatus = new LinkedHashMap<String, Object>();
315
        mBuildDaemon.extendedStatus(dstatus);
316
 
317
        LinkedHashMap<String, Object> status = new LinkedHashMap<String, Object>();
318
        status.put("Basic", basicStatus);
319
        status.put("Daemon", dstatus);
320
 
321
        String outBody = dumpJson(status, "");
322
        resp.append(outBody);
323
    }
324
    else if ( request.equals( "shutdown") ) {
325
        resp.append("Gracefull shutdown\n");
326
        mLogger.fatal("Gracefull shutdown requested");
327
        BuildDaemon.mShutDown = true;
328
    }
329
    else if ( request.equals( "kill") ) {
330
        resp.append("Will commit suicide\n");
331
        mLogger.fatal("Immediate program termination");
332
        mustDie = true;
333
        isRunning = false;
334
    }
335
    else {
336
        String eMgs = "Build Daemon Command Interface: Unknown command:" + request;
337
        mLogger.warn( eMgs );
338
        resp.append(eMgs);
339
        resp.append("\n");
340
    }
341
}
342
 
343
    /**Display a simple-ish hashmap as JSON
344
     * Only handles some basic types of data
345
     *      - Integers, Longs
346
     *      - Strings
347
     *      - Boolean
348
     *      - null
349
     *      - Hash Map (recursively)
350
     * 
351
     * @param estatus   Hash Map to display
352
     * @param indent    Indentation string
353
     * @return json encoded representation of the hashmap
354
     */
355
    @SuppressWarnings("unchecked")
356
    private String dumpJson(LinkedHashMap<String, Object> estatus, String indent)
357
    {
358
        StringBuilder s = new StringBuilder();
359
        String prefix = "";
360
        s.append(indent).append("{\n");
361
 
362
        for (Entry<String, Object> entry : estatus.entrySet()) {
363
            String key = entry.getKey();
364
            Object obj = entry.getValue();
365
            String objType = "";
366
            s.append(prefix);
367
            prefix = ",\n";
368
 
369
            s.append(indent).append('"').append(key).append('"').append(": ");
370
            if ( obj != null) {
371
                objType = obj.getClass().getName();
372
            }
373
 
374
            if ( obj == null ) {
375
                s.append("null");
376
            } else  if (objType.endsWith(".Integer") || objType.endsWith(".Long") ) {
377
                s.append(obj.toString());
378
 
379
            } else  if (objType.endsWith(".String") ) {
380
                s.append('"').append(obj.toString()).append('"');
381
 
382
            } else  if (objType.endsWith(".Boolean") ) {
383
                s.append(obj.toString());
384
 
385
            } else  if (objType.endsWith(".LinkedHashMap") ) {
386
                s.append(dumpJson((LinkedHashMap<String, Object>) obj, indent + "    "));
387
 
388
            } else {
389
                s.append('"').append("Unknown Class:").append(objType).append('"');
390
            }
391
        }
392
        s.append('\n').append(indent).append("}");
393
        return s.toString();
394
    }
395
}
396