flapjack 0.7.1 → 0.7.2

Sign up to get free protection for your applications and to get access to all the features.
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