Subversion Repositories DevTools

Rev

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