Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

replace live.php with proper sinatra-based API

  • Loading branch information...
commit f56e178394982eb67cf42be65c69dce1191db031 1 parent 82b4a6c
Benedikt Böhm authored November 23, 2011
4  Gemfile
... ...
@@ -1,2 +1,6 @@
1 1
 source "http://rubygems.org"
  2
+
2 3
 gemspec
  4
+
  5
+gem 'sinatra', '~> 1.2.7'
  6
+gem 'thin', '~> 1.2.5'
2  config.ru
... ...
@@ -0,0 +1,2 @@
  1
+require 'livestatus/api'
  2
+run Livestatus::API
252  contrib/live.php
... ...
@@ -1,252 +0,0 @@
1  
-<?php
2  
-/*****************************************************************************
3  
- *
4  
- * live.php - Standalone PHP script to serve the unix socket of the
5  
- *            MKLivestatus NEB module as webservice.
6  
- *
7  
- * Copyright (c) 2010,2011 Lars Michelsen <lm@larsmichelsen.com>
8  
- * Copyright (c) 2011 Benedikt Böhm <bb@xnull.de>
9  
- *
10  
- * License:
11  
- *
12  
- * This program is free software; you can redistribute it and/or modify
13  
- * it under the terms of the GNU General Public License version 2 as
14  
- * published by the Free Software Foundation.
15  
- *
16  
- * This program is distributed in the hope that it will be useful,
17  
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19  
- * GNU General Public License for more details.
20  
- *
21  
- * You should have received a copy of the GNU General Public License
22  
- * along with this program; if not, write to the Free Software
23  
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24  
- *
25  
- * @AUTHOR   Lars Michelsen <lm@larsmichelsen.com>
26  
- * @HOME     http://nagios.larsmichelsen.com/livestatusslave/
27  
- * @VERSION  1.1
28  
- *****************************************************************************/
29  
-
30  
-/**
31  
- * Script configuration.
32  
- */
33  
-
34  
-$conf = Array(
35  
-    // The socket type can be 'unix' for connecting with the unix socket or 'tcp'
36  
-    // to connect to a tcp socket.
37  
-    'socketType'       => 'unix',
38  
-    // When using a unix socket the path to the socket needs to be set
39  
-    'socketPath'       => '/var/nagios/rw/live',
40  
-    // When using a tcp socket the address and port needs to be set
41  
-    'socketAddress'    => '',
42  
-    'socketPort'       => '',
43  
-    // Modify the default allowed query type match regex
44  
-    'queryTypes'       => '(GET|COMMAND)',
45  
-);
46  
-
47  
-
48  
-###############################################################################
49  
-# Don't modify the code below when you're not aware of what you are doing...
50  
-###############################################################################
51  
-
52  
-class LiveException extends Exception {}
53  
-
54  
-$LIVE = null;
55  
-
56  
-// Start the main function
57  
-livestatusSlave();
58  
-
59  
-function livestatusSlave() {
60  
-    global $conf;
61  
-
62  
-    try {
63  
-        verifyConfig();
64  
-        connectSocket();
65  
-
66  
-        $query = getQuery();
67  
-        response(Array(0, 'OK'), queryLivestatus($query));
68  
-
69  
-        closeSocket();
70  
-        exit(0);
71  
-    } catch(LiveException $e) {
72  
-        response(Array(1, $e->getMessage()), Array());
73  
-        closeSocket();
74  
-        exit(1);
75  
-    }
76  
-}
77  
-
78  
-function readQuery() {
79  
-    global $argv;
80  
-
81  
-    if (isset($_REQUEST['q']) && $_REQUEST['q'] !== '') {
82  
-        return str_replace('\\\\n', "\n", $_REQUEST['q']);
83  
-    } elseif (isset($argv[1]) && $argv[1] !== '') {
84  
-        return str_replace('\\n', "\n", $argv[1]);
85  
-    } else {
86  
-        throw new LiveException('No query given in "q" Attribute nor argv[0].');
87  
-    }
88  
-}
89  
-
90  
-function getQuery() {
91  
-    global $conf;
92  
-    $query = readQuery();
93  
-
94  
-    if (!preg_match("/^".$conf['queryTypes']."\s/", $query))
95  
-        throw new LiveException('Invalid livestatus query: ' . $query);
96  
-
97  
-    return $query;
98  
-}
99  
-
100  
-function response($head, $body) {
101  
-    header('Content-type: application/json');
102  
-    $json_result = json_encode(Array($head, $body));
103  
-
104  
-    // Support jsonp when requested by client (see http://en.wikipedia.org/wiki/JSONP).
105  
-    if (isset($_REQUEST['callback']) && $_REQUEST['callback'] != '')
106  
-        $json_result = $_REQUEST['callback']."(".$json_result.")";
107  
-
108  
-    echo $json_result;
109  
-}
110  
-
111  
-function verifyConfig() {
112  
-    global $conf;
113  
-
114  
-    if (!function_exists('socket_create')) {
115  
-        throw new LiveException('The PHP function socket_create is not available. Maybe the sockets module is missing in your PHP installation.');
116  
-    }
117  
-
118  
-    if ($conf['socketType'] != 'tcp' && $conf['socketType'] != 'unix') {
119  
-        throw new LiveException('Socket Type is invalid. Need to be "unix" or "tcp".');
120  
-    }
121  
-
122  
-    if ($conf['socketType'] == 'unix') {
123  
-        if ($conf['socketPath'] == '') {
124  
-            throw new LiveException('The option socketPath is empty.');
125  
-        }
126  
-
127  
-        if (!file_exists($conf['socketPath'])) {
128  
-            throw new LiveException('The configured livestatus socket does not exists');
129  
-        }
130  
-    }
131  
-
132  
-    elseif ($conf['socketType'] == 'tcp') {
133  
-        if ($conf['socketAddress'] == '') {
134  
-            throw new LiveException('The option socketAddress is empty.');
135  
-        }
136  
-
137  
-        if ($conf['socketPort'] == '') {
138  
-            throw new LiveException('The option socketPort is empty.');
139  
-        }
140  
-    }
141  
-}
142  
-
143  
-function readSocket($len) {
144  
-    global $LIVE;
145  
-    $offset = 0;
146  
-    $socketData = '';
147  
-
148  
-    while($offset < $len) {
149  
-        if (($data = @socket_read($LIVE, $len - $offset)) === false)
150  
-            return false;
151  
-
152  
-        $dataLen = strlen ($data);
153  
-        $offset += $dataLen;
154  
-        $socketData .= $data;
155  
-
156  
-        if ($dataLen == 0)
157  
-            break;
158  
-    }
159  
-
160  
-    return $socketData;
161  
-}
162  
-
163  
-function queryLivestatus($query) {
164  
-    global $LIVE;
165  
-
166  
-    // Query to get a json formated array back
167  
-    // Use fixed16 header
168  
-    socket_write($LIVE, $query . "OutputFormat: json\nResponseHeader: fixed16\n\n");
169  
-    socket_shutdown($LIVE, 1);
170  
-
171  
-    if (substr($query, 0, 7) == "COMMAND") {
172  
-        return Array();
173  
-    }
174  
-
175  
-    // Read 16 bytes to get the status code and body size
176  
-    $read = readSocket(16);
177  
-
178  
-    if ($read === false)
179  
-        throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
180  
-
181  
-    // Extract status code
182  
-    $status = substr($read, 0, 3);
183  
-
184  
-    // Extract content length
185  
-    $len = intval(trim(substr($read, 4, 11)));
186  
-
187  
-    // Read socket until end of data
188  
-    $read = readSocket($len);
189  
-
190  
-    if ($read === false)
191  
-        throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
192  
-
193  
-    // Catch errors (Like HTTP 200 is OK)
194  
-    if ($status != "200")
195  
-        throw new LiveException('Problem while reading from socket: '.$read);
196  
-
197  
-    // Catch problems occured while reading? 104: Connection reset by peer
198  
-    if (socket_last_error($LIVE) == 104)
199  
-        throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
200  
-
201  
-    // Decode the json response
202  
-    $obj = json_decode(utf8_encode($read));
203  
-
204  
-    // json_decode returns null on syntax problems
205  
-    if ($obj === null)
206  
-        throw new LiveException('The response has an invalid format');
207  
-    else
208  
-        return $obj;
209  
-}
210  
-
211  
-function connectSocket() {
212  
-    global $conf, $LIVE;
213  
-
214  
-    // Create socket connection
215  
-    if ($conf['socketType'] === 'unix') {
216  
-        $LIVE = socket_create(AF_UNIX, SOCK_STREAM, 0);
217  
-    } elseif ($conf['socketType'] === 'tcp') {
218  
-        $LIVE = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
219  
-    }
220  
-
221  
-    if ($LIVE == false) {
222  
-        throw new LiveException('Could not create livestatus socket connection.');
223  
-    }
224  
-
225  
-    // Connect to the socket
226  
-    if ($conf['socketType'] === 'unix') {
227  
-        $result = socket_connect($LIVE, $conf['socketPath']);
228  
-    } elseif ($conf['socketType'] === 'tcp') {
229  
-        $result = socket_connect($LIVE, $conf['socketAddress'], $conf['socketPort']);
230  
-    }
231  
-
232  
-    if ($result == false) {
233  
-        throw new LiveException('Unable to connect to livestatus socket.');
234  
-    }
235  
-
236  
-    // Maybe set some socket options
237  
-    if ($conf['socketType'] === 'tcp') {
238  
-        // Disable Nagle's Algorithm - Nagle's Algorithm is bad for brief protocols
239  
-        if (defined('TCP_NODELAY')) {
240  
-            socket_set_option($LIVE, SOL_TCP, TCP_NODELAY, 1);
241  
-        } else {
242  
-            // See http://bugs.php.net/bug.php?id=46360
243  
-            socket_set_option($LIVE, SOL_TCP, 1, 1);
244  
-        }
245  
-    }
246  
-}
247  
-
248  
-function closeSocket() {
249  
-    global $LIVE;
250  
-    @socket_close($LIVE);
251  
-    $LIVE = null;
252  
-}
29  lib/livestatus/api.rb
... ...
@@ -0,0 +1,29 @@
  1
+require 'livestatus/connection'
  2
+require 'livestatus/models'
  3
+require 'sinatra/base'
  4
+require 'yajl'
  5
+
  6
+module Livestatus
  7
+  class API < Sinatra::Base
  8
+    def parse_headers(env)
  9
+      Hash[env.select do |k, v|
  10
+        k =~ /^HTTP_X_LIVESTATUS_/
  11
+      end.map do |k, v|
  12
+        [k[18..-1].downcase.to_sym, v]
  13
+      end]
  14
+    end
  15
+
  16
+    get '/' do
  17
+      headers = parse_headers(request.env)
  18
+
  19
+      halt 400, 'no query specified' unless headers.include?(:query)
  20
+      method, model = headers.delete(:query).split
  21
+
  22
+      halt 400, 'invalid method' unless ['GET', 'COMMAND'].include?(method)
  23
+      method = method.downcase.to_sym
  24
+
  25
+      c = Livestatus::Connection.new(:uri => 'unix:///var/nagios/rw/live')
  26
+      Yajl::Encoder.encode(c.handler.send(method, model, headers))
  27
+    end
  28
+  end
  29
+end
2  lib/livestatus/connection.rb
@@ -19,6 +19,8 @@ def handler!
19 19
       case @config[:uri]
20 20
       when /^https?:\/\//
21 21
         PatronHandler.new(@config)
  22
+      when /^unix:\/\//
  23
+        UnixHandler.new(@config)
22 24
       else
23 25
         raise AttributeError, "unknown uri type: #{@config[:uri]}"
24 26
       end
17  lib/livestatus/handler.rb
... ...
@@ -1,4 +1,19 @@
1  
-require "livestatus/handler/base"
  1
+module Livestatus
  2
+
  3
+  class HandlerException < StandardError; end
  4
+
  5
+  class BaseHandler
  6
+    def get(table_name, options = {})
  7
+      query(:get, table_name.to_s, options)
  8
+    end
  9
+
  10
+    def command(cmd, time = nil)
  11
+      time = Time.now.to_i unless time
  12
+      query(:command, "[#{time}] #{cmd}")
  13
+    end
  14
+  end
  15
+
  16
+end
2 17
 
3 18
 Dir["#{File.dirname(__FILE__)}/handler/*.rb"].each do |path|
4 19
   require "livestatus/handler/#{File.basename(path, '.rb')}"
32  lib/livestatus/handler/base.rb
... ...
@@ -1,32 +0,0 @@
1  
-module Livestatus
2  
-
3  
-  class HandlerException < StandardError; end
4  
-
5  
-  class BaseHandler
6  
-    def get(table_name, options = {})
7  
-      data = query(:get, table_name.to_s, options)
8  
-
9  
-      if options.include?(:columns)
10  
-        columns = options[:columns].split(" ")
11  
-      else
12  
-        columns = data.delete_at(0)
13  
-      end
14  
-
15  
-      column_zip(columns, data)
16  
-    end
17  
-
18  
-    def command(cmd, time = nil)
19  
-      time = Time.now.to_i unless time
20  
-      query(:command, "[#{time}] #{cmd}")
21  
-    end
22  
-
23  
-    private
24  
-
25  
-    def column_zip(columns, data)
26  
-      data.map do |d|
27  
-        Hash[columns.zip(d)]
28  
-      end
29  
-    end
30  
-  end
31  
-
32  
-end
23  lib/livestatus/handler/patron.rb
... ...
@@ -1,6 +1,6 @@
  1
+require 'livestatus/handler'
1 2
 require 'patron'
2 3
 require 'yajl'
3  
-require 'cgi'
4 4
 
5 5
 module Livestatus
6 6
 
@@ -12,31 +12,26 @@ def initialize(config)
12 12
       @session.timeout = 10
13 13
       @session.headers["User-Agent"] = "livestatus/#{VERSION} ruby/#{RUBY_VERSION}"
14 14
       @session.insecure = config[:insecure]
15  
-      @session.auth_type = config[:auth_type].to_sym
  15
+      @session.auth_type = config.fetch(:auth_type, :basic).to_sym
16 16
       @session.username = config[:username]
17 17
       @session.password = config[:password]
18 18
       @uri = config[:uri]
19 19
     end
20 20
 
21 21
     def query(method, query, headers = {})
22  
-      headers = headers.map { |k,v| "#{k.to_s.capitalize}: #{v}" }.join("\n")
23  
-      headers += "\n" unless headers.empty?
  22
+      headers = Hash[headers.merge({
  23
+        :query => "#{method.to_s.upcase} #{query}"
  24
+      }).map do |k, v|
  25
+        ["X-Livestatus-#{k.to_s.dasherize}", v]
  26
+      end]
24 27
 
25  
-      query = CGI::escape("#{method.to_s.upcase} #{query}\n#{headers}")
26  
-      result = session.get("#{@uri}?q=#{query}")
  28
+      result = session.get(@uri, headers)
27 29
 
28 30
       unless result.status == 200
29 31
         raise HandlerException, "livestatus query failed with status #{result.status}"
30 32
       end
31 33
 
32  
-      parser = Yajl::Parser.new
33  
-      data = parser.parse(result.body)
34  
-
35  
-      if data[0][0] > 0
36  
-        raise HandlerException, "livestatus returned error: #{data[0][1]}"
37  
-      end
38  
-
39  
-      return data[1]
  34
+      Yajl::Parser.parse(result.body)
40 35
     end
41 36
   end
42 37
 
55  lib/livestatus/handler/unix.rb
... ...
@@ -0,0 +1,55 @@
  1
+require 'livestatus/handler'
  2
+require 'socket'
  3
+require 'yajl'
  4
+
  5
+module Livestatus
  6
+
  7
+  class UnixHandler < BaseHandler
  8
+    def initialize(config)
  9
+      @socket = UNIXSocket.open(config[:uri].sub(/^unix:\/\//, ''))
  10
+    end
  11
+
  12
+    def get(table_name, options = {})
  13
+      data = super
  14
+
  15
+      if options.include?(:columns)
  16
+        columns = options[:columns].split(" ")
  17
+      else
  18
+        columns = data.delete_at(0)
  19
+      end
  20
+
  21
+      column_zip(columns, data)
  22
+    end
  23
+
  24
+    def query(method, query, headers = {})
  25
+      headers.merge!({
  26
+        :response_header => "fixed16",
  27
+        :output_format => "json",
  28
+        :keep_alive => "on",
  29
+      })
  30
+
  31
+      headers = headers.map { |k,v| "#{k.to_s.camelize}: #{v}" }.join("\n")
  32
+      headers += "\n" unless headers.empty?
  33
+
  34
+      @socket.write("#{method.to_s.upcase} #{query}\n#{headers}\n")
  35
+
  36
+      res = @socket.read(16)
  37
+      status, length = res[0..2].to_i, res[4..14].chomp.to_i
  38
+
  39
+      unless status == 200
  40
+        raise HandlerException, "livestatus query failed with status #{status}"
  41
+      end
  42
+
  43
+      Yajl::Parser.new.parse(@socket.read(length))
  44
+    end
  45
+
  46
+    private
  47
+
  48
+    def column_zip(columns, data)
  49
+      data.map do |d|
  50
+        Hash[columns.zip(d)]
  51
+      end
  52
+    end
  53
+  end
  54
+
  55
+end

0 notes on commit f56e178

Please sign in to comment.
Something went wrong with that request. Please try again.