flapjack 0.7.1 → 0.7.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.
Files changed (45) hide show
  1. data/CHANGELOG.md +29 -0
  2. data/README.md +1 -4
  3. data/bin/flapjack +24 -2
  4. data/bin/flapjack-nagios-receiver +4 -2
  5. data/bin/receive-events +191 -0
  6. data/bin/simulate-failed-check +144 -0
  7. data/features/notification_rules.feature +63 -2
  8. data/features/steps/events_steps.rb +15 -3
  9. data/flapjack.gemspec +1 -1
  10. data/lib/flapjack/data/contact.rb +15 -9
  11. data/lib/flapjack/data/entity.rb +19 -1
  12. data/lib/flapjack/data/entity_check.rb +12 -0
  13. data/lib/flapjack/data/event.rb +10 -2
  14. data/lib/flapjack/data/notification.rb +12 -8
  15. data/lib/flapjack/data/notification_rule.rb +3 -1
  16. data/lib/flapjack/executive.rb +71 -17
  17. data/lib/flapjack/gateways/api.rb +5 -2
  18. data/lib/flapjack/gateways/jabber.rb +26 -17
  19. data/lib/flapjack/gateways/web.rb +54 -9
  20. data/lib/flapjack/gateways/web/public/css/bootstrap-responsive.min.css +9 -0
  21. data/lib/flapjack/gateways/web/public/css/bootstrap.min.css +9 -0
  22. data/lib/flapjack/gateways/web/public/css/flapjack.css +51 -0
  23. data/lib/flapjack/gateways/web/public/img/flapjack_white_bg_400_353.jpeg +0 -0
  24. data/lib/flapjack/gateways/web/public/img/glyphicons-halflings-white.png +0 -0
  25. data/lib/flapjack/gateways/web/public/img/glyphicons-halflings.png +0 -0
  26. data/lib/flapjack/gateways/web/public/js/bootstrap.min.js +6 -0
  27. data/lib/flapjack/gateways/web/views/_foot.haml +8 -0
  28. data/lib/flapjack/gateways/web/views/_head.haml +10 -0
  29. data/lib/flapjack/gateways/web/views/_nav.haml +9 -3
  30. data/lib/flapjack/gateways/web/views/check.haml +140 -138
  31. data/lib/flapjack/gateways/web/views/checks.haml +49 -0
  32. data/lib/flapjack/gateways/web/views/contact.haml +78 -37
  33. data/lib/flapjack/gateways/web/views/contacts.haml +23 -17
  34. data/lib/flapjack/gateways/web/views/entities.haml +28 -0
  35. data/lib/flapjack/gateways/web/views/entity.haml +44 -0
  36. data/lib/flapjack/gateways/web/views/index.haml +27 -44
  37. data/lib/flapjack/gateways/web/views/self_stats.haml +65 -22
  38. data/lib/flapjack/version.rb +1 -1
  39. data/spec/lib/flapjack/executive_spec.rb +6 -2
  40. data/spec/lib/flapjack/gateways/api_spec.rb +15 -0
  41. data/spec/lib/flapjack/gateways/web/views/contact.haml_spec.rb +2 -1
  42. data/spec/lib/flapjack/gateways/web/views/index.haml_spec.rb +3 -2
  43. data/spec/lib/flapjack/gateways/web_spec.rb +23 -9
  44. data/tmp/create_events_failure.rb +6 -4
  45. metadata +23 -12
data/CHANGELOG.md ADDED
@@ -0,0 +1,29 @@
1
+ ## Flapjack Changelog
2
+
3
+ # 0.7.2 - 2013-05-06
4
+ - Feature: executive instance keys now expire after 7 days, touched every event gh-111 (@jessereynolds)
5
+ - Feature: slightly less sucky looking web UI, also now includes entity listing screens (@jessereynolds)
6
+ - Feature: expose notification rules and intervals via the Web UI gh-150, gh-151 (@jessereynolds)
7
+ - Feature: command line - support "--version", "help" etc gh-134 (@jessereynolds)
8
+ - Feature: replay events from another flapjack gh-138 (@jessereynolds)
9
+ - Bug: recovery is not resetting notification intervals gh-136 (@jessereynolds)
10
+ - Bug: recoveries are blocked for users with notification rules gh-148 (@jessereynolds)
11
+ - Bug: jabber gateway now uses configured alias for commands gh-138 (@jessereynolds)
12
+ - Bug: jabber gateway was crashing on querying entities with invalid regex gh-147 (@jessereynolds)
13
+ - Bug: handle media addresses correctly when adding contacts and generating messages gh-145 (@jessereynolds)
14
+
15
+ # 0.7.1 - 2013-04-24
16
+ - Feature: archive incoming events in a sliding window gh-127 (@jessereynolds)
17
+ - Bug: Unable to retrieve status of a check containing non word characters via the API gh-117 (@ali-graham)
18
+ - Bug: Disable Thin's loading of Daemons gh-133 (@jessereynolds, thanks @johnf)
19
+
20
+ # 0.7.0 - 2013-04-18
21
+ - Feature: Introduce Notification Rules gh-55 (@jessereynolds)
22
+ - Feature: Tagging on contacts and entities, expose via API gh-125 (@ali-graham)
23
+ - Feature: API improvements (notification rules, contact's timezone and notification intervals per media, tags) (@ali-graham, @jessereynolds)
24
+ - Feature: Contact mass update (rather than drop all then import) gh-124 (@ali-graham)
25
+ - Feature: Improve error handling (log file paths, permissions), expose internal stats as json gh-122 (@auxesis)
26
+ - Incompatable Change: POST /contacts in the API now includes intervals per media and is incompatible with previous versions
27
+
28
+ # 0.6.61 - 2013-01-11
29
+ - todo (and previous versions)
data/README.md CHANGED
@@ -1,9 +1,6 @@
1
1
  # Flapjack
2
2
 
3
- [![Travis CI Status][id_travis_img]][id_travis_link]
4
-
5
- [id_travis_link]: https://secure.travis-ci.org/#!/flpjck/flapjack
6
- [id_travis_img]: https://secure.travis-ci.org/flpjck/flapjack.png
3
+ [![Build Status](https://travis-ci.org/flpjck/flapjack.png)](https://travis-ci.org/flpjck/flapjack)
7
4
 
8
5
  [flapjack-project.com](http://flapjack-project.com/)
9
6
 
data/bin/flapjack CHANGED
@@ -10,6 +10,7 @@ require 'optparse'
10
10
  require 'ostruct'
11
11
 
12
12
  require 'flapjack/configuration'
13
+ require 'flapjack/version'
13
14
 
14
15
  options = OpenStruct.new
15
16
  options.config = File.join('etc', 'flapjack_config.yaml')
@@ -17,7 +18,7 @@ options.daemonize = nil
17
18
 
18
19
  @exe = File.basename(__FILE__)
19
20
 
20
- OptionParser.new do |opts|
21
+ optparse = OptionParser.new do |opts|
21
22
  opts.banner = "Usage: flapjack COMMAND [OPTIONS]"
22
23
 
23
24
  opts.separator ""
@@ -27,6 +28,8 @@ OptionParser.new do |opts|
27
28
  opts.separator " restart #{" " * 23} (re)start flapjack"
28
29
  opts.separator " reload #{" " * 24} reload flapjack configuration"
29
30
  opts.separator " status #{" " * 24} see if flapjack is running"
31
+ opts.separator " version #{" " * 23} display flapjack version and exit"
32
+ opts.separator " help #{" " * 26} display this usage info"
30
33
  opts.separator ""
31
34
  opts.separator "Options"
32
35
 
@@ -46,7 +49,13 @@ OptionParser.new do |opts|
46
49
  options.log_path = l
47
50
  end
48
51
 
49
- end.parse!(ARGV)
52
+ opts.on("-v", "--version", "display flapjack version") do |v|
53
+ options.version = v
54
+ end
55
+
56
+
57
+ end
58
+ optparse.parse!(ARGV)
50
59
 
51
60
  FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
52
61
 
@@ -71,6 +80,10 @@ daemonize = options.daemonize.nil? ?
71
80
  !!config_env['daemonize'] :
72
81
  options.daemonize
73
82
 
83
+ if options.version
84
+ puts Flapjack::VERSION
85
+ exit
86
+ end
74
87
 
75
88
  flapjack_coord = Proc.new {
76
89
  require 'flapjack/coordinator'
@@ -177,12 +190,21 @@ when "status"
177
190
  exit 3
178
191
  end
179
192
 
193
+ when "help"
194
+ puts optparse
195
+ exit
196
+
197
+ when "version"
198
+ puts Flapjack::VERSION
199
+ exit
200
+
180
201
  else
181
202
  if ARGV.nil? || ARGV.empty?
182
203
  puts "No command provided"
183
204
  else
184
205
  puts "Unknown command provided: '#{ARGV[0]}'"
185
206
  end
207
+ puts "\n#{optparse}"
186
208
  exit 1
187
209
 
188
210
  end
@@ -74,7 +74,7 @@ options.daemonize = nil
74
74
 
75
75
  exe = File.basename(__FILE__)
76
76
 
77
- OptionParser.new do |opts|
77
+ optparse = OptionParser.new do |opts|
78
78
  opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
79
79
 
80
80
  opts.separator ""
@@ -106,7 +106,8 @@ OptionParser.new do |opts|
106
106
  options.log_path = l
107
107
  end
108
108
 
109
- end.parse!(ARGV)
109
+ end
110
+ optparse.parse!(ARGV)
110
111
 
111
112
  FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
112
113
 
@@ -219,6 +220,7 @@ else
219
220
  else
220
221
  puts "Unknown command provided: '#{ARGV[0]}'"
221
222
  end
223
+ puts "\n#{optparse}"
222
224
  exit 1
223
225
 
224
226
  end
@@ -0,0 +1,191 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib/')
4
+ $: << File.dirname(__FILE__) + '/../lib'
5
+ end
6
+
7
+ require 'optparse'
8
+ require 'ostruct'
9
+ require 'redis'
10
+ require 'yajl/json_gem'
11
+
12
+ require 'flapjack/configuration'
13
+
14
+ def pike(message)
15
+ puts "piking out: #{message}"
16
+ exit 1
17
+ end
18
+
19
+ def send_event(event, opts)
20
+ redis = opts[:redis]
21
+ redis.lpush 'events', event
22
+ end
23
+
24
+ def receive(opts)
25
+ redis = Redis.new(opts[:redis_options])
26
+ source_redis = Redis.new(:url => opts[:source])
27
+
28
+ archives = get_archive_keys_stats(source_redis)
29
+ raise "found no archives!" unless archives && archives.length > 0
30
+
31
+ puts "found archives: #{archives.inspect}"
32
+
33
+ # each archive bucket is a redis list that is written
34
+ # with brpoplpush, that is newest items are added to the left (head)
35
+ # of the list, so oldest events are to be found at the tail of the list.
36
+ #
37
+ # the index of these archives, in the 'archives' array, also stores the
38
+ # redis key names for each bucket in oldest to newest
39
+ events_sent = 0
40
+ case
41
+ when opts[:all]
42
+ archive_key = archives[0][:name]
43
+ cursor = -1
44
+ when opts[:last], opts[:time]
45
+ raise "Sorry, unimplemented"
46
+ else
47
+ # wait for the next event to be archived, so point the cursor at a non-existant
48
+ # slot in the list, the one before the 0'th
49
+ archive_key = archives[-1][:name]
50
+ cursor = -1 - archives[-1][:size]
51
+ end
52
+
53
+ puts archive_key
54
+
55
+ loop do
56
+ new_archive_key = false
57
+ # something to read at cursor?
58
+ event = source_redis.lindex(archive_key, cursor)
59
+ if event
60
+ send_event(event, :redis => redis)
61
+ events_sent += 1
62
+ print "#{events_sent} " if events_sent % 1000 == 0
63
+ cursor -= 1
64
+ else
65
+ puts "\narchive key: #{archive_key}, cursor: #{cursor}"
66
+ # do we need to look at the next archive bucket?
67
+ archives = get_archive_keys_stats(source_redis)
68
+ i = archives.index {|a| a[:name] == archive_key }
69
+ if archives[i][:size] = (cursor.abs + 1)
70
+ if archives[i + 1]
71
+ archive_key = archives[i + 1][:name]
72
+ puts archive_key
73
+ cursor = -1
74
+ new_archive_key = true
75
+ else
76
+ return unless opts[:follow]
77
+ end
78
+ end
79
+ sleep 1 unless new_archive_key
80
+ end
81
+ end
82
+ end
83
+
84
+ def get_archive_keys_stats(source_redis)
85
+ source_redis.keys("events_archive:*").sort.map {|a|
86
+ { :name => a,
87
+ :size => source_redis.llen(a) }
88
+ }
89
+ end
90
+
91
+ options = OpenStruct.new
92
+ options.config = nil
93
+ options.daemonize = nil
94
+
95
+ exe = File.basename(__FILE__)
96
+
97
+ optparse = OptionParser.new do |opts|
98
+ opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
99
+
100
+ opts.separator ""
101
+ opts.separator "Commands"
102
+ opts.separator " help"
103
+ opts.separator ""
104
+ opts.separator "Options"
105
+
106
+ opts.on("-c", "--config [PATH]", String, "PATH to the config file to use") do |c|
107
+ options.config = c
108
+ end
109
+
110
+ opts.on("-s", "--source URL", String, "URL of source redis database, eg redis://localhost:6379/0") do |s|
111
+ options.source = s
112
+ end
113
+
114
+ opts.on("-f", "--follow", String, "keep reading events as they are archived on the source") do |f|
115
+ options.follow = true
116
+ end
117
+
118
+ opts.on("-a", "--all", String, "replay all archived events from the source") do |a|
119
+ options.all = true
120
+ end
121
+
122
+ opts.on("-l", "--last COUNT", String, "replay the last COUNT events from the source") do |l|
123
+ options.count = l
124
+ end
125
+
126
+ opts.on("-t", "--time TIME", String, "replay all events archived on the source since TIME") do |t|
127
+ options.since = t
128
+ end
129
+
130
+ end
131
+ optparse.parse!(ARGV)
132
+
133
+ FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
134
+
135
+ if options.config
136
+ config_file = options.config
137
+ pike "specified config file cannot be read" unless File.readable?(config_file)
138
+ else
139
+ [ 'etc/flapjack_config.yaml',
140
+ File.dirname(__FILE__) + 'etc/flapjack_config.yaml',
141
+ '/etc/flapjack/flapjack_config.yaml' ].each do |candidate|
142
+ if File.readable?(candidate)
143
+ config_file = candidate
144
+ break
145
+ else
146
+ puts "not found and/or not readable: #{candidate}"
147
+ end
148
+ end
149
+ pike "no config file specified and none found in default locations" unless config_file
150
+ end
151
+
152
+ config = Flapjack::Configuration.new
153
+ config.load(config_file)
154
+ config_env = config.all
155
+ redis_options = config.for_redis
156
+
157
+ if config_env.nil? || config_env.empty?
158
+ puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{options.config}'"
159
+ exit(false)
160
+ end
161
+
162
+ unless options.source
163
+ puts "--source URL is required"
164
+ exit 1
165
+ end
166
+
167
+ unless options.follow || options.all
168
+ puts "one or both of --follow or --all is required"
169
+ exit 1
170
+ end
171
+
172
+ case ARGV[0]
173
+ when "help"
174
+ puts optparse
175
+ exit
176
+ else
177
+ unless ARGV.nil? || ARGV.empty?
178
+ puts "Unknown command provided: '#{ARGV[0]}'"
179
+ puts "\n#{optparse}"
180
+ exit 1
181
+ end
182
+ end
183
+
184
+ receive(:follow => options.follow,
185
+ :all => options.all,
186
+ :source => options.source,
187
+ :last => options.last,
188
+ :time => options.time,
189
+ :redis_options => redis_options)
190
+
191
+
@@ -0,0 +1,144 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ unless $:.include?(File.dirname(__FILE__) + '/../lib/')
4
+ $: << File.dirname(__FILE__) + '/../lib'
5
+ end
6
+
7
+ require 'optparse'
8
+ require 'ostruct'
9
+ require 'redis'
10
+ require 'yajl/json_gem'
11
+ require 'eventmachine'
12
+
13
+ require 'flapjack/configuration'
14
+
15
+ def pike(message)
16
+ puts "piking out: #{message}"
17
+ exit 1
18
+ end
19
+
20
+ def send_event(event, opts)
21
+ redis = opts[:redis]
22
+ redis.lpush 'events', event.to_json
23
+ end
24
+
25
+ def fail(opts)
26
+ redis = Redis.new(opts[:redis_options])
27
+ stop_after = opts[:minutes].to_i * 60
28
+ recover = opts[:recover]
29
+ event = {
30
+ 'entity' => opts[:entity] || 'foo-app-01',
31
+ 'check' => opts[:check] || 'HTTP',
32
+ 'type' => 'service',
33
+ 'timestamp' => Time.now.to_i
34
+ }
35
+ failure = event.merge('state' => 'critical', 'summary' => 'Connection refused')
36
+ recovery = event.merge('state' => 'ok', 'summary' => '200 OK, 37 ms')
37
+ key = "#{event['entity']}:#{event['check']}"
38
+
39
+ puts "#{Time.now}: sending failure event for #{key}"
40
+ send_event(failure, :redis => redis)
41
+
42
+ EM.run {
43
+
44
+ EM.add_timer(stop_after) do
45
+ puts "#{Time.now}: stopping"
46
+ if recover
47
+ puts "#{Time.now}: sending recovery event for #{key}"
48
+ send_event(recovery, :redis => redis)
49
+ end
50
+ EM.stop
51
+ end
52
+
53
+ EM.add_periodic_timer(10) do
54
+ puts "#{Time.now}: sending failure event for #{key}"
55
+ send_event(failure, :redis => redis)
56
+ end
57
+
58
+ }
59
+
60
+ end
61
+
62
+
63
+ options = OpenStruct.new
64
+ options.config = nil
65
+ options.daemonize = nil
66
+
67
+ exe = File.basename(__FILE__)
68
+
69
+ optparse = OptionParser.new do |opts|
70
+ opts.banner = "Usage: #{exe} COMMAND [OPTIONS]"
71
+
72
+ opts.separator ""
73
+ opts.separator "Commands"
74
+ opts.separator " fail-and-recover"
75
+ opts.separator " fail"
76
+ opts.separator ""
77
+ opts.separator "Options"
78
+
79
+ opts.on("-c", "--config [PATH]", String, "PATH to the config file to use") do |c|
80
+ options.config = c
81
+ end
82
+
83
+ opts.on("-t", "--time MINUTES", String, "MINUTES to generate failure events for") do |t|
84
+ options.minutes = t
85
+ end
86
+
87
+ opts.on("-e", "--entity ENTITY", String, "ENTITY to generate failure events for ('foo-app-01')") do |e|
88
+ options.entity = e
89
+ end
90
+
91
+ opts.on("-k", "--check CHECK", String, "CHECK to generate failure events for ('HTTP')") do |k|
92
+ options.check = k
93
+ end
94
+
95
+ end
96
+ optparse.parse!(ARGV)
97
+
98
+ FLAPJACK_ENV = ENV['FLAPJACK_ENV'] || 'development'
99
+
100
+ if options.config
101
+ config_file = options.config
102
+ pike "specified config file cannot be read" unless File.readable?(config_file)
103
+ else
104
+ [ 'etc/flapjack_config.yaml',
105
+ File.dirname(__FILE__) + 'etc/flapjack_config.yaml',
106
+ '/etc/flapjack/flapjack_config.yaml' ].each do |candidate|
107
+ if File.readable?(candidate)
108
+ config_file = candidate
109
+ break
110
+ else
111
+ puts "not found and/or not readable: #{candidate}"
112
+ end
113
+ end
114
+ pike "no config file specified and none found in default locations" unless config_file
115
+ end
116
+
117
+ config = Flapjack::Configuration.new
118
+ config.load(config_file)
119
+ config_env = config.all
120
+ redis_options = config.for_redis
121
+
122
+ if config_env.nil? || config_env.empty?
123
+ puts "No config data for environment '#{FLAPJACK_ENV}' found in '#{options.config}'"
124
+ exit(false)
125
+ end
126
+
127
+ options = {:redis_options => redis_options, :minutes => options.minutes,
128
+ :entity => options.entity, :check => options.check}
129
+ case ARGV[0]
130
+ when "fail-and-recover"
131
+ fail(options.merge(:recover => true))
132
+ puts " done."
133
+ when "fail"
134
+ fail(options.merge(:recover => false))
135
+ puts " done."
136
+ else
137
+ if ARGV.nil? || ARGV.empty?
138
+ puts "No command provided."
139
+ else
140
+ puts "Unknown command provided: '#{ARGV[0]}'"
141
+ end
142
+ puts "\n#{optparse}"
143
+ exit 1
144
+ end