flapjack 0.6.51 → 0.6.52

Sign up to get free protection for your applications and to get access to all the features.
@@ -152,3 +152,11 @@ Feature: events
152
152
  Given check 'abc' for entity 'def' is in a failure state
153
153
  When an acknowledgement event is received for check 'abc' on entity 'def'
154
154
  Then a notification should be generated for check 'abc' on entity 'def'
155
+
156
+ Scenario: Brief failure then OK
157
+ Given check 'abc' for entity 'def' is in an ok state
158
+ When a failure event is received for check 'abc' on entity 'def'
159
+ And 10 seconds passes
160
+ And an ok event is received for check 'abc' on entity 'def'
161
+ Then a notification should not be generated for check 'abc' on entity 'def'
162
+
@@ -161,4 +161,3 @@ end
161
161
  Then /^show me the notifications?$/ do
162
162
  puts @app.logger.messages.join("\n")
163
163
  end
164
-
@@ -5,6 +5,8 @@
5
5
 
6
6
  require 'set'
7
7
 
8
+ require 'flapjack/data/entity'
9
+
8
10
  module Flapjack
9
11
 
10
12
  module Data
@@ -30,12 +32,14 @@ module Flapjack
30
32
  def self.delete_all(options = {})
31
33
  raise "Redis connection not set" unless redis = options[:redis]
32
34
 
33
- redis.del( redis.keys("contact:*") +
34
- redis.keys("contact_media:*") +
35
- # FIXME: when we do source tagging we can properly
36
- # clean up contacts_for: keys
37
- # redis.keys('contacts_for:*') +
38
- redis.keys("contact_pagerduty:*") )
35
+ keys_to_delete = redis.keys("contact:*") +
36
+ redis.keys("contact_media:*") +
37
+ # FIXME: when we do source tagging we can properly
38
+ # clean up contacts_for: keys
39
+ # redis.keys('contacts_for:*') +
40
+ redis.keys("contact_pagerduty:*")
41
+
42
+ redis.del(keys_to_delete) unless keys_to_delete.length == 0
39
43
  end
40
44
 
41
45
  def self.find_by_id(contact_id, options = {})
@@ -110,7 +110,12 @@ module Flapjack
110
110
  end
111
111
 
112
112
  def tags
113
- @tags ||= load_tags
113
+ @tags ||= ::Set.new( @redis.keys("#{TAG_PREFIX}:*").inject([]) {|memo, entity_tag|
114
+ if Flapjack::Data::Tag.find(entity_tag, :redis => @redis).include?(@id.to_s)
115
+ memo << entity_tag.sub(/^#{TAG_PREFIX}:/, '')
116
+ end
117
+ memo
118
+ } )
114
119
  end
115
120
 
116
121
  def add_tags(*enum)
@@ -139,16 +144,6 @@ module Flapjack
139
144
  @logger = options[:logger]
140
145
  end
141
146
 
142
- def load_tags
143
- entity_tags = ::Set.new
144
- tag_data = @redis.keys("#{TAG_PREFIX}:*").inject([]) do |memo, entity_tag|
145
- tag = Flapjack::Data::Tag.find(entity_tag, :redis => @redis)
146
- memo << entity_tag.sub(/^#{TAG_PREFIX}:/, '') if tag.include?(@id.to_s)
147
- memo
148
- end
149
- entity_tags.merge(tag_data)
150
- end
151
-
152
147
  end
153
148
 
154
149
  end
@@ -5,6 +5,7 @@ require 'yajl/json_gem'
5
5
  require 'flapjack/patches'
6
6
 
7
7
  require 'flapjack/data/contact'
8
+ require 'flapjack/data/event'
8
9
  require 'flapjack/data/entity'
9
10
 
10
11
  # TODO might want to split the class methods out to a separate class, DAO pattern
@@ -153,6 +154,7 @@ module Flapjack
153
154
  end
154
155
 
155
156
  # change the end time of a scheduled maintenance (including when one is current)
157
+ # TODO allow to update summary as well
156
158
  def update_scheduled_maintenance(start_time, patches = {})
157
159
 
158
160
  # check if there is such a scheduled maintenance period
@@ -162,7 +164,7 @@ module Flapjack
162
164
 
163
165
  if patches[:end_time]
164
166
  end_time = patches[:end_time]
165
- raise ArgumentError unless end_time > start_time
167
+ raise ArgumentError, "end time must be after start time" unless end_time > start_time
166
168
  old_end_time = start_time + old_duration
167
169
  duration = end_time - start_time
168
170
  @redis.zadd("#{@key}:scheduled_maintenances", duration, start_time)
@@ -170,7 +172,6 @@ module Flapjack
170
172
 
171
173
  # scheduled maintenance periods have changed, revalidate
172
174
  update_current_scheduled_maintenance(:revalidate => true)
173
-
174
175
  end
175
176
 
176
177
  # delete a scheduled maintenance
@@ -404,17 +405,6 @@ module Flapjack
404
405
  }
405
406
  end
406
407
 
407
- # returns an array of pagerduty credentials. If more than one contact for this entity_check
408
- # has pagerduty credentials then there'll be one hash in the array for each set of
409
- # credentials.
410
- def pagerduty_credentials(options)
411
- self.contacts.inject([]) {|ret, contact|
412
- cred = contact.pagerduty_credentials
413
- ret << cred if cred
414
- ret
415
- }
416
- end
417
-
418
408
  # takes a check, looks up contacts that are interested in this check (or in the check's entity)
419
409
  # and returns an array of contact records
420
410
  def contacts
@@ -6,7 +6,7 @@ module Flapjack
6
6
  module Data
7
7
  class Event
8
8
 
9
- attr_accessor :previous_state
9
+ attr_accessor :previous_state, :previous_state_duration
10
10
 
11
11
  # Helper method for getting the next event.
12
12
  #
@@ -168,6 +168,7 @@ module Flapjack
168
168
  end
169
169
 
170
170
  event.previous_state = entity_check.state
171
+ event.previous_state_duration = Time.now.to_i - entity_check.last_change
171
172
  @logger.info("No previous state for event #{event.id}") if event.previous_state.nil?
172
173
 
173
174
  # If there is a state change, update record with: the time, the new state
@@ -6,6 +6,9 @@ module Flapjack
6
6
  module Filters
7
7
 
8
8
  # * If the service event’s state is ok and the previous state was ok, don’t alert
9
+ # * If the service event's state is ok and the previous notification was a recovery, don't alert
10
+ # * If the service event's state is ok and the previous state was not ok and for less than 30
11
+ # seconds, don't alert
9
12
  # * If the service event's state is ok and there is unscheduled downtime set, end the unscheduled
10
13
  # downtime
11
14
  class Ok
@@ -20,8 +23,23 @@ module Flapjack
20
23
  result = true
21
24
  end
22
25
 
23
- # end any unscheduled downtime
24
26
  entity_check = Flapjack::Data::EntityCheck.for_event_id(event.id, :redis => @persistence)
27
+
28
+ last_notification = entity_check.last_notification
29
+ @log.debug("Filter: Ok: last notification: #{last_notification.inspect}")
30
+ if last_notification[:type] == 'recovery'
31
+ @log.debug("Filter: Ok: last notification was a recovery, so blocking")
32
+ result = true
33
+ end
34
+
35
+ if event.previous_state != 'ok'
36
+ if event.previous_state_duration < 30
37
+ @log.debug("Filter: Ok: previous non ok state was for less than 30 seconds, so blocking")
38
+ result = true
39
+ end
40
+ end
41
+
42
+ # end any unscheduled downtime
25
43
  entity_check.end_unscheduled_maintenance
26
44
  end
27
45
 
@@ -48,7 +48,10 @@ module Flapjack
48
48
 
49
49
  def main
50
50
  logger.debug("pagerduty gateway - commencing main method")
51
- raise "Can't connect to the pagerduty API" unless test_pagerduty_connection
51
+ while not test_pagerduty_connection do
52
+ logger.error("Can't connect to the pagerduty API, retrying after 10 seconds")
53
+ EM::Synchrony.sleep(10)
54
+ end
52
55
 
53
56
  # TODO: only clear this if there isn't another pagerduty gateway instance running
54
57
  # or better, include an instance ID in the semaphore key name
@@ -160,16 +163,25 @@ module Flapjack
160
163
  @logger.debug "found unacknowledged failing checks as follows: " + unacknowledged_failing_checks.join(', ')
161
164
 
162
165
  unacknowledged_failing_checks.each do |entity_check|
163
- pagerduty_credentials = entity_check.pagerduty_credentials(:redis => @redis_timer)
166
+
167
+ # If more than one contact for this entity_check has pagerduty
168
+ # credentials then there'll be one hash in the array for each set of
169
+ # credentials.
170
+ ec_credentials = entity_check.contacts.inject([]) {|ret, contact|
171
+ cred = contact.pagerduty_credentials
172
+ ret << cred if cred
173
+ ret
174
+ }
175
+
164
176
  check = entity_check.check
165
177
 
166
- if pagerduty_credentials.empty?
178
+ if ec_credentials.empty?
167
179
  @logger.debug("No pagerduty credentials found for #{entity_check.entity_name}:#{check}, skipping")
168
180
  next
169
181
  end
170
182
 
171
183
  # FIXME: try each set of credentials until one works (may have stale contacts turning up)
172
- options = pagerduty_credentials.first.merge('check' => "#{entity_check.entity_name}:#{check}")
184
+ options = ec_credentials.first.merge('check' => "#{entity_check.entity_name}:#{check}")
173
185
 
174
186
  acknowledged = pagerduty_acknowledged?(options)
175
187
  if acknowledged.nil?
@@ -238,6 +238,12 @@ module Flapjack
238
238
  haml :contact
239
239
  end
240
240
 
241
+ protected
242
+
243
+ def render_haml(file, scope)
244
+ Haml::Engine.new(File.read(File.dirname(__FILE__) + '/web/views/' + file)).render(scope)
245
+ end
246
+
241
247
  private
242
248
 
243
249
  def get_entity_check(entity, check)
@@ -1,8 +1,7 @@
1
1
  !!! 5
2
- - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
- - nav = nav_haml.render
4
- - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
- - css = css_haml.render
2
+ - nav = render_haml('_nav.haml', self)
3
+ - css = render_haml('_css.haml', self)
4
+ - check_path_escaped = URI.escape(@entity, ' #&;/=?') << '/' << URI.escape(@check, ' #&;/=?')
6
5
  %html
7
6
  %head
8
7
  %title Flapjack - Check: #{@entity}:#{@check}
@@ -11,7 +10,7 @@
11
10
  %div#wrapper
12
11
  = nav
13
12
  %h1 #{@check} on #{@entity}
14
- %form{:action => "/acknowledgements/#{@entity}/#{@check}", :method => "post"}
13
+ %form{:action => "/acknowledgements/#{check_path_escaped}", :method => "post"}
15
14
  %h2
16
15
  State: #{@check_state ? @check_state.upcase : ''}
17
16
  - if (['warning', 'critical'].include?(@check_state) and !(@current_unscheduled_maintenance || @current_scheduled_maintenance))
@@ -26,7 +25,7 @@
26
25
  %input{:type => 'text', :name => 'duration', :value => ''}
27
26
  - if @current_unscheduled_maintenance
28
27
  %h3 (Acknowledged - #{@current_unscheduled_maintenance[:summary]})
29
- %form{:action => "/end_unscheduled_maintenance/#{@entity}/#{@check}", :method => "post"}
28
+ %form{:action => "/end_unscheduled_maintenance/#{check_path_escaped}", :method => "post"}
30
29
  %p
31
30
  %input{:type => 'submit', :value => 'End Unscheduled Maintenance (Unacknowledge)', :class => 'button'}
32
31
  - if @current_scheduled_maintenance
@@ -93,12 +92,12 @@
93
92
  %td
94
93
  - if end_time > Time.now.to_i
95
94
  - if start_time > Time.now.to_i
96
- %form{ :action => "/scheduled_maintenances/#{@entity}/#{@check}", :method => "post"}
95
+ %form{ :action => "/scheduled_maintenances/#{check_path_escaped}", :method => "post"}
97
96
  %input{:type => 'hidden', :name => '_method', :value => 'delete'}
98
97
  %input{:type => 'hidden', :name => 'start_time', :value => start_time}
99
98
  %input{:type => 'submit', :value => 'Delete', :class => 'button'}
100
99
  - else
101
- %form{ :action => "/scheduled_maintenances/#{@entity}/#{@check}", :method => "post"}
100
+ %form{ :action => "/scheduled_maintenances/#{check_path_escaped}", :method => "post"}
102
101
  %input{:type => 'hidden', :name => '_method', :value => 'patch'}
103
102
  %input{:type => 'hidden', :name => 'start_time', :value => start_time}
104
103
  %input{:type => 'hidden', :name => 'end_time', :value => 'now'}
@@ -106,7 +105,7 @@
106
105
  - else
107
106
  %p No maintenance is scheduled
108
107
  %h4 Add Scheduled Maintenance
109
- %form{:action => "/scheduled_maintenances/#{@entity}/#{@check}", :method => "post"}
108
+ %form{:action => "/scheduled_maintenances/#{check_path_escaped}", :method => "post"}
110
109
  %fieldset
111
110
  %table
112
111
  %tr
@@ -1,8 +1,6 @@
1
1
  !!! 5
2
- - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
- - nav = nav_haml.render
4
- - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
- - css = css_haml.render
2
+ - nav = render_haml('_nav.haml', self)
3
+ - css = render_haml('_css.haml', self)
6
4
  %html
7
5
  %head
8
6
  %title Flapjack - Contact: #{@contact.name}
@@ -39,7 +37,7 @@
39
37
  %p= entity.name
40
38
  %td
41
39
  - checks.each do |check|
42
- - link = "/check?entity=#{entity.name}&check=#{check}"
40
+ - link = "/check?entity=#{CGI.escape(entity.name)}&check=#{CGI.escape(check)}"
43
41
  %p
44
42
  %a(title='check status' href=link) #{check}
45
43
  - else
@@ -1,8 +1,6 @@
1
1
  !!! 5
2
- - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
- - nav = nav_haml.render
4
- - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
- - css = css_haml.render
2
+ - nav = render_haml('_nav.haml', self)
3
+ - css = render_haml('_css.haml', self)
6
4
  %html
7
5
  %head
8
6
  %title Flapjack - Contacts
@@ -1,8 +1,6 @@
1
1
  !!! 5
2
- - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
- - nav = nav_haml.render
4
- - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
- - css = css_haml.render
2
+ - nav = render_haml('_nav.haml', self)
3
+ - css = render_haml('_css.haml', self)
6
4
  %html
7
5
  %head
8
6
  %title Flapjack
@@ -36,7 +34,7 @@
36
34
  %tr
37
35
  %td= entity
38
36
  %td
39
- - link = "/check?entity=" + entity + "&check=" + check
37
+ - link = "/check?entity=" + CGI.escape(entity) + "&check=" + CGI.escape(check)
40
38
  %a(title='check detail' href=link) #{check}
41
39
  %td{:class => status}
42
40
  = status.upcase
@@ -49,7 +47,3 @@
49
47
  - else
50
48
  - last_notified = 'never'
51
49
  %td= last_notified
52
-
53
- -#%ul
54
- -#- @keys.each do |key|
55
- -#%li= key
@@ -1,8 +1,6 @@
1
1
  !!! 5
2
- - nav_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_nav.haml'))
3
- - nav = nav_haml.render
4
- - css_haml = Haml::Engine.new(File.read(File.dirname(__FILE__) + '/_css.haml'))
5
- - css = css_haml.render
2
+ - nav = render_haml('_nav.haml', self)
3
+ - css = render_haml('_css.haml', self)
6
4
  %html
7
5
  %head
8
6
  %title Flapjack - Self Stats
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  module Flapjack
4
- VERSION = "0.6.51"
4
+ VERSION = "0.6.52"
5
5
  end
@@ -1,15 +1,25 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  require 'flapjack/data/contact'
4
+ require 'flapjack/data/entity_check'
4
5
 
5
6
  describe Flapjack::Data::Contact, :redis => true do
6
7
 
7
- def add_contacts
8
+ before(:each) do
8
9
  Flapjack::Data::Contact.add({'id' => '362',
9
10
  'first_name' => 'John',
10
11
  'last_name' => 'Johnson',
11
- 'email' => 'johnj@example.com' },
12
+ 'email' => 'johnj@example.com',
13
+ 'media' => {
14
+ 'pagerduty' => {
15
+ 'service_key' => '123456789012345678901234',
16
+ 'subdomain' => 'flpjck',
17
+ 'username' => 'flapjack',
18
+ 'password' => 'very_secure'
19
+ }
20
+ }},
12
21
  :redis => @redis)
22
+
13
23
  Flapjack::Data::Contact.add({'id' => '363',
14
24
  'first_name' => 'Jane',
15
25
  'last_name' => 'Janeley',
@@ -18,8 +28,6 @@ describe Flapjack::Data::Contact, :redis => true do
18
28
  end
19
29
 
20
30
  it "returns a list of all contacts" do
21
- add_contacts
22
-
23
31
  contacts = Flapjack::Data::Contact.all(:redis => @redis)
24
32
  contacts.should_not be_nil
25
33
  contacts.should be_an(Array)
@@ -29,18 +37,12 @@ describe Flapjack::Data::Contact, :redis => true do
29
37
  end
30
38
 
31
39
  it "finds a contact by id" do
32
- add_contacts
33
-
34
40
  contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
35
41
  contact.should_not be_nil
36
42
  contact.name.should == "John Johnson"
37
43
  end
38
44
 
39
- it "finds all contacts for a check on an entity"
40
-
41
45
  it "deletes all contacts" do
42
- add_contacts
43
-
44
46
  Flapjack::Data::Contact.delete_all(:redis => @redis)
45
47
  contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
46
48
  contact.should be_nil
@@ -48,8 +50,44 @@ describe Flapjack::Data::Contact, :redis => true do
48
50
  contact.should be_nil
49
51
  end
50
52
 
51
- it "returns pagerduty credentials for a contact"
53
+ it "returns a list of entities and their checks for a contact" do
54
+ entity_name = 'abc-123'
55
+
56
+ Flapjack::Data::Entity.add({'id' => '5000',
57
+ 'name' => entity_name,
58
+ 'contacts' => ['362']},
59
+ :redis => @redis)
60
+
61
+ ec = Flapjack::Data::EntityCheck.for_entity_name(entity_name, 'PING', :redis => @redis)
62
+ t = Time.now.to_i
63
+ ec.update_state('ok', :timestamp => t, :summary => 'a')
52
64
 
53
- it "returns a list of entities for a contact"
65
+ contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
66
+ eandcs = contact.entities_and_checks
67
+ eandcs.should_not be_nil
68
+ eandcs.should be_an(Array)
69
+ eandcs.should have(1).entity_and_checks
70
+
71
+ eandc = eandcs.first
72
+ eandc.should be_a(Hash)
73
+
74
+ entity = eandc[:entity]
75
+ entity.name.should == entity_name
76
+ checks = eandc[:checks]
77
+ checks.should be_a(Set)
78
+ checks.should have(1).check
79
+ checks.should include('PING')
80
+ end
81
+
82
+ it "returns pagerduty credentials for a contact" do
83
+ contact = Flapjack::Data::Contact.find_by_id('362', :redis => @redis)
84
+ credentials = contact.pagerduty_credentials
85
+ credentials.should_not be_nil
86
+ credentials.should be_a(Hash)
87
+ credentials.should == {'service_key' => '123456789012345678901234',
88
+ 'subdomain' => 'flpjck',
89
+ 'username' => 'flapjack',
90
+ 'password' => 'very_secure'}
91
+ end
54
92
 
55
93
  end
@@ -13,8 +13,15 @@ describe Flapjack::Data::EntityCheck, :redis => true do
13
13
  let(:half_an_hour) { 30 * 60 }
14
14
 
15
15
  before(:each) do
16
+ Flapjack::Data::Contact.add({'id' => '362',
17
+ 'first_name' => 'John',
18
+ 'last_name' => 'Johnson',
19
+ 'email' => 'johnj@example.com' },
20
+ :redis => @redis)
21
+
16
22
  Flapjack::Data::Entity.add({'id' => '5000',
17
- 'name' => name},
23
+ 'name' => name,
24
+ 'contacts' => ['362']},
18
25
  :redis => @redis)
19
26
  end
20
27
 
@@ -106,21 +113,21 @@ describe Flapjack::Data::EntityCheck, :redis => true do
106
113
  timestamp.should_not be_nil
107
114
  timestamp.should == t.to_s
108
115
 
109
- umps = @redis.zrange("#{name}:#{check}:unscheduled_maintenances", 0, -1, :with_scores => true)
116
+ umps = ec.maintenances(nil, nil, :scheduled => false)
110
117
  umps.should_not be_nil
111
118
  umps.should be_an(Array)
112
119
  umps.should have(1).unscheduled_maintenance_period
113
- umps[0].should be_an(Array)
120
+ umps[0].should be_a(Hash)
114
121
 
115
- start_time = umps[0][0]
122
+ start_time = umps[0][:start_time]
116
123
  start_time.should_not be_nil
117
- start_time.should be_a(String)
118
- start_time.should == t.to_s
124
+ start_time.should be_an(Integer)
125
+ start_time.should == t
119
126
 
120
- score = umps[0][1]
121
- score.should_not be_nil
122
- score.should be_a(Float)
123
- score.should == half_an_hour
127
+ duration = umps[0][:duration]
128
+ duration.should_not be_nil
129
+ duration.should be_a(Float)
130
+ duration.should == half_an_hour
124
131
 
125
132
  summary = @redis.get("#{name}:#{check}:#{t}:unscheduled_maintenance:summary")
126
133
  summary.should_not be_nil
@@ -133,32 +140,167 @@ describe Flapjack::Data::EntityCheck, :redis => true do
133
140
  ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
134
141
  :duration => half_an_hour, :summary => "30 minutes")
135
142
 
136
- smps = @redis.zrange("#{name}:#{check}:scheduled_maintenances", 0, -1, :with_scores => true)
143
+ smps = ec.maintenances(nil, nil, :scheduled => true)
144
+ smps.should_not be_nil
145
+ smps.should be_an(Array)
146
+ smps.should have(1).scheduled_maintenance_period
147
+ smps[0].should be_a(Hash)
148
+
149
+ start_time = smps[0][:start_time]
150
+ start_time.should_not be_nil
151
+ start_time.should be_an(Integer)
152
+ start_time.should == (t + (60 * 60))
153
+
154
+ duration = smps[0][:duration]
155
+ duration.should_not be_nil
156
+ duration.should be_a(Float)
157
+ duration.should == half_an_hour
158
+ end
159
+
160
+ # TODO this should probably enforce that it starts in the future
161
+ it "creates a scheduled maintenance period covering the current time" do
162
+ t = Time.now.to_i
163
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
164
+ ec.create_scheduled_maintenance(:start_time => t - (60 * 60),
165
+ :duration => 2 * (60 * 60), :summary => "2 hours")
166
+
167
+ smps = ec.maintenances(nil, nil, :scheduled => true)
168
+ smps.should_not be_nil
169
+ smps.should be_an(Array)
170
+ smps.should have(1).scheduled_maintenance_period
171
+ smps[0].should be_a(Hash)
172
+
173
+ start_time = smps[0][:start_time]
174
+ start_time.should_not be_nil
175
+ start_time.should be_an(Integer)
176
+ start_time.should == (t - (60 * 60))
177
+
178
+ duration = smps[0][:duration]
179
+ duration.should_not be_nil
180
+ duration.should be_a(Float)
181
+ duration.should == 2 * (60 * 60)
182
+ end
183
+
184
+ it "updates a scheduled maintenance period for a future time" do
185
+ t = Time.now.to_i
186
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
187
+ ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
188
+ :duration => 2 * (60 * 60), :summary => "2 hours")
189
+
190
+ ec.update_scheduled_maintenance(t + (60 * 60), :end_time => t + (4 * (60 * 60)))
191
+ smps = ec.maintenances(nil, nil, :scheduled => true)
192
+ smps.should_not be_nil
193
+ smps.should be_an(Array)
194
+ smps.should have(1).scheduled_maintenance_period
195
+ smps[0].should be_a(Hash)
196
+
197
+ start_time = smps[0][:start_time]
198
+ start_time.should_not be_nil
199
+ start_time.should be_an(Integer)
200
+ start_time.should == (t + (60 * 60))
201
+
202
+ duration = smps[0][:duration]
203
+ duration.should_not be_nil
204
+ duration.should be_a(Float)
205
+ duration.should == 3 * (60 * 60)
206
+ end
207
+
208
+ # TODO this should probably enforce that it starts in the future
209
+ it "updates a scheduled maintenance period covering the current time", :time => true do
210
+ t = Time.now.to_i
211
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
212
+ ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
213
+ :duration => 2 * (60 * 60), :summary => "2 hours")
214
+
215
+ Delorean.time_travel_to( Time.at(t + (90 * 60)) )
216
+
217
+ ec.update_scheduled_maintenance(t + (60 * 60), :end_time => t + (4 * (60 * 60)))
218
+ smps = ec.maintenances(nil, nil, :scheduled => true)
219
+ smps.should_not be_nil
220
+ smps.should be_an(Array)
221
+ smps.should have(1).scheduled_maintenance_period
222
+ smps[0].should be_a(Hash)
223
+
224
+ start_time = smps[0][:start_time]
225
+ start_time.should_not be_nil
226
+ start_time.should be_an(Integer)
227
+ start_time.should == (t + (60 * 60))
228
+
229
+ duration = smps[0][:duration]
230
+ duration.should_not be_nil
231
+ duration.should be_a(Float)
232
+ duration.should == 3 * (60 * 60)
233
+ end
234
+
235
+ it "fails to update a scheduled maintenance period when not found" do
236
+ t = Time.now.to_i
237
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
238
+ lambda {
239
+ ec.update_scheduled_maintenance(t + (60 * 60), :end_time => t + (2 * (60 * 60)))
240
+ }.should raise_error(ArgumentError)
241
+
242
+ smps = ec.maintenances(nil, nil, :scheduled => true)
243
+ smps.should_not be_nil
244
+ smps.should be_an(Array)
245
+ smps.should be_empty
246
+ end
247
+
248
+ it "fails to update a scheduled maintenance period with invalid end time" do
249
+ t = Time.now.to_i
250
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
251
+ ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
252
+ :duration => 2 * (60 * 60), :summary => "2 hours")
253
+
254
+ lambda {
255
+ ec.update_scheduled_maintenance(t + (60 * 60), :end_time => t - (4 * (60 * 60)))
256
+ }.should raise_error(ArgumentError)
257
+ smps = ec.maintenances(nil, nil, :scheduled => true)
137
258
  smps.should_not be_nil
138
259
  smps.should be_an(Array)
139
260
  smps.should have(1).scheduled_maintenance_period
140
- smps[0].should be_an(Array)
261
+ smps[0].should be_a(Hash)
141
262
 
142
- start_time = smps[0][0]
263
+ start_time = smps[0][:start_time]
143
264
  start_time.should_not be_nil
144
- start_time.should be_a(String)
145
- start_time.should == (t + (60 * 60)).to_s
265
+ start_time.should be_an(Integer)
266
+ start_time.should == (t + (60 * 60))
146
267
 
147
- score = smps[0][1]
148
- score.should_not be_nil
149
- score.should be_a(Float)
150
- score.should == half_an_hour
268
+ duration = smps[0][:duration]
269
+ duration.should_not be_nil
270
+ duration.should be_a(Float)
271
+ duration.should == 2 * (60 * 60)
151
272
  end
152
273
 
153
- it "creates a scheduled maintenance period covering the current time"
274
+ it "removes a scheduled maintenance period for a future time" do
275
+ t = Time.now.to_i
276
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
277
+ ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
278
+ :duration => 2 * (60 * 60), :summary => "2 hours")
279
+
280
+ ec.delete_scheduled_maintenance(:start_time => t + (60 * 60))
154
281
 
155
- it "updates a scheduled maintenance period for a future time"
282
+ smps = ec.maintenances(nil, nil, :scheduled => true)
283
+ smps.should_not be_nil
284
+ smps.should be_an(Array)
285
+ smps.should be_empty
286
+ end
287
+
288
+ # TODO this should probably enforce that it starts in the future
289
+ it "removes a scheduled maintenance period covering a current time", :time => true do
290
+ t = Time.now.to_i
291
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
292
+ ec.create_scheduled_maintenance(:start_time => t + (60 * 60),
293
+ :duration => 2 * (60 * 60), :summary => "2 hours")
156
294
 
157
- it "updates a scheduled maintenance period covering the current time"
295
+ Delorean.time_travel_to( Time.at(t + (90 * 60)) )
158
296
 
159
- it "removes a scheduled maintenance period for a future time"
297
+ ec.delete_scheduled_maintenance(:start_time => t + (60 * 60))
160
298
 
161
- it "removes a scheduled maintenance period covering a current time"
299
+ smps = ec.maintenances(nil, nil, :scheduled => true)
300
+ smps.should_not be_nil
301
+ smps.should be_an(Array)
302
+ smps.should be_empty
303
+ end
162
304
 
163
305
  it "returns a list of scheduled maintenance periods" do
164
306
  t = Time.now.to_i
@@ -210,8 +352,6 @@ describe Flapjack::Data::EntityCheck, :redis => true do
210
352
  :summary => "second"}
211
353
  end
212
354
 
213
- it "updates scheduled maintenance periods"
214
-
215
355
  end
216
356
 
217
357
  it "creates an acknowledgement" do
@@ -300,20 +440,20 @@ describe Flapjack::Data::EntityCheck, :redis => true do
300
440
  ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
301
441
 
302
442
  t = Time.now.to_i
303
- # ec.update_state('ok', :timestamp => time_before(t, 5), :summary => 'a')
304
- # ec.update_state('critical', :timestamp => time_before(t, 4), :summary => 'b')
305
- # ec.update_state('ok', :timestamp => time_before(t, 3), :summary => 'c')
306
- # ec.update_state('critical', :timestamp => time_before(t, 2), :summary => 'd')
307
- # ec.update_state('ok', :timestamp => time_before(t, 1), :summary => 'e')
308
-
309
- # states = ec.historical_states(time_before(t, 4), t)
310
- # states.should_not be_nil
311
- # states.should be_an(Array)
312
- # states.should have(4).data_hashes
313
- # states[0][:summary].should == 'b'
314
- # states[1][:summary].should == 'c'
315
- # states[2][:summary].should == 'd'
316
- # states[3][:summary].should == 'e'
443
+ ec.update_state('ok', :timestamp => time_before(t, 5), :summary => 'a')
444
+ ec.update_state('critical', :timestamp => time_before(t, 4), :summary => 'b')
445
+ ec.update_state('ok', :timestamp => time_before(t, 3), :summary => 'c')
446
+ ec.update_state('critical', :timestamp => time_before(t, 2), :summary => 'd')
447
+ ec.update_state('ok', :timestamp => time_before(t, 1), :summary => 'e')
448
+
449
+ states = ec.historical_states(time_before(t, 4), t)
450
+ states.should_not be_nil
451
+ states.should be_an(Array)
452
+ states.should have(4).data_hashes
453
+ states[0][:summary].should == 'b'
454
+ states[1][:summary].should == 'c'
455
+ states[2][:summary].should == 'd'
456
+ states[3][:summary].should == 'e'
317
457
  end
318
458
 
319
459
  it "returns a list of historical scheduled maintenances for a time range" do
@@ -360,7 +500,18 @@ describe Flapjack::Data::EntityCheck, :redis => true do
360
500
  ec.should_not be_failed
361
501
  end
362
502
 
363
- it "returns a status summary"
503
+ it "returns a status summary" do
504
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
505
+
506
+ t = Time.now.to_i
507
+ ec.update_state('ok', :timestamp => time_before(t, 5), :summary => 'a')
508
+ ec.update_state('critical', :timestamp => time_before(t, 4), :summary => 'b')
509
+ ec.update_state('ok', :timestamp => time_before(t, 3), :summary => 'c')
510
+ ec.update_state('critical', :timestamp => time_before(t, 2), :summary => 'd')
511
+
512
+ summary = ec.summary
513
+ summary.should == 'd'
514
+ end
364
515
 
365
516
  it "returns timestamps for its last notifications" do
366
517
  t = Time.now.to_i
@@ -374,4 +525,12 @@ describe Flapjack::Data::EntityCheck, :redis => true do
374
525
  ec.last_recovery_notification.should == t
375
526
  end
376
527
 
528
+ it "finds all related contacts" do
529
+ ec = Flapjack::Data::EntityCheck.for_entity_name(name, check, :redis => @redis)
530
+ contacts = ec.contacts
531
+ contacts.should_not be_nil
532
+ contacts.should be_an(Array)
533
+ contacts.should have(1).contact
534
+ contacts.first.name.should == 'John Johnson'
535
+ end
377
536
  end
@@ -3,10 +3,34 @@ require 'flapjack/data/entity'
3
3
 
4
4
  describe Flapjack::Data::Tag, :redis => true do
5
5
 
6
- it "adds references to tags"
6
+ it "adds references to tags" do
7
+ tags = Flapjack::Data::Tag.create('special', ['apple', 'button', 'carbon'], :redis => @redis)
7
8
 
8
- it "deletes references from tags"
9
+ tags.should include('carbon')
10
+ tags.should_not include('chocolate')
9
11
 
10
- it "lists references contained in a tag"
12
+ tags.add('chocolate')
13
+ tags.should include('chocolate')
14
+ end
15
+
16
+ it "deletes references from tags" do
17
+ tags = Flapjack::Data::Tag.create('special', ['apple', 'button', 'carbon'], :redis => @redis)
18
+
19
+ tags.should include('apple')
20
+ tags.should include('button')
21
+
22
+ tags.delete('button')
23
+ tags.should include('apple')
24
+ tags.should_not include('button')
25
+ end
26
+
27
+ it "lists references contained in a tag" do
28
+ t1 = Flapjack::Data::Tag.create('special', ['apple', 'button', 'carbon'], :redis => @redis)
29
+
30
+ t2 = Flapjack::Data::Tag.find('special', :redis => @redis)
31
+ t2.should include('apple')
32
+ t2.should include('carbon')
33
+ t2.should_not include('chocolate')
34
+ end
11
35
 
12
36
  end
@@ -61,7 +61,24 @@ describe 'Flapjack::Gateways::API', :sinatra => true do
61
61
  last_response.status.should == 204
62
62
  end
63
63
 
64
- it "returns a list of scheduled maintenance periods within a time window for an entity"
64
+ it "returns a list of scheduled maintenance periods within a time window for an entity" do
65
+ start = Time.parse('1 Jan 2012')
66
+ finish = Time.parse('6 Jan 2012')
67
+
68
+ result = mock('result')
69
+ result_json = %q{"result"}
70
+ result.should_receive(:to_json).and_return(result_json)
71
+ entity_presenter.should_receive(:scheduled_maintenance).with(start.to_i, finish.to_i).and_return(result)
72
+ Flapjack::Gateways::API::EntityPresenter.should_receive(:new).
73
+ with(entity, :redis => redis).and_return(entity_presenter)
74
+ Flapjack::Data::Entity.should_receive(:find_by_name).
75
+ with(entity_name, :redis => redis).and_return(entity)
76
+
77
+ get "/scheduled_maintenances/#{entity_name_esc}?" +
78
+ "start_time=#{CGI.escape(start.iso8601)}&end_time=#{CGI.escape(finish.iso8601)}"
79
+ last_response.should be_ok
80
+ last_response.body.should == result_json
81
+ end
65
82
 
66
83
  it "returns a list of scheduled maintenance periods for a check on an entity" do
67
84
  result = mock('result')
@@ -112,8 +129,26 @@ describe 'Flapjack::Gateways::API', :sinatra => true do
112
129
  last_response.body.should == result_json
113
130
  end
114
131
 
132
+ it "returns a list of unscheduled maintenance periods within a time window for a check an entity" do
133
+ start = Time.parse('1 Jan 2012')
134
+ finish = Time.parse('6 Jan 2012')
115
135
 
116
- it "returns a list of unscheduled maintenance periods within a time window for a check an entity"
136
+ result = mock('result')
137
+ result_json = %q{"result"}
138
+ result.should_receive(:to_json).and_return(result_json)
139
+ entity_check_presenter.should_receive(:unscheduled_maintenance).with(start.to_i, finish.to_i).and_return(result)
140
+ Flapjack::Gateways::API::EntityCheckPresenter.should_receive(:new).
141
+ with(entity_check).and_return(entity_check_presenter)
142
+ Flapjack::Data::Entity.should_receive(:find_by_name).
143
+ with(entity_name, :redis => redis).and_return(entity)
144
+ Flapjack::Data::EntityCheck.should_receive(:for_entity).
145
+ with(entity, check, :redis => redis).and_return(entity_check)
146
+
147
+ get "/unscheduled_maintenances/#{entity_name_esc}/#{check}" +
148
+ "?start_time=#{CGI.escape(start.iso8601)}&end_time=#{CGI.escape(finish.iso8601)}"
149
+ last_response.should be_ok
150
+ last_response.body.should == result_json
151
+ end
117
152
 
118
153
  it "returns a list of outages for an entity" do
119
154
  result = mock('result')
@@ -93,14 +93,17 @@ describe Flapjack::Gateways::Pagerduty, :redis => true do
93
93
  Flapjack::RedisPool.should_receive(:new)
94
94
  fp.bootstrap(:config => config)
95
95
 
96
- entity_check = mock('entity_check')
97
- entity_check.should_receive(:check).and_return('PING')
98
- entity_check.should_receive(:pagerduty_credentials).and_return([{
96
+ contact = mock('contact')
97
+ contact.should_receive(:pagerduty_credentials).and_return({
99
98
  'service_key' => '12345678',
100
99
  'subdomain"' => 'flpjck',
101
100
  'username' => 'flapjack',
102
101
  'password' => 'password123'
103
- }])
102
+ })
103
+
104
+ entity_check = mock('entity_check')
105
+ entity_check.should_receive(:check).and_return('PING')
106
+ entity_check.should_receive(:contacts).and_return([contact])
104
107
  entity_check.should_receive(:entity_name).exactly(2).times.and_return('foo-app-01.bar.net:PING')
105
108
  entity_check.should_receive(:create_acknowledgement).with('summary' => 'Acknowledged on PagerDuty')
106
109
 
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'web/views/check.haml', :haml_view => true do
4
+
5
+ it "should escape unsafe check characters in URIs" do
6
+ @entity = 'abc-xyz-01'
7
+ @check = 'Disk / Utilisation'
8
+ @last_notifications = {}
9
+
10
+ page = render_haml('check.haml', self)
11
+ page.should match(%r{/abc-xyz-01/Disk%20%2F%20Utilisation})
12
+ end
13
+
14
+ end
@@ -0,0 +1,21 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'web/views/contact.haml', :haml_view => true do
4
+
5
+ it "should escape unsafe check characters in URI parameters" do
6
+ @contact = mock('contact')
7
+ @contact.should_receive(:media)
8
+ @contact.should_receive(:name).twice.and_return('Aeschylus')
9
+
10
+ entity = mock('entity')
11
+ entity.should_receive(:name).twice.and_return('abc-xyz-01')
12
+
13
+ checks = ['Disk / Utilisation']
14
+
15
+ @entities_and_checks = [{:entity => entity, :checks => checks}]
16
+
17
+ page = render_haml('contact.haml', self)
18
+ page.should match(%r{\?entity=abc-xyz-01&amp;check=Disk\+%2F\+Utilisation})
19
+ end
20
+
21
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'web/views/index.haml', :haml_view => true do
4
+
5
+ it "should escape unsafe check characters in URI parameters" do
6
+ @states = [['abc-xyz-01', 'Disk / Utilisation', '-', '-', '-', false, false, nil, nil]]
7
+
8
+ page = render_haml('index.haml', self)
9
+ page.should match(%r{\?entity=abc-xyz-01&amp;check=Disk\+%2F\+Utilisation})
10
+ end
11
+
12
+ end
@@ -216,8 +216,17 @@ describe Flapjack::Gateways::Web, :sinatra => true, :redis => true do
216
216
  last_response.should be_ok
217
217
  end
218
218
 
219
- it "shows details of an individual contact found by email"
219
+ it "shows details of an individual contact found by id" do
220
+ contact = mock('contact')
221
+ contact.should_receive(:name).twice.and_return("Smithson Smith")
222
+ contact.should_receive(:media).exactly(3).times.and_return({})
223
+ contact.should_receive(:entities_and_checks).and_return([])
220
224
 
221
- it "shows details of an individual contact found by id"
225
+ Flapjack::Data::Contact.should_receive(:find_by_id).
226
+ with('0362', :redis => @redis).and_return(contact)
227
+
228
+ get "/contacts/0362"
229
+ last_response.should be_ok
230
+ end
222
231
 
223
232
  end
data/spec/spec_helper.rb CHANGED
@@ -45,5 +45,11 @@ RSpec.configure do |config|
45
45
  @redis.quit
46
46
  end
47
47
 
48
+ config.after(:each, :time => true) do
49
+ Delorean.back_to_the_present
50
+ end
51
+
52
+ config.include HamlViewHelper, :haml_view => true
53
+
48
54
  config.include Rack::Test::Methods, :sinatra => true
49
55
  end
@@ -0,0 +1,15 @@
1
+ require 'haml'
2
+ require 'flapjack/utility'
3
+
4
+ module HamlViewHelper
5
+
6
+ TEMPLATE_PATH = File.dirname(__FILE__) +
7
+ '/../../lib/flapjack/gateways/web/views/'
8
+
9
+ include Flapjack::Utility
10
+
11
+ def render_haml(file, scope)
12
+ Haml::Engine.new(File.read(TEMPLATE_PATH + file)).render(scope)
13
+ end
14
+
15
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flapjack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.51
4
+ version: 0.6.52
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2012-11-29 00:00:00.000000000 Z
14
+ date: 2012-12-07 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: daemons
@@ -432,11 +432,15 @@ files:
432
432
  - spec/lib/flapjack/gateways/oobetet_spec.rb
433
433
  - spec/lib/flapjack/gateways/pagerduty_spec.rb
434
434
  - spec/lib/flapjack/gateways/sms_messagenet.spec.rb
435
+ - spec/lib/flapjack/gateways/web/views/check.haml_spec.rb
436
+ - spec/lib/flapjack/gateways/web/views/contact.haml_spec.rb
437
+ - spec/lib/flapjack/gateways/web/views/index.haml_spec.rb
435
438
  - spec/lib/flapjack/gateways/web_spec.rb
436
439
  - spec/lib/flapjack/pikelet_spec.rb
437
440
  - spec/lib/flapjack/redis_pool_spec.rb
438
441
  - spec/lib/flapjack/utility_spec.rb
439
442
  - spec/spec_helper.rb
443
+ - spec/support/haml_view_helper.rb
440
444
  - spec/support/profile_all_formatter.rb
441
445
  - spec/support/uncolored_doc_formatter.rb
442
446
  - tasks/events.rake
@@ -512,10 +516,14 @@ test_files:
512
516
  - spec/lib/flapjack/gateways/oobetet_spec.rb
513
517
  - spec/lib/flapjack/gateways/pagerduty_spec.rb
514
518
  - spec/lib/flapjack/gateways/sms_messagenet.spec.rb
519
+ - spec/lib/flapjack/gateways/web/views/check.haml_spec.rb
520
+ - spec/lib/flapjack/gateways/web/views/contact.haml_spec.rb
521
+ - spec/lib/flapjack/gateways/web/views/index.haml_spec.rb
515
522
  - spec/lib/flapjack/gateways/web_spec.rb
516
523
  - spec/lib/flapjack/pikelet_spec.rb
517
524
  - spec/lib/flapjack/redis_pool_spec.rb
518
525
  - spec/lib/flapjack/utility_spec.rb
519
526
  - spec/spec_helper.rb
527
+ - spec/support/haml_view_helper.rb
520
528
  - spec/support/profile_all_formatter.rb
521
529
  - spec/support/uncolored_doc_formatter.rb