Skip to content

Commit

Permalink
add nagios/splunk and tests for it
Browse files Browse the repository at this point in the history
  • Loading branch information
Max Horbul committed Apr 12, 2012
1 parent 254984f commit c0f657d
Show file tree
Hide file tree
Showing 8 changed files with 382 additions and 0 deletions.
16 changes: 16 additions & 0 deletions lib/nagios/splunk.rb
@@ -0,0 +1,16 @@
module Nagios

STATUS = { 1 => "WARN", 2 => "CRITICAL", 0 => "OK" }

module Splunk

LOGIN_URL = "/services/auth/login"
POOL_LIST_URL = "/services/licenser/pools"
LICENSE_LIST_URL = "/services/licenser/licenses"

autoload :Check, "splunk/check"
autoload :RestClient, "splunk/rest_client"
autoload :CLI, "splunk/cli"

end
end
73 changes: 73 additions & 0 deletions lib/nagios/splunk/check.rb
@@ -0,0 +1,73 @@
require 'nokogiri'

module Nagios

module Splunk

# Nagios::Splunk::Check implements different types of checks of
class Check

attr_reader :rest_client

# @param [String] server_url full server url with username and password (RFC 3986)
# example: https://user:pass@localhost:8089/
def initialize(client)
@rest_client = client
end

# license usage check
# @param [Integer] warn license usage threshhold
# @param [Integer] crit license usage threshhold
# @return [Array<Integer, String>] exit code and message
def license_usage(warn, crit)
quota = licenses.
select {|k,v| v["type"] == "enterprise" && v["status"] == "VALID" }.
reduce(0) { |r,l| r+=l[1]["quota"].to_i }
used_quota = pools.reduce(0) {|r,p| r+=p[1]["used_bytes"].to_i }
case true
when used_quota > quota * crit.to_i / 100:
code = 2
when used_quota > quota * warn.to_i / 100:
code = 1
else
code = 0
end
message = "License #{STATUS[code]}: #{used_quota * 100 / quota}% of license capacity is used"
message << " | quota: #{quota} B; used: #{used_quota} B"
return [code, message]
end

# list of avialable licenses
# @return [Array<Hash>]
def licenses
response = rest_client.get(LICENSE_LIST_URL)
response.code == "200" ? parse_data(response.body) : {}
end

# list of available pools
# @return [Array<Hash>]
def pools
response = rest_client.get(POOL_LIST_URL)
response.code == "200" ? parse_data(response.body) : {}
end

private

def parse_data(data)
doc = Nokogiri::Slop(data)
doc.remove_namespaces!
doc.search("entry").reduce(Hash.new) do |r,e|
name = e.title.content
next r if name =~ /^F+D{0,1}$/
r[name] ||= Hash.new
e.search("dict/key").each do |k|
r[name][k.attribute("name").value] = k.text
end
r
end
end

end

end
end
29 changes: 29 additions & 0 deletions lib/nagios/splunk/cli.rb
@@ -0,0 +1,29 @@
require 'mixlib/cli'

module Nagios
module Splunk

class CLI
include Mixlib::CLI

option(:server_url,
:short => "-s URL",
:long => "--server URL",
:default => 'https://admin:changeme@localhost:8089/',
:description => "Splunk server url")

option(:warn,
:short => "-w WARN",
:long => "--warn WARN",
:default => '80',
:description => "Warn % of license capacity usage")

option(:crit,
:short => "-c CRIT",
:long => "--crit CRIT",
:default => '90',
:description => "Critical % of license capacity usage")
end

end
end
75 changes: 75 additions & 0 deletions lib/nagios/splunk/rest_client.rb
@@ -0,0 +1,75 @@
require 'net/https'

module Nagios
module Splunk

# Rest client which communicates with Splunk via REST API
class RestClient

attr_reader :token

# turn on/off debugging
# @param [<True, False>]
def self.debug(value = nil)
@debug = value if value
@debug
end

# parse server url string and set up intance variables
# @param [String] server_url full server url with username and password (RFC 3986)
# example: https://user:pass@localhost:8089/
def initialize(server_url)
uri = URI.parse(server_url)
@host = uri.host
@port = uri.port
@username = uri.user
@password = uri.password
end

# send HTTP request to the server
# @param [String] url
# @return [Net::HTTPRespose]
def get(url)
login unless token
response = client.request(request(url, token))
end

private

# build GET request and include Authorization
# @param [String] url
# @param [String] token authenticate token
def request(url, token)
headers = { "Authorization" => "Splunk #{token}" }
Net::HTTP::Get.new(url, headers)
end

# build HTTP connection
# @return [Net::HTTP]
def client
@client ||= Net::HTTP.new(@host, @port)
@client.use_ssl = true
@client.set_debug_output $stderr if debug
@client
end

def debug
self.class.debug
end

# receive authentication token
def login
request = Net::HTTP::Post.new(LOGIN_URL)
request.set_form_data({"username" => @username, "password" => @password})
response = client.request(request)
if response.code == "200"
doc = Nokogiri::XML.parse(response.body)
elem = doc.search("//sessionKey").first
@token = elem.text if elem
end
end

end

end
end
52 changes: 52 additions & 0 deletions test/fixtures/licenses.xml
@@ -0,0 +1,52 @@
<xml>
<entry>
<title>AAA</title>
<content>
<dict>
<key name="quota">100</key>
<key name="status">VALID</key>
<key name="type">enterprise</key>
</dict>
</content>
</entry>
<entry>
<title>FFF</title>
<content>
<dict>
<key name="quota">100</key>
<key name="status">VALID</key>
<key name="type">enterprise</key>
</dict>
</content>
</entry>
<entry>
<title>FFFD</title>
<content>
<dict>
<key name="quota">100</key>
<key name="status">VALID</key>
<key name="type">enterprise</key>
</dict>
</content>
</entry>
<entry>
<title>BBB</title>
<content>
<dict>
<key name="quota">200</key>
<key name="status">EXPIRED</key>
<key name="type">enterprise</key>
</dict>
</content>
</entry>
<entry>
<title>CCC</title>
<content>
<dict>
<key name="quota">300</key>
<key name="status">VALID</key>
<key name="type">forwarder</key>
</dict>
</content>
</entry>
</xml>
18 changes: 18 additions & 0 deletions test/fixtures/pools.xml
@@ -0,0 +1,18 @@
<xml>
<entry>
<title>AAA</title>
<content>
<dict>
<key name="used_bytes">10</key>
</dict>
</content>
</entry>
<entry>
<title>BBB</title>
<content>
<dict>
<key name="used_bytes">20</key>
</dict>
</content>
</entry>
</xml>
107 changes: 107 additions & 0 deletions test/nagios/splunk/check_test.rb
@@ -0,0 +1,107 @@
require File.join(File.dirname(__FILE__), '../../test_helper')

describe Nagios::Splunk::Check do

before do
@client = MiniTest::Mock.new
@response = MiniTest::Mock.new
@response1 = MiniTest::Mock.new
@check = Nagios::Splunk::Check.new(@client)
@licenses_xml = File.read(File.join(MiniTest.fixtures_path, "licenses.xml"))
@pools_xml = File.read(File.join(MiniTest.fixtures_path, "pools.xml"))
@licenses = {
"AAA" => {"quota" => "100", "status" => "VALID", "type" => "enterprise"},
"BBB" => {"quota" => "200", "status" => "EXPIRED", "type" => "enterprise"},
"CCC" => {"quota" => "300", "status" => "VALID", "type" => "forwarder"}
}
@pools = {
"AAA" => {"used_bytes" => "10"},
"BBB" => {"used_bytes" => "20"}
}
end

describe "when fetch data" do

describe "and response code is not 200 OK" do

before do
@response.expect(:code, "404")
@response.expect(:body, "")
end

it "should have empty license list" do
@client.expect(:get, @response, [Nagios::Splunk::LICENSE_LIST_URL])
@check.licenses.must_equal Hash.new
end

it "should have empty pools list" do
@client.expect(:get, @response, [Nagios::Splunk::POOL_LIST_URL])
@check.pools.must_equal Hash.new
end

end

describe "and body is empty" do

before do
@response.expect(:code, "200")
@response.expect(:body, "<xml />")
end

it "should have empty license list" do
@client.expect(:get, @response, [Nagios::Splunk::LICENSE_LIST_URL])
@check.licenses.must_equal Hash.new
end

it "should have empty pools list" do
@client.expect(:get, @response, [Nagios::Splunk::POOL_LIST_URL])
@check.pools.must_equal Hash.new
end

end

it "should be able to fetch licenses" do
@response.expect(:code, "200")
@response.expect(:body, @licenses_xml)
@client.expect(:get, @response, [Nagios::Splunk::LICENSE_LIST_URL])
@check.licenses.must_equal @licenses
end

it "should be able to fetch pools" do
@response.expect(:code, "200")
@response.expect(:body, @pools_xml)
@client.expect(:get, @response, [Nagios::Splunk::POOL_LIST_URL])
@check.pools.must_equal @pools
end

end

describe "when check license usage" do

before do
@response.expect(:code, "200")
@response.expect(:body, @licenses_xml)
@client.expect(:get, @response, [Nagios::Splunk::LICENSE_LIST_URL])

@response1.expect(:code, "200")
@response1.expect(:body, @pools_xml)
@client.expect(:get, @response1, [Nagios::Splunk::POOL_LIST_URL])
end

it "should return CRITICAL alert" do
message = "License CRITICAL: 30% of license capacity is used | quota: 100 B; used: 30 B"
@check.license_usage(10, 20).must_equal [2, message]
end

it "should return WARN alert" do
message = "License WARN: 30% of license capacity is used | quota: 100 B; used: 30 B"
@check.license_usage(20, 40).must_equal [1, message]
end

it "should return OK" do
message = "License OK: 30% of license capacity is used | quota: 100 B; used: 30 B"
@check.license_usage(40, 50).must_equal [0, message]
end
end

end
12 changes: 12 additions & 0 deletions test/test_helper.rb
@@ -0,0 +1,12 @@
$:.push File.join(File.dirname(__FILE__), "../lib/nagios")

require 'rubygems'
require "bundler/setup"
require 'splunk'
require 'minitest/autorun'

module MiniTest
def self.fixtures_path
File.expand_path(File.join(File.dirname(__FILE__), "fixtures"))
end
end

0 comments on commit c0f657d

Please sign in to comment.