nagira 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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