Permalink
Browse files

initial commit

  • Loading branch information...
0 parents commit 88040346fd80dddeb8cd66ede21a4e002fa4cd59 @hollow hollow committed Oct 28, 2011
Showing with 446 additions and 0 deletions.
  1. +4 −0 .gitignore
  2. +1 −0 .rvmrc
  3. +2 −0 Gemfile
  4. +20 −0 LICENSE
  5. +30 −0 README.rst
  6. +1 −0 Rakefile
  7. +13 −0 contrib/example.rb
  8. +252 −0 contrib/live.php
  9. +6 −0 lib/livestatus.rb
  10. +26 −0 lib/livestatus/connection.rb
  11. +67 −0 lib/livestatus/handler.rb
  12. +3 −0 lib/livestatus/version.rb
  13. +21 −0 livestatus.gemspec
4 .gitignore
@@ -0,0 +1,4 @@
+*.gem
+.bundle
+Gemfile.lock
+pkg/*
1 .rvmrc
@@ -0,0 +1 @@
+rvm use --create ruby-1.9.2-p290@livestatus
2 Gemfile
@@ -0,0 +1,2 @@
+source "http://rubygems.org"
+gemspec
20 LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2011 Benedikt Böhm
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 README.rst
@@ -0,0 +1,30 @@
+==========
+livestatus
+==========
+
+:Author: `Benedikt Böhm <bb@xnull.de>`_
+:Web: http://github.com/zenops/livestatus
+:Git: ``git clone https://github.com/zenops/livestatus``
+
+Livestatus is a simple Ruby library to get Nagios data via MK Livestatus or
+LivestatusSlave.
+
+Contributing to livestatus
+==========================
+
+- Check out the latest master to make sure the feature hasn't been implemented
+ or the bug hasn't been fixed yet
+
+- Check out the issue tracker to make sure someone already hasn't requested it
+ and/or contributed it
+
+- Fork the project
+
+- Start a feature/bugfix branch
+
+- Commit and push until you are happy with your contribution
+
+Copyright
+=========
+
+Copyright (c) 2011 Benedikt Böhm. See LICENSE for further details.
1 Rakefile
@@ -0,0 +1 @@
+require "bundler/gem_tasks"
13 contrib/example.rb
@@ -0,0 +1,13 @@
+#!/usr/bin/env ruby
+
+require 'livestatus'
+
+c = Livestatus::Connection.new("https://nagios.example.com/nagios/live.php")
+
+c.handler.session.insecure = true
+c.handler.session.auth_type = :basic
+c.handler.session.username = 'admin'
+c.handler.session.password = 'password'
+
+c.command("DISABLE_NOTIFICATIONS")
+puts c.get("status").inspect
252 contrib/live.php
@@ -0,0 +1,252 @@
+<?php
+/*****************************************************************************
+ *
+ * live.php - Standalone PHP script to serve the unix socket of the
+ * MKLivestatus NEB module as webservice.
+ *
+ * Copyright (c) 2010,2011 Lars Michelsen <lm@larsmichelsen.com>
+ * Copyright (c) 2011 Benedikt Böhm <bb@xnull.de>
+ *
+ * License:
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ * @AUTHOR Lars Michelsen <lm@larsmichelsen.com>
+ * @HOME http://nagios.larsmichelsen.com/livestatusslave/
+ * @VERSION 1.1
+ *****************************************************************************/
+
+/**
+ * Script configuration.
+ */
+
+$conf = Array(
+ // The socket type can be 'unix' for connecting with the unix socket or 'tcp'
+ // to connect to a tcp socket.
+ 'socketType' => 'unix',
+ // When using a unix socket the path to the socket needs to be set
+ 'socketPath' => '/var/nagios/rw/live',
+ // When using a tcp socket the address and port needs to be set
+ 'socketAddress' => '',
+ 'socketPort' => '',
+ // Modify the default allowed query type match regex
+ 'queryTypes' => '(GET|COMMAND)',
+);
+
+
+###############################################################################
+# Don't modify the code below when you're not aware of what you are doing...
+###############################################################################
+
+class LiveException extends Exception {}
+
+$LIVE = null;
+
+// Start the main function
+livestatusSlave();
+
+function livestatusSlave() {
+ global $conf;
+
+ try {
+ verifyConfig();
+ connectSocket();
+
+ $query = getQuery();
+ response(Array(0, 'OK'), queryLivestatus($query));
+
+ closeSocket();
+ exit(0);
+ } catch(LiveException $e) {
+ response(Array(1, $e->getMessage()), Array());
+ closeSocket();
+ exit(1);
+ }
+}
+
+function readQuery() {
+ global $argv;
+
+ if (isset($_REQUEST['q']) && $_REQUEST['q'] !== '') {
+ return str_replace('\\\\n', "\n", $_REQUEST['q']);
+ } elseif (isset($argv[1]) && $argv[1] !== '') {
+ return str_replace('\\n', "\n", $argv[1]);
+ } else {
+ throw new LiveException('No query given in "q" Attribute nor argv[0].');
+ }
+}
+
+function getQuery() {
+ global $conf;
+ $query = readQuery();
+
+ if (!preg_match("/^".$conf['queryTypes']."\s/", $query))
+ throw new LiveException('Invalid livestatus query: ' . $query);
+
+ return $query;
+}
+
+function response($head, $body) {
+ header('Content-type: application/json');
+ $json_result = json_encode(Array($head, $body));
+
+ // Support jsonp when requested by client (see http://en.wikipedia.org/wiki/JSONP).
+ if (isset($_REQUEST['callback']) && $_REQUEST['callback'] != '')
+ $json_result = $_REQUEST['callback']."(".$json_result.")";
+
+ echo $json_result;
+}
+
+function verifyConfig() {
+ global $conf;
+
+ if (!function_exists('socket_create')) {
+ throw new LiveException('The PHP function socket_create is not available. Maybe the sockets module is missing in your PHP installation.');
+ }
+
+ if ($conf['socketType'] != 'tcp' && $conf['socketType'] != 'unix') {
+ throw new LiveException('Socket Type is invalid. Need to be "unix" or "tcp".');
+ }
+
+ if ($conf['socketType'] == 'unix') {
+ if ($conf['socketPath'] == '') {
+ throw new LiveException('The option socketPath is empty.');
+ }
+
+ if (!file_exists($conf['socketPath'])) {
+ throw new LiveException('The configured livestatus socket does not exists');
+ }
+ }
+
+ elseif ($conf['socketType'] == 'tcp') {
+ if ($conf['socketAddress'] == '') {
+ throw new LiveException('The option socketAddress is empty.');
+ }
+
+ if ($conf['socketPort'] == '') {
+ throw new LiveException('The option socketPort is empty.');
+ }
+ }
+}
+
+function readSocket($len) {
+ global $LIVE;
+ $offset = 0;
+ $socketData = '';
+
+ while($offset < $len) {
+ if (($data = @socket_read($LIVE, $len - $offset)) === false)
+ return false;
+
+ $dataLen = strlen ($data);
+ $offset += $dataLen;
+ $socketData .= $data;
+
+ if ($dataLen == 0)
+ break;
+ }
+
+ return $socketData;
+}
+
+function queryLivestatus($query) {
+ global $LIVE;
+
+ // Query to get a json formated array back
+ // Use fixed16 header
+ socket_write($LIVE, $query . "OutputFormat: json\nResponseHeader: fixed16\n\n");
+ socket_shutdown($LIVE, 1);
+
+ if (substr($query, 0, 7) == "COMMAND") {
+ return Array();
+ }
+
+ // Read 16 bytes to get the status code and body size
+ $read = readSocket(16);
+
+ if ($read === false)
+ throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
+
+ // Extract status code
+ $status = substr($read, 0, 3);
+
+ // Extract content length
+ $len = intval(trim(substr($read, 4, 11)));
+
+ // Read socket until end of data
+ $read = readSocket($len);
+
+ if ($read === false)
+ throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
+
+ // Catch errors (Like HTTP 200 is OK)
+ if ($status != "200")
+ throw new LiveException('Problem while reading from socket: '.$read);
+
+ // Catch problems occured while reading? 104: Connection reset by peer
+ if (socket_last_error($LIVE) == 104)
+ throw new LiveException('Problem while reading from socket: '.socket_strerror(socket_last_error($LIVE)));
+
+ // Decode the json response
+ $obj = json_decode(utf8_encode($read));
+
+ // json_decode returns null on syntax problems
+ if ($obj === null)
+ throw new LiveException('The response has an invalid format');
+ else
+ return $obj;
+}
+
+function connectSocket() {
+ global $conf, $LIVE;
+
+ // Create socket connection
+ if ($conf['socketType'] === 'unix') {
+ $LIVE = socket_create(AF_UNIX, SOCK_STREAM, 0);
+ } elseif ($conf['socketType'] === 'tcp') {
+ $LIVE = socket_create(AF_INET, SOCK_STREAM, SOL_TCP);
+ }
+
+ if ($LIVE == false) {
+ throw new LiveException('Could not create livestatus socket connection.');
+ }
+
+ // Connect to the socket
+ if ($conf['socketType'] === 'unix') {
+ $result = socket_connect($LIVE, $conf['socketPath']);
+ } elseif ($conf['socketType'] === 'tcp') {
+ $result = socket_connect($LIVE, $conf['socketAddress'], $conf['socketPort']);
+ }
+
+ if ($result == false) {
+ throw new LiveException('Unable to connect to livestatus socket.');
+ }
+
+ // Maybe set some socket options
+ if ($conf['socketType'] === 'tcp') {
+ // Disable Nagle's Algorithm - Nagle's Algorithm is bad for brief protocols
+ if (defined('TCP_NODELAY')) {
+ socket_set_option($LIVE, SOL_TCP, TCP_NODELAY, 1);
+ } else {
+ // See http://bugs.php.net/bug.php?id=46360
+ socket_set_option($LIVE, SOL_TCP, 1, 1);
+ }
+ }
+}
+
+function closeSocket() {
+ global $LIVE;
+ @socket_close($LIVE);
+ $LIVE = null;
+}
6 lib/livestatus.rb
@@ -0,0 +1,6 @@
+require "livestatus/version"
+
+module Livestatus
+ autoload :Connection, "livestatus/connection"
+ autoload :Handler, "livestatus/handler"
+end
26 lib/livestatus/connection.rb
@@ -0,0 +1,26 @@
+require "livestatus/handler"
+
+module Livestatus
+
+ class Connection
+ attr_accessor :handler
+
+ def initialize(uri)
+ case uri
+ when /^https?:\/\//
+ @handler = PatronHandler.new(uri)
+ else
+ raise AttributeError, "unknown uri type: #{uri}"
+ end
+ end
+
+ def get(table, headers = {})
+ handler.get(table, headers)
+ end
+
+ def command(cmd, time = nil)
+ handler.command(cmd, time)
+ end
+ end
+
+end
67 lib/livestatus/handler.rb
@@ -0,0 +1,67 @@
+require 'patron'
+require 'yajl'
+require 'cgi'
+
+module Livestatus
+
+ class HandlerException < StandardError; end
+
+ class BaseHandler
+ def get(table, headers = {})
+ data = query(:get, table, headers)
+
+ if headers.include?("Columns")
+ columns = headers["Columns"].split(" ")
+ else
+ columns = data.delete_at(0)
+ end
+
+ column_zip(columns, data)
+ end
+
+ def command(cmd, time = nil)
+ time = Time.now.to_i unless time
+ query(:command, "[#{time}] #{cmd}")
+ end
+
+ private
+
+ def column_zip(columns, data)
+ data.map do |d|
+ Hash[columns.zip(d)]
+ end
+ end
+ end
+
+ class PatronHandler < BaseHandler
+ attr_accessor :session
+
+ def initialize(uri)
+ @session = Patron::Session.new
+ @session.timeout = 10
+ @session.headers["User-Agent"] = "livestatus/#{VERSION} ruby/#{RUBY_VERSION}"
+ @uri = uri
+ end
+
+ def query(method, query, headers = {})
+ headers = headers.map { |k,v| "#{k}: #{v}" }.join("\n")
+ headers += "\n" unless headers.empty?
+
+ query = CGI::escape("#{method.to_s.upcase} #{query}\n#{headers}")
+ result = session.get("#{@uri}?q=#{query}")
+
+ unless result.status == 200
+ raise HandlerException, "livestatus query failed with status #{result.status}"
+ end
+
+ parser = Yajl::Parser.new
+ data = parser.parse(result.body)
+
+ if data[0][0] > 0
+ raise HandlerException, "livestatus returned error: #{data[0][1]}"
+ end
+
+ return data[1]
+ end
+ end
+end
3 lib/livestatus/version.rb
@@ -0,0 +1,3 @@
+module Livestatus
+ VERSION = "0.0.1"
+end
21 livestatus.gemspec
@@ -0,0 +1,21 @@
+# -*- encoding: utf-8 -*-
+$:.push File.expand_path("../lib", __FILE__)
+require "livestatus/version"
+
+Gem::Specification.new do |s|
+ s.name = "livestatus"
+ s.version = Livestatus::VERSION
+ s.authors = ["Benedikt Böhm"]
+ s.email = ["bb@xnull.de"]
+ s.homepage = ""
+ s.summary = %q{Simple API wrapper for MK Livestatus and LivestatusSlave}
+ s.description = %q{Simple API wrapper for MK Livestatus and LivestatusSlave}
+
+ s.files = `git ls-files`.split("\n")
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
+ s.require_paths = ["lib"]
+
+ s.add_runtime_dependency "patron"
+ s.add_runtime_dependency "yajl-ruby"
+end

0 comments on commit 8804034

Please sign in to comment.