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