nagira 0.2.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. data/History.md +89 -0
  2. data/Rakefile +128 -0
  3. data/bin/nagira +11 -0
  4. data/bin/nagira-setup +6 -0
  5. data/config/defaults.rb +74 -0
  6. data/config/environment.rb +44 -0
  7. data/config/nagira.defaults +66 -0
  8. data/config/nagira.init_d +133 -0
  9. data/lib/app.rb +330 -0
  10. data/lib/app/routes/get/config.rb +22 -0
  11. data/lib/app/routes/get/objects.rb +71 -0
  12. data/lib/app/routes/get/status.rb +153 -0
  13. data/lib/app/routes/put.rb +52 -0
  14. data/lib/app/routes/put/status.rb +139 -0
  15. data/lib/nagira.rb +55 -0
  16. data/lib/nagira/background_parse.rb +28 -0
  17. data/lib/nagira/nagios.rb +20 -0
  18. data/lib/nagira/timed_parse.rb +77 -0
  19. data/spec/00_configuration_spec.rb +62 -0
  20. data/spec/01_nagira_response_spec.rb +122 -0
  21. data/spec/02_0_status_spec.rb +53 -0
  22. data/spec/02_nagira_data_spec.rb +101 -0
  23. data/spec/03_api_spec.rb +48 -0
  24. data/spec/spec_helper.rb +4 -0
  25. data/test/benchmark.rb +26 -0
  26. data/test/data/bad/README +1 -0
  27. data/test/data/bad/nagios.cfg +1305 -0
  28. data/test/data/bad/objects.cache +1868 -0
  29. data/test/data/bad/status.dat +1766 -0
  30. data/test/data/json/GET.txt +15 -0
  31. data/test/data/json/README.txt +9 -0
  32. data/test/data/json/host_check.json +4 -0
  33. data/test/data/json/host_check.sh +4 -0
  34. data/test/data/json/ping.json +5 -0
  35. data/test/data/json/ping_and_http.json +12 -0
  36. data/test/data/json/ping_and_http_check.sh +4 -0
  37. data/test/data/json/ping_check.sh +4 -0
  38. data/test/data/nagios.cfg +1305 -0
  39. data/test/data/objects.cache +1868 -0
  40. data/test/data/status.dat +1652 -0
  41. data/version.txt +1 -0
  42. metadata +384 -0
@@ -0,0 +1,52 @@
1
+ class Nagira < Sinatra::Base
2
+
3
+ # @method parse_input_data
4
+ # @overload before("Parse PUT request body")
5
+ #
6
+ # Process the data before on each HTTP request.
7
+ #
8
+ # @return [Array] @input Sets @input instance variable.
9
+ #
10
+ before do
11
+ if request.put?
12
+ data = request.body.read
13
+ @input = case @format
14
+ when :json then JSON.parse data
15
+ when :xml then Hash.from_xml data
16
+ when :yaml then YAML.load data
17
+ end
18
+ # Make sure we always return an Array
19
+ @input = [@input] if @input.is_a? Hash
20
+ end
21
+ end
22
+
23
+ # Define helpers for put methods
24
+ helpers do
25
+
26
+ # Helper to send PUT update to Nagios::ExternalCommands
27
+ #
28
+ # @param [Hash] params
29
+ # @param [Symbol] action Nagios external command name
30
+ #
31
+ # FIXME: This only accepts single service. Modify to use Arrays too
32
+ def put_update action, params
33
+ res = $nagios[:commands].write(params.merge({ :action => action }))
34
+ { :status => res[:result], :object => res[:data]}
35
+ end
36
+ end
37
+
38
+ # Small helper to submit data to ::Nagios::ExternalCommands
39
+ # object. For status updates sends external coond via
40
+ # ::Nagios::ExternalCommands.send method.
41
+ def update_service_status params
42
+ put_update :PROCESS_SERVICE_CHECK_RESULT, params
43
+ end
44
+
45
+ # Small helper to submit data to ::Nagios::ExternalCommands
46
+ # object. For host status updates sends external command via
47
+ # ::Nagios::ExternalCommands.write method.
48
+ def update_host_status params
49
+ put_update :PROCESS_HOST_CHECK_RESULT, params
50
+ end
51
+
52
+ end
@@ -0,0 +1,139 @@
1
+ class Nagira < Sinatra::Base
2
+
3
+
4
+ # @method put_status
5
+ # @overload put("/_status")
6
+ #
7
+ # Submit JSON Hash for multiple services, on multiple hosts.
8
+ put "/_status" do
9
+ "TODO: Not implemented"
10
+ end
11
+
12
+ # @method put_status_host_name
13
+ # @overload put("/_status/:host_name")
14
+ #
15
+ # Update hoststatus information only for the given host. URL
16
+ # hostname always override hostname given in the JSON file.
17
+ #
18
+ # == Example
19
+ #
20
+ # $ curl -i -H "Accept: application/json" -d @host.json -X
21
+ # PUT http://localhost:4567/_status/svaroh
22
+ #
23
+ # => {"status": true, "object": [{"data": {"host_name":"svaroh",
24
+ # "status_code": "0", "plugin_output": "ping OK", "action":
25
+ # "PROCESS_HOST_CHECK_RESULT"}, "result":true, "messages": []}]}
26
+ #
27
+ # == Example JSON
28
+ #
29
+ # {
30
+ # "status_code":"0",
31
+ # "plugin_output" : "ping OK"
32
+ # }
33
+ put "/_status/:host_name" do
34
+ @data = update_host_status @input.first.merge({
35
+ 'host_name' => params['host_name']
36
+ })
37
+ nil
38
+ end
39
+
40
+ # Same as /_status/:host_name (Not implemented)
41
+ #
42
+ # @method put__host_status_host_name
43
+ # @overload put("/_host_status/:host_name")
44
+ #
45
+ put "/_host_status/:host_name" do
46
+ "Not implemented: TODO"
47
+ end
48
+
49
+ # @method put_status_host_name_services
50
+ # @overload put("/_status/:host_name/_services")
51
+ #
52
+ # Update multiple services on the same host.
53
+ #
54
+ # Hostname from URL always overrides host_name if it's is provided
55
+ # in the JSON data.
56
+ #
57
+ # == Example return JSON data
58
+ #
59
+ # $ curl -i -H "Accept: application/json" -d @dat_m.json -X PUT
60
+ # http://localhost:4567/_status/svaroh/_services
61
+ #
62
+ # [{"status":true, "object":[{"data":{"host_name":"svaroh",
63
+ # "service_description":"PING", "return_code":"0",
64
+ # "plugin_output":"OK",
65
+ # "action":"PROCESS_SERVICE_CHECK_RESULT"}, "result":true,
66
+ # "messages":[]}]}, {"status":true,
67
+ # "object":[{"data":{"host_name":"svaroh",
68
+ # "service_description":"Apache", "return_code":"r20",
69
+ # "plugin_output":"cwfailedOK",
70
+ # "action":"PROCESS_SERVICE_CHECK_RESULT"}, "result":true,
71
+ # "messages":[]}]}]%
72
+ #
73
+ # == Example JSON for submit
74
+ #
75
+ # All attributes provided in the example below are requried for host
76
+ # service status information:
77
+ # - host_name
78
+ # - service_description
79
+ # - return_code
80
+ # - plugin_output
81
+ #
82
+ #
83
+ # [{ "host_name":"viy",
84
+ # "service_description":"PING",
85
+ # "return_code":"0",
86
+ # "plugin_output":"64 bytes from 127.0.0.1: icmp_seq=0 ttl=64 time=0.046 ms "
87
+ # },
88
+ #
89
+ # {"host_name":"svaroh",
90
+ # "service_description":"Apache",
91
+ # "return_code":"2",
92
+ # "plugin_output":"HTTP GEt failed"
93
+ # }
94
+ # ]
95
+ #
96
+ #
97
+ put "/_status/:host_name/_services" do
98
+
99
+ data = []
100
+ @input.each do |datum|
101
+ # FIXME - this calls update for each service. Should be batching them together
102
+ data << update_service_status(
103
+ datum.merge({
104
+ 'host_name' => params['host_name']
105
+ })
106
+ )
107
+ end
108
+ @data = data
109
+ nil
110
+
111
+ end
112
+
113
+ # Update single service on a single host by JSON data.
114
+ put "/_status/:host_name/_services/:service_description" do
115
+ @data = update_service_status \
116
+ @input.first.merge({
117
+ 'service_description' => params['service_description'],
118
+ 'host_name' => params['host_name']
119
+ })
120
+ nil
121
+ end
122
+
123
+ # @method put_status_as_http_parms
124
+ # @overload put(/_status/:host_name/_services/:service_description/_return_code/:return_code/_plugin_output/:plugin_output)
125
+ #
126
+ # Update single service status on a single host. Use data provided
127
+ # in RESTful path as parameters.
128
+ #
129
+ # == Example
130
+ # curl -d "test data" \
131
+ # -X PUT http://localhost:4567/_status/viy/_services/PING/_return_code/0/_plugin_output/OK
132
+ # # => ok
133
+ put "/_status/:host_name/_services/:service_description/_return_code/:return_code/_plugin_output/:plugin_output" do
134
+
135
+ @data = update_service_status params
136
+ nil
137
+ end
138
+
139
+ end
data/lib/nagira.rb ADDED
@@ -0,0 +1,55 @@
1
+ require 'active_model/serialization'
2
+ require 'active_model/serializers/xml' # for Hash.to_xml
3
+
4
+ require 'active_support/inflector'
5
+ require 'active_support/inflector/inflections'
6
+ require 'active_support/core_ext/hash/slice' # for Hash.slice
7
+
8
+ require 'json'
9
+ require 'yaml'
10
+ require 'sinatra'
11
+ require 'sinatra/reloader'
12
+
13
+ $: << File.dirname(__FILE__) << File.dirname(File.dirname(__FILE__))
14
+
15
+ require 'config/defaults'
16
+
17
+ require "app/routes/get/config"
18
+ require "app/routes/get/objects"
19
+ require "app/routes/get/status"
20
+
21
+ require "app/routes/put"
22
+ require "app/routes/put/status"
23
+
24
+
25
+ #
26
+ # environment file must go after default, some settings override
27
+ # defaults.
28
+ #
29
+ require 'config/environment'
30
+ require 'nagira/nagios'
31
+
32
+ class Nagira < Sinatra::Base
33
+
34
+ VERSION = File.read(File.expand_path(File.dirname(__FILE__)) + '/../version.txt').strip
35
+ GITHUB = "http://dmytro.github.com/nagira/"
36
+
37
+ # Get all routes that Nagira provides.
38
+ def api
39
+ api = { }
40
+
41
+ param_regex = Regexp.new '\(\[\^\\\\\/\?\#\]\+\)'
42
+ Nagira.routes.keys.each do |method|
43
+ api[method] ||= []
44
+ Nagira.routes[method].each do |r|
45
+ path = r[0].inspect[3..-3]
46
+ r[1].each do |parm|
47
+ path.sub!(param_regex,":#{parm}")
48
+ end
49
+ path.gsub!('\\','')
50
+ api[method] << path unless path.empty?
51
+ end
52
+ end
53
+ api
54
+ end
55
+ end
@@ -0,0 +1,28 @@
1
+ require 'nagios'
2
+
3
+ module Nagios
4
+ ##
5
+ # Background parsing of status.dat file in separate thread. Runs on
6
+ # regular intervals slightly shorter than :ttl
7
+ #
8
+ class BackgroundParser
9
+
10
+ ##
11
+ #
12
+ # If :ttl is not defined set to 0 and do not run
13
+ # background parsing.
14
+ #
15
+ def initialize
16
+ interval = [::DEFAULT[:ttl],1].max || nil
17
+ if interval && ::DEFAULT[:start_background_parser]
18
+ puts "[#{Time.now}] Starting background parser thread with interval #{interval} sec"
19
+ $bg = Thread.new {
20
+ loop {
21
+ $nagios[:status].parse
22
+ sleep interval
23
+ } #loop
24
+ } # thread
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,20 @@
1
+ module Nagios
2
+
3
+ require 'nagira/timed_parse'
4
+ require 'nagira/background_parse'
5
+
6
+ # Extensions to Nagios::Status and Objects classes for use with
7
+ # Nagira: keep track of file modification times and parse only
8
+ # changed files.
9
+ class Config
10
+ include Nagios::TimedParse
11
+ end
12
+
13
+ class Status
14
+ include Nagios::TimedParse
15
+ end
16
+
17
+ class Nagios::Objects
18
+ include Nagios::TimedParse
19
+ end
20
+ end
@@ -0,0 +1,77 @@
1
+ require 'nagios'
2
+
3
+ module Nagios
4
+ # Keep track of last parsed time and last changed time of the
5
+ # status/cache file to avoid parsing on each HTTP request.
6
+ module TimedParse
7
+
8
+ # Set some minimum interval for re-parsing of the status file:
9
+ # even if file changes often, we do not want to parse it more
10
+ # often, then this number of seconds.
11
+
12
+ TTL = ::DEFAULT[:ttl] || 0
13
+
14
+ # Override constructor and parse method from ::Nagios::Objects or
15
+ # ::Nagios::Status classes, add instance variables to handle
16
+ # modification and parseing times of status file.
17
+ # Original methods are aliased:
18
+ # - initialize -> constructor
19
+ # - parse -> parse!
20
+ # See also
21
+ # http://www.ruby-forum.com/topic/969161
22
+ def self.included(base)
23
+ base.class_eval do
24
+ alias_method :parse!, :parse
25
+ alias_method :constructor, :initialize
26
+
27
+ # @method initialize
28
+ # Extend current constructor with some additional data to
29
+ # track file change time
30
+ #
31
+ # @param [String] file Path to status file
32
+ # @param [Fixnum] parse_interval Number of seconds between
33
+ # re-parsing of the file
34
+ def initialize(file, parse_interval=TTL)
35
+ constructor(file)
36
+
37
+ # Time when status file was last time parsed, set it to 0 secs
38
+ # epoch to make sure it will be parsed
39
+ @last_parsed = Time.at(0)
40
+
41
+ # Last time file was changed
42
+ @last_changed = File.mtime(@path)
43
+ @parse_interval = parse_interval
44
+ end
45
+
46
+
47
+ # Extend original parse method: parse file only if it needs
48
+ # parsing and set time of parser run to current time.
49
+ def parse
50
+ if need_parsing?
51
+ parse!
52
+ @last_parsed = Time.now
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ attr_accessor :last_parsed, :parse_interval
59
+
60
+ def last_changed
61
+ @last_changed = File.mtime(@path)
62
+ end
63
+
64
+ # Return true if file is changed since it was parsed last time
65
+ def changed?
66
+ self.last_changed > self.last_parsed
67
+ end
68
+
69
+ # Check if:
70
+ # - file changed?
71
+ # - was it parsed recently?
72
+ def need_parsing?
73
+ changed? && ((Time.now - self.last_parsed) > @parse_interval)
74
+ end
75
+
76
+ end
77
+ end
@@ -0,0 +1,62 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Configuration" do
4
+
5
+ set :environment, ENV['RACK_ENV'] || :test
6
+
7
+ before {
8
+ @cfg = Nagios::Config.new(Nagira.settings.nagios_cfg)
9
+ }
10
+
11
+ context "nagios.cfg" do
12
+
13
+ it { File.should exist @cfg.path }
14
+
15
+ it "should be parseable" do
16
+ lambda { @cfg.parse }.should_not raise_error
17
+ end
18
+
19
+ context "parsing nagios.cfg file" do
20
+
21
+ before { @cfg.parse }
22
+
23
+ it "should have PATH to objects file" do
24
+ @cfg.object_cache_file.should be_a_kind_of String
25
+ end
26
+
27
+ it "should have PATH to status file" do
28
+ @cfg.status_file.should be_a_kind_of String
29
+ end
30
+
31
+ end # parsing nagios.cfg file
32
+ end # nagios.cfg
33
+
34
+ context "data files" do
35
+ before { @cfg.parse }
36
+
37
+ context Nagios::Status do
38
+
39
+ subject { Nagios::Status.new( Nagira.settings.status_cfg || @cfg.status_file ) }
40
+
41
+ it { File.should exist( subject.path ) }
42
+
43
+ it "should be parseable" do
44
+ lambda { subject.parse }.should_not raise_error
45
+ end
46
+ end # Nagios::Status
47
+
48
+
49
+ context Nagios::Objects do
50
+
51
+ subject { Nagios::Objects.new( Nagira.settings.objects_cfg || @cfg.object_cache_file) }
52
+
53
+ it { File.should exist subject.path }
54
+
55
+ it "should be parseable" do
56
+ lambda { subject.parse }.should_not raise_error
57
+ end
58
+ end # Nagios::Objects
59
+
60
+ end # data files
61
+
62
+ end
@@ -0,0 +1,122 @@
1
+ require 'spec_helper'
2
+
3
+ describe Nagira do
4
+
5
+ set :environment, ENV['RACK_ENV'] || :test
6
+
7
+ include Rack::Test::Methods
8
+
9
+ def app
10
+ @app ||= Nagira
11
+ end
12
+
13
+ TOP_PAGES = %w{ _config _objects _status _api }
14
+ FORMATS = %w{ xml yaml json}
15
+ DEFAULT_FORMAT = ::Nagira.settings.format
16
+ TYPES = %w{state list}
17
+
18
+ context "simple page load" do
19
+ TOP_PAGES.each do |page|
20
+ it "/#{page} should load" do
21
+ get "/#{page}"
22
+ last_response.should be_ok
23
+ end
24
+
25
+
26
+ # Check 3 different formats
27
+ FORMATS.each do |format|
28
+
29
+ it "should load '#{page}.#{format}' page" do
30
+ get "/#{page}.#{format}"
31
+ last_response.should be_ok
32
+ end
33
+
34
+ end
35
+ end
36
+ end
37
+
38
+ context "data format check" do
39
+
40
+ TOP_PAGES.each do |page|
41
+ context page do
42
+
43
+ FORMATS.each do |format|
44
+
45
+ context format do
46
+
47
+ before do
48
+ get "/#{page}.#{format}"
49
+ @header = last_response.header
50
+ @body = last_response.body
51
+ end
52
+
53
+ it "Content-type: application/#{format}" do
54
+ @header['Content-Type'].should =~ /^application\/#{format}.*/
55
+ end
56
+
57
+ it "body should have content" do
58
+ @body.should_not be_empty
59
+ end
60
+
61
+ it "#{format} should be parseable" do
62
+ case format
63
+ when 'json'
64
+ lambda { JSON.parse @body }.should_not raise_error
65
+ when 'xml'
66
+ lambda { Hash.from_xml @body }.should_not raise_error
67
+ when 'yaml'
68
+ lambda { YAML.load @body }.should_not raise_error
69
+ end
70
+ end
71
+ end
72
+
73
+ end
74
+
75
+ context 'default format' do
76
+ it "/#{page}.#{Nagira.settings.format} response should be the same as /#{page}" do
77
+ get "/#{page}.#{Nagira.settings.format}"
78
+ a = last_response.body
79
+ get "/#{page}"
80
+ b = last_response.body
81
+ a.should eq b
82
+ end
83
+ end
84
+
85
+ end
86
+ end
87
+
88
+ #
89
+ # GET /config
90
+ # ----------------------------------------
91
+ context "/config" do
92
+
93
+ before do
94
+ get "/_config.json"
95
+ @data = JSON.parse last_response.body
96
+ end
97
+
98
+ context "important items in Nagios configuration" do
99
+
100
+ # Configuration strings
101
+
102
+ %w{ log_file object_cache_file resource_file status_file
103
+ nagios_user nagios_group }.each do |key|
104
+
105
+ it "attribute #{key} should be a String" do
106
+ @data[key].should be_a_kind_of String
107
+ end
108
+ end
109
+
110
+ # Congiration arrays
111
+ %w{cfg_file cfg_dir}.each do |key|
112
+ it "attribute #{key} should be an Array" do
113
+ @data[key].should be_a_kind_of Array
114
+ end
115
+ end
116
+
117
+ end
118
+ end # /config
119
+
120
+
121
+ end
122
+ end