flapjack 0.6.43 → 0.6.44

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 (90) hide show
  1. data/bin/flapjack +4 -2
  2. data/bin/flapjack-nagios-receiver +4 -1
  3. data/bin/flapjack-populator +4 -1
  4. data/etc/flapjack_config.yaml.example +111 -106
  5. data/features/steps/notifications_steps.rb +6 -6
  6. data/lib/flapjack/configuration.rb +76 -24
  7. data/lib/flapjack/coordinator.rb +27 -44
  8. data/lib/flapjack/data/entity.rb +28 -7
  9. data/lib/flapjack/data/entity_check.rb +18 -20
  10. data/lib/flapjack/executive.rb +5 -4
  11. data/lib/flapjack/gateways/api.rb +391 -0
  12. data/lib/flapjack/gateways/api/entity_check_presenter.rb +185 -0
  13. data/lib/flapjack/gateways/api/entity_presenter.rb +70 -0
  14. data/lib/flapjack/gateways/base.rb +38 -0
  15. data/lib/flapjack/{notification → gateways}/email.rb +4 -5
  16. data/lib/flapjack/{notification → gateways}/email/alert.html.haml +0 -0
  17. data/lib/flapjack/{notification → gateways}/email/alert.text.erb +0 -0
  18. data/lib/flapjack/gateways/jabber.rb +387 -0
  19. data/lib/flapjack/gateways/oobetet.rb +241 -0
  20. data/lib/flapjack/gateways/pagerduty.rb +247 -0
  21. data/lib/flapjack/{notification → gateways}/sms.rb +5 -6
  22. data/lib/flapjack/{notification → gateways}/sms/messagenet.rb +1 -1
  23. data/lib/flapjack/gateways/web.rb +293 -0
  24. data/lib/flapjack/{web → gateways/web}/views/_css.haml +0 -0
  25. data/lib/flapjack/{web → gateways/web}/views/_nav.haml +0 -0
  26. data/lib/flapjack/{web → gateways/web}/views/check.haml +0 -0
  27. data/lib/flapjack/{web → gateways/web}/views/contact.haml +0 -0
  28. data/lib/flapjack/{web → gateways/web}/views/contacts.haml +0 -0
  29. data/lib/flapjack/{web → gateways/web}/views/index.haml +0 -0
  30. data/lib/flapjack/{web → gateways/web}/views/self_stats.haml +0 -0
  31. data/lib/flapjack/pikelet.rb +0 -23
  32. data/lib/flapjack/version.rb +1 -1
  33. data/spec/lib/flapjack/coordinator_spec.rb +56 -36
  34. data/spec/lib/flapjack/data/entity_spec.rb +53 -4
  35. data/spec/lib/flapjack/{api → gateways/api}/entity_check_presenter_spec.rb +10 -13
  36. data/spec/lib/flapjack/{api → gateways/api}/entity_presenter_spec.rb +10 -10
  37. data/spec/lib/flapjack/{api_spec.rb → gateways/api_spec.rb} +14 -14
  38. data/spec/lib/flapjack/gateways/email_spec.rb +6 -0
  39. data/spec/lib/flapjack/{jabber_spec.rb → gateways/jabber_spec.rb} +9 -9
  40. data/spec/lib/flapjack/{oobetet_spec.rb → gateways/oobetet_spec.rb} +10 -10
  41. data/spec/lib/flapjack/{pagerduty_spec.rb → gateways/pagerduty_spec.rb} +11 -11
  42. data/spec/lib/flapjack/gateways/sms_spec.rb +6 -0
  43. data/spec/lib/flapjack/{web_spec.rb → gateways/web_spec.rb} +4 -4
  44. metadata +46 -79
  45. data/bin/install-flapjack-systemwide +0 -58
  46. data/features/steps/flapjack-importer_steps.rb +0 -109
  47. data/features/steps/flapjack-worker_steps.rb +0 -68
  48. data/lib/flapjack/api.rb +0 -388
  49. data/lib/flapjack/api/entity_check_presenter.rb +0 -181
  50. data/lib/flapjack/api/entity_presenter.rb +0 -66
  51. data/lib/flapjack/cli/worker_manager.rb +0 -46
  52. data/lib/flapjack/inifile.rb +0 -44
  53. data/lib/flapjack/jabber.rb +0 -383
  54. data/lib/flapjack/notifier_engine.rb +0 -40
  55. data/lib/flapjack/notifiers/mailer/init.rb +0 -3
  56. data/lib/flapjack/notifiers/mailer/mailer.rb +0 -51
  57. data/lib/flapjack/notifiers/xmpp/init.rb +0 -3
  58. data/lib/flapjack/notifiers/xmpp/xmpp.rb +0 -46
  59. data/lib/flapjack/oobetet.rb +0 -240
  60. data/lib/flapjack/pagerduty.rb +0 -242
  61. data/lib/flapjack/web.rb +0 -286
  62. data/spec.old/check_sandbox/echo +0 -3
  63. data/spec.old/check_sandbox/sandboxed_check +0 -5
  64. data/spec.old/configs/flapjack-notifier-couchdb.ini +0 -25
  65. data/spec.old/configs/flapjack-notifier.ini +0 -39
  66. data/spec.old/configs/recipients.ini +0 -14
  67. data/spec.old/helpers.rb +0 -15
  68. data/spec.old/inifile_spec.rb +0 -66
  69. data/spec.old/mock-notifiers/mock/init.rb +0 -3
  70. data/spec.old/mock-notifiers/mock/mock.rb +0 -19
  71. data/spec.old/notifier-directories/spoons/testmailer/init.rb +0 -20
  72. data/spec.old/notifier_application_spec.rb +0 -222
  73. data/spec.old/notifier_filters_spec.rb +0 -52
  74. data/spec.old/notifier_options_multiplexer_spec.rb +0 -71
  75. data/spec.old/notifier_options_spec.rb +0 -115
  76. data/spec.old/notifier_spec.rb +0 -57
  77. data/spec.old/notifiers/mailer_spec.rb +0 -36
  78. data/spec.old/notifiers/xmpp_spec.rb +0 -36
  79. data/spec.old/persistence/datamapper_spec.rb +0 -74
  80. data/spec.old/persistence/mock_persistence_backend.rb +0 -26
  81. data/spec.old/simple.ini +0 -6
  82. data/spec.old/spec.opts +0 -4
  83. data/spec.old/test-filters/blocker.rb +0 -13
  84. data/spec.old/test-filters/mock.rb +0 -13
  85. data/spec.old/transports/beanstalkd_spec.rb +0 -44
  86. data/spec.old/transports/mock_transport.rb +0 -58
  87. data/spec.old/worker_application_spec.rb +0 -62
  88. data/spec.old/worker_options_spec.rb +0 -83
  89. data/spec/lib/flapjack/notification/email_spec.rb +0 -6
  90. data/spec/lib/flapjack/notification/sms_spec.rb +0 -6
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Formats entity/check data for presentation by the API methods in Flapjack::Gateways::API.
4
+
5
+ require 'sinatra/base'
6
+
7
+ require 'flapjack/data/entity_check'
8
+
9
+ module Flapjack
10
+
11
+ module Gateways
12
+
13
+ class API < Sinatra::Base
14
+
15
+ class EntityCheckPresenter
16
+
17
+ def initialize(entity_check)
18
+ @entity_check = entity_check
19
+ end
20
+
21
+ def outages(start_time, end_time, options = {})
22
+ # hist_states is an array of hashes, with [state, timestamp, summary] keys
23
+ hist_states = @entity_check.historical_states(start_time, end_time)
24
+ return hist_states if hist_states.empty?
25
+
26
+ initial = @entity_check.historical_state_before(hist_states.first[:timestamp])
27
+ hist_states.unshift(initial) if initial
28
+
29
+ num_states = hist_states.size
30
+
31
+ hist_states.each_with_index do |obj, index|
32
+ ts = obj.delete(:timestamp)
33
+ if index == (num_states - 1)
34
+ # last (even if the only one)
35
+ obj[:start_time] = start_time ? [ts, start_time].max : ts
36
+ obj[:end_time] = end_time
37
+ elsif (index == 0)
38
+ # initial
39
+ obj[:start_time] = start_time ? [ts, start_time].max : ts
40
+ obj[:end_time] = hist_states[index + 1][:timestamp]
41
+ else
42
+ # except for first and last
43
+ obj[:start_time] = ts
44
+ obj[:end_time] = hist_states[index + 1][:timestamp]
45
+ end
46
+ obj[:duration] = obj[:end_time] ? (obj[:end_time] - obj[:start_time]) : nil
47
+ end
48
+
49
+ # p hist_states
50
+
51
+ hist_states.reject {|obj| obj[:state] == 'ok'}
52
+ end
53
+
54
+ def unscheduled_maintenance(start_time, end_time)
55
+ # unsched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
56
+ unsched_maintenance = @entity_check.maintenances(start_time, end_time,
57
+ :scheduled => false)
58
+
59
+ # to see if we start in an unscheduled maintenance period, we must check all unscheduled
60
+ # maintenances before the period and their durations
61
+ start_in_unsched = start_time.nil? ? [] :
62
+ @entity_check.maintenances(nil, start_time, :scheduled => false).select {|pu|
63
+ pu[:end_time] >= start_time
64
+ }
65
+
66
+ start_in_unsched + unsched_maintenance
67
+ end
68
+
69
+ def scheduled_maintenance(start_time, end_time)
70
+ # sched_maintenance is an array of hashes, with [duration, timestamp, summary] keys
71
+ sched_maintenance = @entity_check.maintenances(start_time, end_time,
72
+ :scheduled => true)
73
+
74
+ # to see if we start in a scheduled maintenance period, we must check all scheduled
75
+ # maintenances before the period and their durations
76
+ start_in_sched = start_time.nil? ? [] :
77
+ @entity_check.maintenances(nil, start_time, :scheduled => true).select {|ps|
78
+ ps[:end_time] >= start_time
79
+ }
80
+
81
+ start_in_sched + sched_maintenance
82
+ end
83
+
84
+ # TODO test whether the below overlapping logic is prone to off-by-one
85
+ # errors; the numbers may line up more neatly if we consider outages to
86
+ # start one second after the maintenance period ends.
87
+ #
88
+ # TODO test performance with larger data sets
89
+ def downtime(start_time, end_time)
90
+ sched_maintenances = scheduled_maintenance(start_time, end_time)
91
+
92
+ outs = outages(start_time, end_time)
93
+
94
+ total_secs = {}
95
+ percentages = {}
96
+
97
+ outs.collect {|obj| obj[:state]}.uniq.each do |st|
98
+ total_secs[st] = 0
99
+ percentages[st] = (start_time.nil? || end_time.nil?) ? nil : 0
100
+ end
101
+
102
+ unless outs.empty?
103
+
104
+ # Initially we need to check for cases where a scheduled
105
+ # maintenance period is fully covered by an outage period.
106
+ # We then create two new outage periods to cover the time around
107
+ # the scheduled maintenance period, and remove the original.
108
+
109
+ sched_maintenances.each do |sm|
110
+
111
+ split_outs = []
112
+
113
+ outs.each { |o|
114
+ next unless o[:end_time] && (o[:start_time] < sm[:start_time]) &&
115
+ (o[:end_time] > sm[:end_time])
116
+ o[:delete] = true
117
+ split_outs += [{:state => o[:state],
118
+ :start_time => o[:start_time],
119
+ :end_time => sm[:start_time],
120
+ :duration => sm[:start_time] - o[:start_time],
121
+ :summary => "#{o[:summary]} [split start]"},
122
+ {:state => o[:state],
123
+ :start_time => sm[:end_time],
124
+ :end_time => o[:end_time],
125
+ :duration => o[:end_time] - sm[:end_time],
126
+ :summary => "#{o[:summary]} [split finish]"}]
127
+ }
128
+
129
+ outs.reject! {|o| o[:delete]}
130
+ outs += split_outs
131
+ # not strictly necessary to keep the data sorted, but
132
+ # will make more sense while debgging
133
+ outs.sort! {|a,b| a[:start_time] <=> b[:start_time]}
134
+ end
135
+
136
+ sched_maintenances.each do |sm|
137
+
138
+ outs.each do |o|
139
+ next unless o[:end_time] && (sm[:start_time] < o[:end_time]) &&
140
+ (sm[:end_time] > o[:start_time])
141
+
142
+ if sm[:start_time] <= o[:start_time] &&
143
+ sm[:end_time] >= o[:end_time]
144
+
145
+ # outage is fully overlapped by the scheduled maintenance
146
+ o[:delete] = true
147
+
148
+ elsif sm[:start_time] <= o[:start_time]
149
+ # partially overlapping on the earlier side
150
+ o[:start_time] = sm[:end_time]
151
+ o[:duration] = o[:end_time] - o[:start_time]
152
+ elsif sm[:end_time] >= o[:end_time]
153
+ # partially overlapping on the later side
154
+ o[:end_time] = sm[:start_time]
155
+ o[:duration] = o[:end_time] - o[:start_time]
156
+ end
157
+ end
158
+
159
+ outs.reject! {|o| o[:delete]}
160
+ end
161
+
162
+ total_secs = outs.inject(total_secs) {|ret, o|
163
+ ret[o[:state]] += o[:duration] if o[:duration]
164
+ ret
165
+ }
166
+
167
+ unless (start_time.nil? || end_time.nil?)
168
+ total_secs.each_pair do |st, ts|
169
+ percentages[st] = (total_secs[st] * 100.0) / (end_time.to_f - start_time.to_f)
170
+ end
171
+ total_secs['ok'] = (end_time - start_time) - total_secs.values.reduce(:+)
172
+ percentages['ok'] = 100 - percentages.values.reduce(:+)
173
+ end
174
+ end
175
+
176
+ {:total_seconds => total_secs, :percentages => percentages, :downtime => outs}
177
+ end
178
+
179
+ end
180
+
181
+ end
182
+
183
+ end
184
+
185
+ end
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Formats entity data for presentation by the API methods in Flapjack::Gateways::API.
4
+ # Currently this just aggregates all of the check data for an entity, leaving
5
+ # clients to make any further calculations for themselves.
6
+
7
+ require 'sinatra/base'
8
+
9
+ require 'flapjack/gateways/api/entity_check_presenter'
10
+ require 'flapjack/data/entity_check'
11
+
12
+ module Flapjack
13
+
14
+ module Gateways
15
+
16
+ class API < Sinatra::Base
17
+
18
+ class EntityPresenter
19
+
20
+ def initialize(entity, options = {})
21
+ @entity = entity
22
+ @redis = options[:redis]
23
+ end
24
+
25
+ def outages(start_time, end_time)
26
+ checks.collect {|c|
27
+ {:check => c, :outages => check_presenter(c).outages(start_time, end_time)}
28
+ }
29
+ end
30
+
31
+ def unscheduled_maintenance(start_time, end_time)
32
+ checks.collect {|c|
33
+ {:check => c, :unscheduled_maintenance =>
34
+ check_presenter(c).unscheduled_maintenance(start_time, end_time)}
35
+ }
36
+ end
37
+
38
+ def scheduled_maintenance(start_time, end_time)
39
+ checks.collect {|c|
40
+ {:check => c, :scheduled_maintenance =>
41
+ check_presenter(c).scheduled_maintenance(start_time, end_time)}
42
+ }
43
+ end
44
+
45
+ def downtime(start_time, end_time)
46
+ checks.collect {|c|
47
+ {:check => c, :downtime =>
48
+ check_presenter(c).downtime(start_time, end_time)}
49
+ }
50
+ end
51
+
52
+ private
53
+
54
+ def checks
55
+ @check_list ||= @entity.check_list
56
+ end
57
+
58
+ def check_presenter(check)
59
+ entity_check = Flapjack::Data::EntityCheck.for_entity(@entity, check,
60
+ :redis => @redis)
61
+ presenter = Flapjack::Gateways::API::EntityCheckPresenter.new(entity_check)
62
+ end
63
+
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+
70
+ end
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'flapjack/pikelet'
4
+
5
+ module Flapjack
6
+ module Gateways
7
+
8
+ module Generic
9
+ include Flapjack::GenericPikelet
10
+ end
11
+
12
+ module Resque
13
+ include Flapjack::Pikelet
14
+ end
15
+
16
+ module Thin
17
+ include Flapjack::Pikelet
18
+
19
+ attr_accessor :port
20
+
21
+ alias_method :orig_bootstrap, :bootstrap
22
+
23
+ def bootstrap(opts = {})
24
+ return if @bootstrapped
25
+
26
+ if config = opts[:config]
27
+ @port = config['port'] ? config['port'].to_i : nil
28
+ @port = 3001 if (@port.nil? || @port <= 0 || @port > 65535)
29
+ end
30
+
31
+ orig_bootstrap(opts)
32
+ end
33
+
34
+ end
35
+
36
+ end
37
+
38
+ end
@@ -5,15 +5,15 @@ require 'erb'
5
5
  require 'haml'
6
6
  require 'socket'
7
7
 
8
- require 'flapjack/pikelet'
9
8
  require 'flapjack/data/entity_check'
10
9
 
10
+ require 'flapjack/gateways/base'
11
+
11
12
  module Flapjack
12
- module Notification
13
+ module Gateways
13
14
 
14
15
  class Email
15
-
16
- extend Flapjack::ResquePikelet
16
+ extend Flapjack::Gateways::Resque
17
17
 
18
18
  class << self
19
19
 
@@ -42,7 +42,6 @@ module Flapjack
42
42
 
43
43
  def perform(notification)
44
44
  @logger.debug "Woo, got a notification to send out: #{notification.inspect}"
45
- opts = {:logger => @logger}
46
45
 
47
46
  notification_type = notification['notification_type']
48
47
  contact_first_name = notification['contact_first_name']
@@ -0,0 +1,387 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'socket'
4
+
5
+ require 'eventmachine'
6
+ # the redis/synchrony gems need to be required in this particular order, see
7
+ # the redis-rb README for details
8
+ require 'hiredis'
9
+ require 'em-synchrony'
10
+ require 'redis/connection/synchrony'
11
+ require 'redis'
12
+
13
+ require 'chronic_duration'
14
+
15
+ require 'blather/client/client'
16
+ require 'em-synchrony/fiber_iterator'
17
+ require 'yajl/json_gem'
18
+
19
+ require 'flapjack/data/entity_check'
20
+ require 'flapjack/redis_pool'
21
+ require 'flapjack/utility'
22
+ require 'flapjack/version'
23
+
24
+ require 'flapjack/gateways/base'
25
+
26
+ module Flapjack
27
+
28
+ module Gateways
29
+
30
+ class Jabber < Blather::Client
31
+ include Flapjack::Gateways::Generic
32
+ include Flapjack::Utility
33
+
34
+ log = Logger.new(STDOUT)
35
+ # log.level = Logger::DEBUG
36
+ log.level = Logger::INFO
37
+ Blather.logger = log
38
+
39
+ alias_method :generic_bootstrap, :bootstrap
40
+ alias_method :generic_cleanup, :cleanup
41
+
42
+ def bootstrap(opts = {})
43
+ generic_bootstrap(opts)
44
+
45
+ @redis_config = opts[:redis_config]
46
+ @redis = Flapjack::RedisPool.new(:config => @redis_config, :size => 1)
47
+
48
+ @buffer = []
49
+ @hostname = Socket.gethostname
50
+ end
51
+
52
+ def cleanup
53
+ @redis.empty! if @redis
54
+ @redis_handler.empty! if @redis_handler
55
+ generic_cleanup
56
+ end
57
+
58
+ def setup
59
+ @flapjack_jid = Blather::JID.new((@config['jabberid'] || 'flapjack') + '/' + @hostname)
60
+
61
+ super(@flapjack_jid, @config['password'], @config['server'], @config['port'].to_i)
62
+
63
+ logger.debug("Building jabber connection with jabberid: " +
64
+ @flapjack_jid.to_s + ", port: " + @config['port'].to_s +
65
+ ", server: " + @config['server'].to_s + ", password: " +
66
+ @config['password'].to_s)
67
+
68
+ register_handler :ready do |stanza|
69
+ EventMachine::Synchrony.next_tick do
70
+ on_ready(stanza)
71
+ end
72
+ end
73
+
74
+ register_handler :message, :groupchat?, :body => /^flapjack:\s+/ do |stanza|
75
+ EventMachine::Synchrony.next_tick do
76
+ on_groupchat(stanza)
77
+ end
78
+ end
79
+
80
+ register_handler :message, :chat? do |stanza|
81
+ EventMachine::Synchrony.next_tick do
82
+ on_chat(stanza)
83
+ end
84
+ end
85
+
86
+ register_handler :disconnected do |stanza|
87
+ ret = true
88
+ EventMachine::Synchrony.next_tick do
89
+ ret = on_disconnect(stanza)
90
+ end
91
+ ret
92
+ end
93
+ end
94
+
95
+ # Join the MUC Chat room after connecting.
96
+ def on_ready(stanza)
97
+ return if should_quit? && @shutting_down
98
+ @redis_handler ||= Flapjack::RedisPool.new(:config => @redis_config, :size => 1)
99
+ @connected_at = Time.now.to_i
100
+ logger.info("Jabber Connected")
101
+ if @config['rooms'] && @config['rooms'].length > 0
102
+ @config['rooms'].each do |room|
103
+ logger.info("Joining room #{room}")
104
+ presence = Blather::Stanza::Presence.new
105
+ presence.from = @flapjack_jid
106
+ presence.to = Blather::JID.new("#{room}/#{@config['alias']}")
107
+ presence << "<x xmlns='http://jabber.org/protocol/muc'/>"
108
+ write presence
109
+ say(room, "flapjack jabber gateway started at #{Time.now}, hello!", :groupchat)
110
+ end
111
+ end
112
+ return if @buffer.empty?
113
+ while stanza = @buffer.shift
114
+ @logger.debug("Sending a buffered jabber message to: #{stanza.to}, using: #{stanza.type}, message: #{stanza.body}")
115
+ write(stanza)
116
+ end
117
+ end
118
+
119
+ def interpreter(command)
120
+ msg = nil
121
+ action = nil
122
+ entity_check = nil
123
+ case
124
+ when command =~ /^ACKID\s+(\d+)(?:\s*(.*?)(?:\s*duration.*?(\d+.*\w+.*))?)$/i;
125
+ ackid = $1
126
+ comment = $2
127
+ duration_str = $3
128
+
129
+ error = nil
130
+ dur = nil
131
+
132
+ if comment.nil? || (comment.length == 0)
133
+ error = "please provide a comment, eg \"flapjack: ACKID #{$1} AL looking\""
134
+ elsif duration_str
135
+ # a fairly liberal match above, we'll let chronic_duration do the heavy lifting
136
+ dur = ChronicDuration.parse(duration_str)
137
+ end
138
+
139
+ four_hours = 4 * 60 * 60
140
+ duration = (dur.nil? || (dur <= 0)) ? four_hours : dur
141
+
142
+ event_id = @redis_handler.hget('unacknowledged_failures', ackid)
143
+
144
+ if event_id.nil?
145
+ error = "not found"
146
+ else
147
+ entity_check = Flapjack::Data::EntityCheck.for_event_id(event_id, :redis => @redis_handler)
148
+ error = "unknown entity" if entity_check.nil?
149
+ end
150
+
151
+ if entity_check && entity_check.in_unscheduled_maintenance?
152
+ error = "#{event_id} is already acknowledged"
153
+ end
154
+
155
+ if error
156
+ msg = "ERROR - couldn't ACK #{ackid} - #{error}"
157
+ else
158
+ msg = "ACKing #{entity_check.check} on #{entity_check.entity_name} (#{ackid})"
159
+ action = Proc.new {
160
+ entity_check.create_acknowledgement('summary' => (comment || ''),
161
+ 'acknowledgement_id' => ackid, 'duration' => duration)
162
+ }
163
+ end
164
+
165
+ when command =~ /^help$/
166
+ msg = "commands: \n"
167
+ msg += " ACKID <id> <comment> [duration: <time spec>] \n"
168
+ msg += " find entities matching /pattern/ \n"
169
+ msg += " test notifications for <entity>[:<check>] \n"
170
+ msg += " identify \n"
171
+ msg += " help \n"
172
+
173
+ when command =~ /^identify$/
174
+ t = Process.times
175
+ boot_time = Time.at(@redis_handler.get('boot_time').to_i)
176
+ msg = "Flapjack #{Flapjack::VERSION} process #{Process.pid} on #{`hostname -f`.chomp} \n"
177
+ msg += "Boot time: #{boot_time}\n"
178
+ msg += "User CPU Time: #{t.utime}\n"
179
+ msg += "System CPU Time: #{t.stime}\n"
180
+ msg += `uname -a`.chomp + "\n"
181
+
182
+ when command =~ /^test notifications for\s+([a-z0-9\-\.]+)(:(.+))?$/i
183
+ entity_name = $1
184
+ check_name = $3 ? $3 : 'test'
185
+
186
+ msg = "so you want me to test notifications for entity: #{entity_name}, check: #{check_name} eh? ... well OK!"
187
+
188
+ entity = Flapjack::Data::Entity.find_by_name(entity_name, :redis => @redis_handler)
189
+ if entity
190
+ summary = "Testing notifications to all contacts interested in entity: #{entity.name}, check: #{check_name}"
191
+
192
+ entity_check = Flapjack::Data::EntityCheck.for_entity(entity, check_name, :redis => @redis_handler)
193
+ puts entity_check.inspect
194
+ entity_check.test_notifications('summary' => summary)
195
+
196
+ else
197
+ msg = "yeah, no i can't see #{entity_name} in my systems"
198
+ end
199
+
200
+ when command =~ /^(find )?entities matching\s+\/(.*)\/.*$/i
201
+ pattern = $2.chomp.strip
202
+ entity_list = Flapjack::Data::Entity.find_all_name_matching(pattern, :redis => @redis_handler)
203
+ max_showable = 30
204
+ number_found = entity_list.length
205
+ entity_list = entity_list[0..(max_showable - 1)] if number_found > max_showable
206
+
207
+ case
208
+ when number_found == 0
209
+ msg = "found no entities matching /#{pattern}/"
210
+ when number_found == 1
211
+ msg = "found #{number_found} entity matching /#{pattern}/ ... \n"
212
+ when number_found > max_showable
213
+ msg = "showing first #{max_showable} of #{number_found} entities found matching /#{pattern}/\n"
214
+ else
215
+ msg = "found #{number_found} entities matching /#{pattern}/ ... \n"
216
+ end
217
+ msg += entity_list.join(', ') unless entity_list.empty?
218
+
219
+ when command =~ /^(.*)/
220
+ words = $1
221
+ msg = "what do you mean, '#{words}'? Type 'help' for a list of acceptable commands."
222
+
223
+ end
224
+
225
+ {:msg => msg, :action => action}
226
+ end
227
+
228
+ def on_groupchat(stanza)
229
+ return if should_quit? && @shutting_down
230
+ logger.debug("groupchat message received: #{stanza.inspect}")
231
+
232
+ if stanza.body =~ /^flapjack:\s+(.*)/
233
+ command = $1
234
+ end
235
+
236
+ results = interpreter(command)
237
+ msg = results[:msg]
238
+ action = results[:action]
239
+
240
+ if msg || action
241
+ say(stanza.from.stripped, msg, :groupchat)
242
+ logger.debug("Sent to group chat: #{msg}")
243
+ action.call if action
244
+ end
245
+ end
246
+
247
+ def on_chat(stanza)
248
+ return if should_quit? && @shutting_down
249
+ logger.debug("chat message received: #{stanza.inspect}")
250
+
251
+ if stanza.body =~ /^flapjack:\s+(.*)/
252
+ command = $1
253
+ else
254
+ command = stanza.body
255
+ end
256
+
257
+ results = interpreter(command)
258
+ msg = results[:msg]
259
+ action = results[:action]
260
+
261
+ if msg || action
262
+ say(stanza.from.stripped, msg, :chat)
263
+ logger.debug("Sent to #{stanza.from.stripped}: #{msg}")
264
+ action.call if action
265
+ end
266
+ end
267
+
268
+ # returning true to prevent the reactor loop from stopping
269
+ def on_disconnect(stanza)
270
+ return true if should_quit? && @shutting_down
271
+ logger.warn("jabbers disconnected! reconnecting in 1 second ...")
272
+ EventMachine::Timer.new(1) do
273
+ connect # Blather::Client.connect
274
+ end
275
+ true
276
+ end
277
+
278
+ def say(to, msg, using = :chat)
279
+ stanza = Blather::Stanza::Message.new(to, msg, using)
280
+ if connected?
281
+ @logger.debug("Sending a jabber message to: #{to.to_s}, using: #{using.to_s}, message: #{msg}")
282
+ write(stanza)
283
+ else
284
+ @logger.debug("Buffering a jabber message to: #{to.to_s}, using: #{using.to_s}, message: #{msg}")
285
+ @buffer << stanza
286
+ end
287
+ end
288
+
289
+ def add_shutdown_event(opts = {})
290
+ return unless redis = opts[:redis]
291
+ redis.rpush(@config['queue'], JSON.generate('notification_type' => 'shutdown'))
292
+ end
293
+
294
+ def main
295
+ logger.debug("New Jabber pikelet with the following options: #{@config.inspect}")
296
+
297
+ count_timer = EM::Synchrony.add_periodic_timer(30) do
298
+ logger.debug("connection count: #{EM.connection_count} #{Time.now.to_s}.#{Time.now.usec.to_s}")
299
+ end
300
+
301
+ keepalive_timer = EM::Synchrony.add_periodic_timer(60) do
302
+ logger.debug("calling keepalive on the jabber connection")
303
+ write(' ') if connected?
304
+ end
305
+
306
+ setup
307
+ connect # Blather::Client.connect
308
+
309
+ # simplified to use a single queue only as it makes the shutdown logic easier
310
+ queue = @config['queue']
311
+ events = {}
312
+
313
+ until should_quit? && @shutting_down
314
+
315
+ # FIXME: should also check if presence has been established in any group chat rooms that are
316
+ # configured before starting to process events, otherwise the first few may get lost (send
317
+ # before joining the group chat rooms)
318
+ if connected?
319
+ logger.debug("jabber is connected so commencing blpop on #{queue}")
320
+ events[queue] = @redis.blpop(queue, 0)
321
+ event = Yajl::Parser.parse(events[queue][1])
322
+ type = event['notification_type'] || 'unknown'
323
+ logger.debug('jabber notification event received')
324
+ logger.debug(event.inspect)
325
+ if 'shutdown'.eql?(type)
326
+ if should_quit?
327
+ @shutting_down = true
328
+ EventMachine::Synchrony.next_tick do
329
+ # get delays without the next_tick
330
+ close # Blather::Client.close
331
+ end
332
+ end
333
+ else
334
+ entity, check = event['event_id'].split(':')
335
+ state = event['state']
336
+ summary = event['summary']
337
+ duration = event['duration'] ? time_period_in_words(event['duration']) : '4 hours'
338
+ address = event['address']
339
+
340
+ logger.debug("processing jabber notification address: #{address}, event: #{entity}:#{check}, state: #{state}, summary: #{summary}")
341
+
342
+ ack_str =
343
+ event['event_count'] &&
344
+ !state.eql?('ok') &&
345
+ !'acknowledgement'.eql?(type) &&
346
+ !'test'.eql?(type) ?
347
+ "::: flapjack: ACKID #{event['event_count']} " : ''
348
+
349
+ type = 'unknown' unless type
350
+
351
+ maint_str = case type
352
+ when 'acknowledgement'
353
+ "has been acknowledged, unscheduled maintenance created for #{duration}"
354
+ when 'test'
355
+ ''
356
+ else
357
+ "is #{state.upcase}"
358
+ end
359
+
360
+ # FIXME - should probably put all the message composition stuff in one place so
361
+ # the logic isn't duplicated in each notification channel.
362
+ # TODO - templatise the messages so they can be customised without changing core code
363
+ headline = "test".eql?(type.downcase) ? "TEST NOTIFICATION" : type.upcase
364
+
365
+ msg = "#{headline} #{ack_str}::: \"#{check}\" on #{entity} #{maint_str} ::: #{summary}"
366
+
367
+ chat_type = :chat
368
+ chat_type = :groupchat if @config['rooms'] && @config['rooms'].include?(address)
369
+ EventMachine::Synchrony.next_tick do
370
+ say(Blather::JID.new(address), msg, chat_type)
371
+ end
372
+ end
373
+ else
374
+ logger.debug("not connected, sleep 1 before retry")
375
+ EM::Synchrony.sleep(1)
376
+ end
377
+ end
378
+
379
+ count_timer.cancel
380
+ keepalive_timer.cancel
381
+ end
382
+
383
+ end
384
+
385
+ end
386
+ end
387
+