notifu 1.2

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.
@@ -0,0 +1,83 @@
1
+ module Notifu
2
+ class Logger
3
+
4
+ attr_reader :syslog_enabled
5
+ attr_reader :elasticsearch_enabled
6
+ attr_reader :es
7
+ attr_reader :syslog
8
+
9
+ LEVELS = [ "debug",
10
+ "info",
11
+ "notice",
12
+ "warning",
13
+ "error",
14
+ "critical",
15
+ "alert",
16
+ "emergency" ]
17
+
18
+ def initialize (mod)
19
+ @syslog_enabled = Notifu::CONFIG[:logging][:syslog][:enabled]
20
+ @elasticsearch_enabled = Notifu::CONFIG[:logging][:elasticsearch][:enabled]
21
+
22
+ @logger = Log4r::Logger.new 'notifu'
23
+
24
+ if self.syslog_enabled
25
+ begin
26
+ @logger.outputters = Log4r::SyslogOutputter.new "notifu", ident: "notifu-#{mod}"
27
+ log "info", "Syslog socket opened"
28
+ rescue
29
+ @logger.outputters = Log4r::Outputter.stdout
30
+ log "error", "Failed to open local syslog socket, using STDOUT"
31
+ end
32
+ else
33
+ log "info", "Syslog disabled"
34
+ @logger.outputters = Log4r::Outputter.stdout
35
+ end
36
+
37
+ if self.elasticsearch_enabled
38
+ begin
39
+ @es = Elasticsearch::Client.new hosts: Notifu::CONFIG[:logging][:elasticsearch][:conn], retry_on_failure: false, transport_options: { request: { timeout: Notifu::CONFIG[:logging][:elasticsearch][:timeout] || 10 } }
40
+ log "info", "Action log output to ElasticSearch - " + Notifu::CONFIG[:logging][:elasticsearch][:conn].to_json
41
+ rescue
42
+ @es = false
43
+ log "error", "Failed to connect to ElasticSearch"
44
+ exit 1
45
+ end
46
+ else
47
+ log "info", "ElasticSearch action logging disabled"
48
+ @es = false
49
+ end
50
+
51
+ end
52
+
53
+ def action_log (type, event)
54
+ if self.elasticsearch_enabled && self.es
55
+ index_name = "notifu-" + Time.now.strftime("%Y.%m.%d").to_s
56
+ begin
57
+ self.es.index index: index_name, type: type, body: event
58
+ rescue Faraday::TimeoutError
59
+ log "error", "Action log action failed: ElasticSearch timeout"
60
+ log "info", "Action log: (#{type}) #{event.to_json}"
61
+ end
62
+ else
63
+ log "debug", "Action log: #{type}"
64
+ log "debug", "Action log: (#{type}) #{event.to_json}"
65
+ end
66
+ end
67
+
68
+ def log (prio, msg)
69
+ @logger.send prio.to_sym, msg
70
+ end
71
+
72
+ end
73
+
74
+
75
+ # class LogFormatter < Log4r::Formatter
76
+ # def format(event)
77
+ # event.to_yaml
78
+ # end
79
+ # end
80
+
81
+ # "#{time.utc.iso8601(3)} notifu-sidekiq[#{::Process.pid}]: #{context} (TID-#{Thread.current.object_id.to_s(36)}) #{severity}: #{message}\n"
82
+
83
+ end
@@ -0,0 +1,49 @@
1
+ class Object
2
+ def deep_symbolize_keys
3
+ return self.inject({}){|memo,(k,v) | memo[k.to_sym] = v.deep_symbolize_keys; memo} if self.is_a? Hash
4
+ return self.inject([]){|memo,v | memo << v.deep_symbolize_keys; memo} if self.is_a? Array
5
+ return self
6
+ end
7
+ end
8
+ class Numeric
9
+ def duration
10
+ secs = self.to_int
11
+ mins = secs / 60
12
+ hours = mins / 60
13
+ days = hours / 24
14
+
15
+ if days > 0
16
+ "#{days}d, #{hours % 24}h"
17
+ elsif hours > 0
18
+ "#{hours}h, #{mins % 60}min"
19
+ elsif mins > 0
20
+ "#{mins}min, #{secs % 60}s"
21
+ elsif secs >= 0
22
+ "#{secs}s"
23
+ end
24
+ end
25
+
26
+ def to_state
27
+ case self.to_int
28
+ when 0
29
+ return "OK"
30
+ when 1
31
+ return "WARNING"
32
+ when 2
33
+ return "CRITICAL"
34
+ else
35
+ return "UNKNOWN [#{self.to_s}]"
36
+ end
37
+ end
38
+ end
39
+
40
+ class String
41
+ def camelize
42
+ return self if self !~ /_/ && self =~ /[A-Z]+.*/
43
+ split('_').map{|e| e.capitalize}.join
44
+ end
45
+
46
+ def to_state
47
+ return self.to_i.to_state
48
+ end
49
+ end
@@ -0,0 +1,5 @@
1
+ require "notifu/model/contact"
2
+ require "notifu/model/sla"
3
+ require "notifu/model/group"
4
+ require "notifu/model/event"
5
+ require "notifu/model/issue"
@@ -0,0 +1,15 @@
1
+ module Notifu
2
+ module Model
3
+ class Contact < Ohm::Model
4
+
5
+ attribute :name
6
+ attribute :full_name
7
+ attribute :cell
8
+ attribute :mail
9
+ attribute :jabber
10
+ index :name
11
+ unique :name
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,52 @@
1
+ module Notifu
2
+ module Model
3
+ class Event
4
+ include Notifu::Util
5
+ attr_reader :notifu_id
6
+ attr_reader :host
7
+ attr_reader :address
8
+ attr_reader :service
9
+ attr_reader :occurrences_trigger
10
+ attr_reader :occurrences_count
11
+ attr_reader :interval
12
+ attr_reader :time_last_event
13
+ attr_reader :time_last_notified
14
+ attr_reader :time_created
15
+ attr_reader :sgs
16
+ attr_reader :action
17
+ attr_reader :code
18
+ attr_reader :aspiring_code
19
+ attr_reader :message
20
+ attr_reader :api_endpoint
21
+ attr_reader :duration
22
+ attr_reader :unsilence
23
+ attr_reader :refresh
24
+ attr_accessor :process_result
25
+
26
+ def initialize args
27
+ payload = args.first
28
+ payload.each { |name, value| instance_variable_set("@#{name}", value) }
29
+ @time_last_notified = Hash.new.to_json.to_s
30
+ @time_created = self.time_last_event
31
+ @aspiring_code = self.code
32
+ @occurrences_trigger ||= 1
33
+ @refresh ||= nil
34
+ @unsilence ||= true
35
+ end
36
+
37
+ def group_sla
38
+ self.sgs.map { |gs| Hash[:group, gs.split(':')[0], :sla, gs.split(':')[1]] }
39
+ end
40
+
41
+ def data
42
+ @data ||= Hash[ instance_variables.map { |var| [var.to_s.sub(/^@/,""), instance_variable_get(var)] } ]
43
+ end
44
+
45
+ def update_process_result!(obj)
46
+ self.process_result ||= JSON.generate(Array.new)
47
+ self.process_result = JSON.generate(JSON.parse(self.process_result) + [ obj ])
48
+ end
49
+
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,14 @@
1
+ module Notifu
2
+ module Model
3
+ class Group < Ohm::Model
4
+
5
+ attribute :name
6
+ set :primary, Notifu::Model::Contact
7
+ set :secondary, Notifu::Model::Contact
8
+ set :tertiary, Notifu::Model::Contact
9
+ index :name
10
+ unique :name
11
+
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,51 @@
1
+ module Notifu
2
+ module Model
3
+ class Issue < Ohm::Model
4
+ include Notifu::Util
5
+ attribute :notifu_id
6
+ attribute :host
7
+ attribute :address
8
+ attribute :service
9
+ attribute :occurrences_trigger
10
+ attribute :occurrences_count
11
+ attribute :interval
12
+ attribute :refresh
13
+ attribute :time_last_event
14
+ attribute :time_last_notified
15
+ attribute :time_created
16
+ attribute :sgs
17
+ attribute :action
18
+ attribute :code
19
+ attribute :aspiring_code
20
+ attribute :message
21
+ attribute :process_result
22
+ attribute :api_endpoint
23
+ attribute :duration
24
+ attribute :unsilence
25
+ index :notifu_id
26
+ index :host
27
+ index :service
28
+ unique :notifu_id
29
+
30
+
31
+ def time_last_notified? (group_name, sla_name)
32
+ begin
33
+ JSON.parse(self.time_last_notified)["#{group_name}:#{sla_name}"]
34
+ rescue
35
+ 0
36
+ end
37
+ end
38
+
39
+ def time_last_notified! (group_name, sla_name, time)
40
+ obj = JSON.parse(self.time_last_notified)
41
+ self.time_last_notified = JSON.generate(obj.merge({ "#{group_name}:#{sla_name}" => time }))
42
+ end
43
+
44
+ def create_from_event event
45
+ event.each { |name, value| instance_variable_set(name, value) }
46
+ self.save
47
+ end
48
+
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,20 @@
1
+ module Notifu
2
+ module Model
3
+ class Sla < Ohm::Model
4
+
5
+ attribute :name
6
+ attribute :timeranges
7
+ attribute :refresh
8
+ attribute :actors
9
+ index :name
10
+ unique :name
11
+
12
+ def timerange_values (now=Time.now)
13
+ dow = now.wday - 1
14
+ dow = 6 if dow < 0
15
+ JSON.parse(self.timeranges)[dow]
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,105 @@
1
+ require 'sensu/redis'
2
+ require 'digest'
3
+ require 'multi_json'
4
+
5
+ module Sensu::Extension
6
+ class Notifu < Handler
7
+
8
+ def definition
9
+ {
10
+ type: 'extension',
11
+ name: 'notifu'
12
+ }
13
+ end
14
+
15
+ def name
16
+ definition[:name]
17
+ end
18
+
19
+ def options
20
+ return @options if @options
21
+ @options = {
22
+ :host => '127.0.0.1',
23
+ :port => 6379,
24
+ :db => 2
25
+ }
26
+ if @settings[:notifu].is_a?(Hash)
27
+ @options.merge!(@settings[:notifu])
28
+ end
29
+ @options
30
+ end
31
+
32
+ def description
33
+ 'Notifu handler for Sensu Server'
34
+ end
35
+
36
+ def post_init
37
+
38
+ if @redis
39
+ yield(@redis)
40
+ else
41
+ Sensu::Redis.connect(options) do |connection|
42
+ connection.auto_reconnect = false
43
+ connection.reconnect_on_error = true
44
+ connection.on_error do |error|
45
+ @logger.warn(error)
46
+ end
47
+ @redis = connection
48
+ end
49
+ end
50
+ @redis.sadd("queues", "processor")
51
+ end
52
+
53
+ def run(event_data)
54
+ event = MultiJson.load(event_data, { :symbolize_keys => true })
55
+ notifu_id = Digest::SHA256.hexdigest("#{event[:client][:name]}:#{event[:client][:address]}:#{event[:check][:name]}").to_s[-10,10]
56
+
57
+ if event[:check][:name] == "keepalive"
58
+ sgs = event[:client][:sgs]
59
+ sgs ||= event[:client][:sla]
60
+ else
61
+ sgs = event[:check][:sgs]
62
+ sgs ||= event[:check][:sla]
63
+ end
64
+
65
+ payload = {
66
+ notifu_id: notifu_id,
67
+ host: event[:client][:name],
68
+ address: event[:client][:address],
69
+ service: event[:check][:name],
70
+ occurrences_trigger: event[:check][:occurrences],
71
+ occurrences_count: event[:occurrences],
72
+ interval: event[:check][:interval] || 0,
73
+ time_last_event: event[:check][:executed],
74
+ sgs: sgs,
75
+ action: event[:action],
76
+ code: event[:check][:status],
77
+ message: event[:check][:output],
78
+ duration: event[:check][:duration],
79
+ api_endpoint: "http://" + @settings[:api][:host].to_s + ":" + @settings[:api][:port].to_s
80
+ }
81
+
82
+ job = {
83
+ 'class' => 'Notifu::Processor',
84
+ 'args' => [ payload ],
85
+ 'jid' => SecureRandom.hex(12),
86
+ 'retry' => true,
87
+ 'enqueued_at' => Time.now.to_f
88
+ }
89
+
90
+ begin
91
+ @redis.lpush("queue:processor", MultiJson.dump(job))
92
+ rescue Exception => e
93
+ yield "failed to send event to Notifu #{e.message}", 1
94
+ end
95
+
96
+ yield "sent event to Notifu #{notifu_id}", 0
97
+ end
98
+
99
+ def stop
100
+ yield
101
+ end
102
+
103
+ end
104
+ end
105
+
@@ -0,0 +1,9 @@
1
+ module Notifu
2
+ module Util
3
+
4
+ def self.option args
5
+ self.instance_variable_set args.keys.first, args.values.first
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,60 @@
1
+ require_relative "sidekiq_init"
2
+
3
+ $logger = Notifu::Logger.new 'actor'
4
+
5
+ Sidekiq.configure_server do |config|
6
+ config.redis = { url: Notifu::CONFIG[:redis_queues] }
7
+ Sidekiq::Logging.logger = Log4r::Logger.new 'sidekiq'
8
+ Sidekiq::Logging.logger.outputters = Log4r::SyslogOutputter.new 'sidekiq', ident: 'notifu-actor'
9
+ # Sidekiq::Logging.logger.formatter = Notifu::LogFormatter.new
10
+ Sidekiq::Logging.logger.level = Log4r::DEBUG
11
+ end
12
+
13
+ Sidekiq.configure_client do |config|
14
+ config.redis = { url: Notifu::CONFIG[:redis_queues] }
15
+ end
16
+
17
+
18
+ module Notifu
19
+
20
+ class Actor
21
+ include Notifu::Util
22
+ include Sidekiq::Worker
23
+
24
+ attr_accessor :issue
25
+ attr_accessor :contacts
26
+
27
+ class << self
28
+ attr_accessor :name
29
+ attr_accessor :desc
30
+ attr_accessor :retry
31
+ end
32
+
33
+ # `act` function must be defined in child classes
34
+ # (provides notification actors modularity)
35
+ #
36
+ def act
37
+ exit 1
38
+ end
39
+
40
+ sidekiq_options :queue => "actor-#{self.name}"
41
+ sidekiq_options :retry => self.retry
42
+
43
+ def perform *args
44
+ sleep 2
45
+ load_data args
46
+ act
47
+ end
48
+
49
+ def load_data args
50
+ self.issue = Notifu::Model::Issue.with(:notifu_id, args[0])
51
+ self.contacts = args[1].map { |contact| Notifu::Model::Contact.with(:name, contact) }
52
+ end
53
+
54
+ end
55
+ end
56
+
57
+ # load all actors
58
+ Dir[File.dirname(__FILE__).sub(/\/lib\/workers$/, "/") + 'actors/*.rb'].each do |file|
59
+ require file
60
+ end