flapjack 0.6.61 → 0.7.0
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/Gemfile +2 -1
- data/README.md +8 -4
- data/features/events.feature +269 -146
- data/features/notification_rules.feature +93 -0
- data/features/steps/events_steps.rb +162 -21
- data/features/steps/notifications_steps.rb +1 -1
- data/features/steps/time_travel_steps.rb +30 -19
- data/features/support/env.rb +71 -1
- data/flapjack.gemspec +3 -0
- data/lib/flapjack/data/contact.rb +256 -57
- data/lib/flapjack/data/entity.rb +2 -1
- data/lib/flapjack/data/entity_check.rb +22 -7
- data/lib/flapjack/data/global.rb +1 -0
- data/lib/flapjack/data/message.rb +2 -0
- data/lib/flapjack/data/notification_rule.rb +172 -0
- data/lib/flapjack/data/tag.rb +7 -2
- data/lib/flapjack/data/tag_set.rb +16 -0
- data/lib/flapjack/executive.rb +147 -13
- data/lib/flapjack/filters/delays.rb +21 -9
- data/lib/flapjack/gateways/api.rb +407 -27
- data/lib/flapjack/gateways/pagerduty.rb +1 -1
- data/lib/flapjack/gateways/web.rb +50 -22
- data/lib/flapjack/gateways/web/views/self_stats.haml +2 -0
- data/lib/flapjack/utility.rb +10 -0
- data/lib/flapjack/version.rb +1 -1
- data/spec/lib/flapjack/data/contact_spec.rb +103 -6
- data/spec/lib/flapjack/data/global_spec.rb +2 -0
- data/spec/lib/flapjack/data/message_spec.rb +6 -0
- data/spec/lib/flapjack/data/notification_rule_spec.rb +22 -0
- data/spec/lib/flapjack/data/notification_spec.rb +6 -0
- data/spec/lib/flapjack/gateways/api_spec.rb +727 -4
- data/spec/lib/flapjack/gateways/jabber_spec.rb +1 -0
- data/spec/lib/flapjack/gateways/web_spec.rb +11 -1
- data/spec/spec_helper.rb +10 -0
- data/tmp/notification_rules.rb +73 -0
- data/tmp/test_json_post.rb +16 -0
- data/tmp/test_notification_rules_api.rb +170 -0
- metadata +59 -2
@@ -0,0 +1,93 @@
|
|
1
|
+
@notification_rules @resque
|
2
|
+
Feature: Notification rules on a per contact basis
|
3
|
+
|
4
|
+
Background:
|
5
|
+
Given the following users exist:
|
6
|
+
| id | first_name | last_name | email | sms |
|
7
|
+
| 1 | Malak | Al-Musawi | malak@example.com | +61400000001 |
|
8
|
+
| 2 | Imani | Farooq | imani@example.com | +61400000002 |
|
9
|
+
|
10
|
+
And the following entities exist:
|
11
|
+
| id | name | contacts |
|
12
|
+
| 1 | foo | 1 |
|
13
|
+
| 2 | bar | 1,2 |
|
14
|
+
|
15
|
+
And user 1 has the following notification intervals:
|
16
|
+
| email | sms |
|
17
|
+
| 15 | 60 |
|
18
|
+
|
19
|
+
And user 1 has the following notification rules:
|
20
|
+
| id | entities | entity_tags | warning_media | critical_media | warning_blackhole | critical_blackhole | time_restrictions |
|
21
|
+
| 1 | foo | | email | sms,email | | | 8-18 weekdays |
|
22
|
+
| 2 | bar | | | sms,email | true | | |
|
23
|
+
|
24
|
+
@time_restrictions @time
|
25
|
+
Scenario: Alerts only during specified time restrictions
|
26
|
+
Given the timezone is America/New_York
|
27
|
+
And the time is February 1 2013 6:59
|
28
|
+
And the check is check 'ping' on entity 'foo'
|
29
|
+
And the check is in an ok state
|
30
|
+
And a critical event is received
|
31
|
+
Then no email alerts should be queued for malak@example.com
|
32
|
+
And the time is February 1 2013 7:01
|
33
|
+
And a critical event is received
|
34
|
+
Then no email alerts should be queued for malak@example.com
|
35
|
+
And the time is February 1 2013 8:01
|
36
|
+
And a critical event is received
|
37
|
+
Then 1 email alert should be queued for malak@example.com
|
38
|
+
When the time is February 1 2013 12:00
|
39
|
+
Then all alert dropping keys for user 1 should have expired
|
40
|
+
When a critical event is received
|
41
|
+
Then 2 email alerts should be queued for malak@example.com
|
42
|
+
When the time is February 1 2013 17:59
|
43
|
+
Then all alert dropping keys for user 1 should have expired
|
44
|
+
When a critical event is received
|
45
|
+
Then 3 email alerts should be queued for malak@example.com
|
46
|
+
When the time is February 1 2013 18:01
|
47
|
+
Then all alert dropping keys for user 1 should have expired
|
48
|
+
When a critical event is received
|
49
|
+
Then 3 email alerts should be queued for malak@example.com
|
50
|
+
|
51
|
+
Scenario: time restrictions continue to work as expected when a contact changes timezone
|
52
|
+
|
53
|
+
@severity @time
|
54
|
+
Scenario: Don't alert when media,severity does not match any matching rule's severity's media
|
55
|
+
Given the check is check 'ping' on entity 'bar'
|
56
|
+
And the check is in an ok state
|
57
|
+
When a warning event is received
|
58
|
+
And 60 minutes passes
|
59
|
+
And a warning event is received
|
60
|
+
Then no email alerts should be queued for malak@example.com
|
61
|
+
|
62
|
+
@severity @time
|
63
|
+
Scenario: Alerts only when media,severity matches any matching rule's severity's media with ok->warning->critical
|
64
|
+
Given the check is check 'ping' on entity 'bar'
|
65
|
+
And the check is in an ok state
|
66
|
+
When a warning event is received
|
67
|
+
And 1 minute passes
|
68
|
+
And a warning event is received
|
69
|
+
Then no email alerts should be queued for malak@example.com
|
70
|
+
When a critical event is received
|
71
|
+
And 5 minute passes
|
72
|
+
And a critical event is received
|
73
|
+
Then 1 email alert should be queued for malak@example.com
|
74
|
+
|
75
|
+
@blackhole
|
76
|
+
Scenario: Drop alerts matching a blackhole rule
|
77
|
+
|
78
|
+
@intervals @time
|
79
|
+
Scenario: Alerts according to custom interval
|
80
|
+
Given the check is check 'ping' on entity 'bar'
|
81
|
+
And the check is in an ok state
|
82
|
+
When a critical event is received
|
83
|
+
Then no email alerts should be queued for malak@example.com
|
84
|
+
When 1 minute passes
|
85
|
+
And a critical event is received
|
86
|
+
Then 1 email alert should be queued for malak@example.com
|
87
|
+
When 10 minutes passes
|
88
|
+
And a critical event is received
|
89
|
+
Then 1 email alert should be queued for malak@example.com
|
90
|
+
When 5 minutes passes
|
91
|
+
And a critical event is received
|
92
|
+
Then 2 email alerts should be queued for malak@example.com
|
93
|
+
|
@@ -1,8 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require 'flapjack/data/entity_check'
|
4
|
-
require 'flapjack/data/event'
|
5
|
-
|
6
3
|
def drain_events
|
7
4
|
loop do
|
8
5
|
event = Flapjack::Data::Event.next(:block => false, :redis => @redis)
|
@@ -53,12 +50,18 @@ def set_ok_state(entity, check)
|
|
53
50
|
:timestamp => (Time.now.to_i - (60*60*24)))
|
54
51
|
end
|
55
52
|
|
56
|
-
def
|
53
|
+
def set_critical_state(entity, check)
|
57
54
|
entity_check = Flapjack::Data::EntityCheck.for_entity_name(entity, check, :redis => @redis)
|
58
55
|
entity_check.update_state(Flapjack::Data::EntityCheck::STATE_CRITICAL,
|
59
56
|
:timestamp => (Time.now.to_i - (60*60*24)))
|
60
57
|
end
|
61
58
|
|
59
|
+
def set_warning_state(entity, check)
|
60
|
+
entity_check = Flapjack::Data::EntityCheck.for_entity_name(entity, check, :redis => @redis)
|
61
|
+
entity_check.update_state(Flapjack::Data::EntityCheck::STATE_WARNING,
|
62
|
+
:timestamp => (Time.now.to_i - (60*60*24)))
|
63
|
+
end
|
64
|
+
|
62
65
|
def submit_ok(entity, check)
|
63
66
|
event = {
|
64
67
|
'type' => 'service',
|
@@ -71,6 +74,18 @@ def submit_ok(entity, check)
|
|
71
74
|
submit_event(event)
|
72
75
|
end
|
73
76
|
|
77
|
+
def submit_warning(entity, check)
|
78
|
+
event = {
|
79
|
+
'type' => 'service',
|
80
|
+
'state' => 'warning',
|
81
|
+
'summary' => '25% packet loss',
|
82
|
+
'entity' => entity,
|
83
|
+
'check' => check,
|
84
|
+
'client' => 'clientx'
|
85
|
+
}
|
86
|
+
submit_event(event)
|
87
|
+
end
|
88
|
+
|
74
89
|
def submit_critical(entity, check)
|
75
90
|
event = {
|
76
91
|
'type' => 'service',
|
@@ -102,62 +117,188 @@ Given /^an entity '([\w\.\-]+)' exists$/ do |entity|
|
|
102
117
|
:redis => @redis )
|
103
118
|
end
|
104
119
|
|
105
|
-
Given
|
120
|
+
Given /^(?:the check|check '([\w\.\-]+)' for entity '([\w\.\-]+)') is in an ok state$/ do |check, entity|
|
121
|
+
check = check ? check : @check
|
122
|
+
entity = entity ? entity : @entity
|
106
123
|
remove_unscheduled_maintenance(entity, check)
|
107
124
|
remove_scheduled_maintenance(entity, check)
|
108
125
|
remove_notifications(entity, check)
|
109
126
|
set_ok_state(entity, check)
|
110
127
|
end
|
111
128
|
|
112
|
-
Given /^check '([\w\.\-]+)' for entity '([\w\.\-]+)' is in a
|
129
|
+
Given /^(?:the check|check '([\w\.\-]+)' for entity '([\w\.\-]+)') is in a critical state$/ do |check, entity|
|
130
|
+
check = check ? check : @check
|
131
|
+
entity = entity ? entity : @entity
|
113
132
|
remove_unscheduled_maintenance(entity, check)
|
114
133
|
remove_scheduled_maintenance(entity, check)
|
115
134
|
remove_notifications(entity, check)
|
116
|
-
|
135
|
+
set_critical_state(entity, check)
|
117
136
|
end
|
118
137
|
|
119
|
-
Given /^check '([\w\.\-]+)' for entity '([\w\.\-]+)' is in scheduled maintenance$/ do |check, entity|
|
138
|
+
Given /^(?:the check|check '([\w\.\-]+)' for entity '([\w\.\-]+)') is in scheduled maintenance$/ do |check, entity|
|
139
|
+
check = check ? check : @check
|
140
|
+
entity = entity ? entity : @entity
|
120
141
|
remove_unscheduled_maintenance(entity, check)
|
121
142
|
set_scheduled_maintenance(entity, check)
|
122
143
|
end
|
123
144
|
|
124
145
|
# TODO set the state directly rather than submit & drain
|
125
|
-
Given /^check '([\w\.\-]+)' for entity '([\w\.\-]+)' is in unscheduled maintenance$/ do |check, entity|
|
146
|
+
Given /^(?:the check|check '([\w\.\-]+)' for entity '([\w\.\-]+)') is in unscheduled maintenance$/ do |check, entity|
|
147
|
+
check = check ? check : @check
|
148
|
+
entity = entity ? entity : @entity
|
126
149
|
remove_scheduled_maintenance(entity, check)
|
127
|
-
|
150
|
+
set_critical_state(entity, check)
|
128
151
|
submit_acknowledgement(entity, check)
|
129
152
|
drain_events # TODO these should only be in When clauses
|
130
153
|
end
|
131
154
|
|
132
|
-
|
155
|
+
Given /^the check is check '(.*)' on entity '([\w\.\-]+)'$/ do |check, entity|
|
156
|
+
@check = check
|
157
|
+
@entity = entity
|
158
|
+
end
|
159
|
+
|
160
|
+
When /^an ok event is received(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
161
|
+
check ||= @check
|
162
|
+
entity ||= @entity
|
133
163
|
submit_ok(entity, check)
|
134
164
|
drain_events
|
135
165
|
end
|
136
166
|
|
137
|
-
When /^a failure event is received for check '([\w\.\-]+)' on entity '([\w\.\-]+)'
|
167
|
+
When /^a failure event is received(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
168
|
+
check ||= @check
|
169
|
+
entity ||= @entity
|
138
170
|
submit_critical(entity, check)
|
139
171
|
drain_events
|
140
172
|
end
|
141
173
|
|
142
|
-
When /^
|
143
|
-
|
174
|
+
When /^a critical event is received(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
175
|
+
check ||= @check
|
176
|
+
entity ||= @entity
|
177
|
+
submit_critical(entity, check)
|
144
178
|
drain_events
|
145
179
|
end
|
146
180
|
|
181
|
+
When /^a warning event is received(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
182
|
+
check ||= @check
|
183
|
+
entity ||= @entity
|
184
|
+
submit_warning(entity, check)
|
185
|
+
drain_events
|
186
|
+
end
|
187
|
+
|
188
|
+
When /^an acknowledgement .*is received(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
189
|
+
check ||= @check
|
190
|
+
entity ||= @entity
|
191
|
+
submit_acknowledgement(entity, check)
|
192
|
+
drain_events
|
193
|
+
end
|
147
194
|
|
148
195
|
# TODO logging is a side-effect, should test for notification generation itself
|
149
|
-
Then /^a notification should not be generated for check '([\w\.\-]+)' on entity '([\w\.\-]+)'
|
196
|
+
Then /^a notification should not be generated(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
197
|
+
check ||= @check
|
198
|
+
entity ||= @entity
|
150
199
|
message = @logger.messages.find_all {|m| m =~ /enerating notifications for event #{entity}:#{check}/ }.last
|
151
|
-
message ?
|
152
|
-
|
200
|
+
found = message ? message.match(/Not generating notifications/) : false
|
201
|
+
found.should be_true
|
153
202
|
end
|
154
203
|
|
155
|
-
Then /^a notification should be generated for check '([\w\.\-]+)' on entity '([\w\.\-]+)'
|
204
|
+
Then /^a notification should be generated(?: for check '([\w\.\-]+)' on entity '([\w\.\-]+)')?$/ do |check, entity|
|
205
|
+
check ||= @check
|
206
|
+
entity ||= @entity
|
156
207
|
message = @logger.messages.find_all {|m| m =~ /enerating notifications for event #{entity}:#{check}/ }.last
|
157
|
-
message ?
|
158
|
-
|
208
|
+
found = message ? message.match(/Generating notifications/) : false
|
209
|
+
found.should be_true
|
159
210
|
end
|
160
211
|
|
161
|
-
Then /^show me the
|
212
|
+
Then /^show me the log$/ do
|
162
213
|
puts @logger.messages.join("\n")
|
163
214
|
end
|
215
|
+
|
216
|
+
# added for notification rules:
|
217
|
+
|
218
|
+
Given /^the following entities exist:$/ do |entities|
|
219
|
+
entities.hashes.each do |entity|
|
220
|
+
contacts = entity['contacts'].split(',')
|
221
|
+
contacts.map! do |contact|
|
222
|
+
contact.strip
|
223
|
+
end
|
224
|
+
#puts "adding entity #{entity['name']} (#{entity['id']}) with contacts: [#{contacts.join(', ')}]"
|
225
|
+
Flapjack::Data::Entity.add({'id' => entity['id'],
|
226
|
+
'name' => entity['name'],
|
227
|
+
'contacts' => contacts},
|
228
|
+
:redis => @redis )
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
Given /^the following users exist:$/ do |contacts|
|
233
|
+
contacts.hashes.each do |contact|
|
234
|
+
media = {}
|
235
|
+
media['email'] = contact['email']
|
236
|
+
media['sms'] = contact['sms']
|
237
|
+
Flapjack::Data::Contact.add({'id' => contact['id'],
|
238
|
+
'first_name' => contact['first_name'],
|
239
|
+
'last_name' => contact['last_name'],
|
240
|
+
'email' => contact['email'],
|
241
|
+
'media' => media},
|
242
|
+
:redis => @redis )
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|
246
|
+
Given /^user (\d+) has the following notification intervals:$/ do |contact_id, intervals|
|
247
|
+
contact = Flapjack::Data::Contact.find_by_id(contact_id, :redis => @redis)
|
248
|
+
intervals.hashes.each do |interval|
|
249
|
+
contact.set_interval_for_media('email', interval['email'].to_i * 60)
|
250
|
+
contact.set_interval_for_media('sms', interval['sms'].to_i * 60)
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
Given /^user (\d+) has the following notification rules:$/ do |contact_id, rules|
|
255
|
+
rules.hashes.each do |rule|
|
256
|
+
entities = rule['entities'].split(',').map { |x| x.strip }
|
257
|
+
entity_tags = rule['entity_tags'].split(',').map { |x| x.strip }
|
258
|
+
warning_media = rule['warning_media'].split(',').map { |x| x.strip }
|
259
|
+
critical_media = rule['critical_media'].split(',').map { |x| x.strip }
|
260
|
+
warning_blackhole = rule['warning_blackhole'].downcase == 'true' ? 'true' : 'false'
|
261
|
+
critical_blackhole = rule['critical_blackhole'].downcase == 'true' ? 'true' : 'false'
|
262
|
+
time_restrictions = []
|
263
|
+
rule['time_restrictions'].split(',').map { |x| x.strip }.each do |time_restriction|
|
264
|
+
case time_restriction
|
265
|
+
when '8-18 weekdays'
|
266
|
+
# FIXME: get timezone from the user definition (or config[:default_contact_timezone])
|
267
|
+
time_zone = ActiveSupport::TimeZone.new("America/New_York")
|
268
|
+
weekdays_8_18 = IceCube::Schedule.new(time_zone.local(2013,2,1,8,0,0), :duration => 60 * 60 * 10)
|
269
|
+
weekdays_8_18.add_recurrence_rule(IceCube::Rule.weekly.day(:monday, :tuesday, :wednesday, :thursday, :friday))
|
270
|
+
time_restrictions << Flapjack::Data::NotificationRule.time_restriction_from_ice_cube_hash(weekdays_8_18.to_hash, time_zone)
|
271
|
+
end
|
272
|
+
end
|
273
|
+
Flapjack::Data::NotificationRule.add({:contact_id => contact_id,
|
274
|
+
:entities => entities,
|
275
|
+
:entity_tags => entity_tags,
|
276
|
+
:warning_media => warning_media,
|
277
|
+
:critical_media => critical_media,
|
278
|
+
:warning_blackhole => warning_blackhole,
|
279
|
+
:critical_blackhole => critical_blackhole,
|
280
|
+
:time_restrictions => time_restrictions}, :redis => @redis)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
Then /^all alert dropping keys for user (\d+) should have expired$/ do |contact_id|
|
285
|
+
@redis.keys("drop_alerts_for_contact:#{contact_id}*").should be_empty
|
286
|
+
end
|
287
|
+
|
288
|
+
# When /^the (\w*) alert block for user (\d*) for (?:the check|check '([\w\.\-]+)' for entity '([\w\.\-]+)') for state (.*) expires$/ do |media, contact, check, entity, state|
|
289
|
+
# check = check ? check : @check
|
290
|
+
# entity = entity ? entity : @entity
|
291
|
+
# num_deleted = @redis.del("drop_alerts_for_contact:#{contact}:#{media}:#{entity}:#{check}:#{state}")
|
292
|
+
# puts "Warning: no keys expired" unless num_deleted > 0
|
293
|
+
# end
|
294
|
+
|
295
|
+
Then /^(.*) email alert(?:s)? should be queued for (.*)$/ do |num_queued, address|
|
296
|
+
check = check ? check : @check
|
297
|
+
entity = entity ? entity : @entity
|
298
|
+
case num_queued
|
299
|
+
when 'no'
|
300
|
+
num_queued = 0
|
301
|
+
end
|
302
|
+
queue = Resque.peek('email_notifications', 0, 30)
|
303
|
+
queue.find_all {|n| n['args'].first['address'] == address }.length.should == num_queued.to_i
|
304
|
+
end
|
@@ -66,7 +66,7 @@ When /^an event notification is generated for entity '([\w\.\-]+)'$/ do |entity|
|
|
66
66
|
'entity' => entity,
|
67
67
|
'check' => 'ping')
|
68
68
|
entity_check = Flapjack::Data::EntityCheck.for_entity_name(entity, 'ping', :redis => @redis)
|
69
|
-
@app.send(:
|
69
|
+
@app.send(:generate_notification_messages, event, entity_check)
|
70
70
|
end
|
71
71
|
|
72
72
|
Then /^an SMS notification for entity '([\w\.\-]+)' should be queued for the user$/ do |entity|
|
@@ -2,33 +2,44 @@
|
|
2
2
|
|
3
3
|
require 'delorean'
|
4
4
|
require 'chronic'
|
5
|
+
require 'active_support/time'
|
5
6
|
|
6
7
|
When /^(.+) passes$/ do |time|
|
7
8
|
period = Chronic.parse("#{time} from now")
|
8
|
-
|
9
|
-
#
|
9
|
+
RedisDelorean.time_travel_to(period)
|
10
|
+
#puts "Time Travelled to #{Time.now.to_s}"
|
10
11
|
end
|
11
12
|
|
12
|
-
Given /^I time travel to (.+)$/ do |period|
|
13
|
-
|
14
|
-
|
15
|
-
end
|
13
|
+
# Given /^I time travel to (.+)$/ do |period|
|
14
|
+
# RedisDelorean.time_travel_to(period)
|
15
|
+
# # puts "Time Travelled to #{Time.now.to_s}"
|
16
|
+
# end
|
16
17
|
|
17
|
-
Given /^
|
18
|
-
|
19
|
-
|
18
|
+
Given /^the timezone is (.*)$/ do |tz|
|
19
|
+
Time.zone = tz
|
20
|
+
Chronic.time_class = Time.zone
|
20
21
|
end
|
21
22
|
|
22
|
-
Given /^
|
23
|
-
|
24
|
-
|
25
|
-
Delorean.time_travel_to time
|
26
|
-
# puts "Time Travelled to #{Time.now.to_s}"
|
23
|
+
Given /^the time is (.*)$/ do |time|
|
24
|
+
RedisDelorean.time_travel_to(Chronic.parse("#{time}"))
|
25
|
+
#puts "Time Travelled to #{Time.now.to_s}"
|
27
26
|
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
28
|
+
# Given /^I come back to the present$/ do
|
29
|
+
# RedisDelorean.back_to_the_present
|
30
|
+
# # puts "Time Travelled to the present, #{Time.now.to_s}"
|
31
|
+
# end
|
32
|
+
|
33
|
+
# Given /^I time travel in (.+) to (.+)$/ do |zone_name, timestamp|
|
34
|
+
# zone = ::Time.find_zone!(zone_name)
|
35
|
+
# time = zone.parse timestamp
|
36
|
+
# RedisDelorean.time_travel_to time
|
37
|
+
# # puts "Time Travelled to #{Time.now.to_s}"
|
38
|
+
# end
|
39
|
+
|
40
|
+
# Then /^the time in UTC should be about (.+)$/ do |timestamp|
|
41
|
+
# actual = Time.now.in_time_zone('UTC')
|
42
|
+
# expected = Time.parse("#{timestamp} UTC")
|
43
|
+
# (expected..expected+5).cover?(actual).should be_true
|
44
|
+
# end
|
34
45
|
|
data/features/support/env.rb
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
require 'delorean'
|
4
|
+
require 'chronic'
|
5
|
+
require 'active_support/time'
|
6
|
+
require 'ice_cube'
|
7
|
+
require 'flapjack/data/entity_check'
|
8
|
+
require 'flapjack/data/event'
|
2
9
|
|
3
10
|
if ENV['COVERAGE']
|
4
11
|
require 'simplecov'
|
@@ -43,9 +50,66 @@ class MockEmailer
|
|
43
50
|
include EM::Deferrable
|
44
51
|
end
|
45
52
|
|
53
|
+
class RedisDelorean
|
54
|
+
|
55
|
+
ExpireAsIfAtScript = <<EXPIRE_AS_IF_AT
|
56
|
+
local keys = redis.call('keys', KEYS[1])
|
57
|
+
local current_time = tonumber(ARGV[1])
|
58
|
+
local expire_as_if_at = tonumber(ARGV[2])
|
59
|
+
local counter = 0
|
60
|
+
|
61
|
+
for i,k in ipairs(keys) do
|
62
|
+
local key_ttl = redis.call('ttl', k)
|
63
|
+
|
64
|
+
-- key does not have timeout if < 0
|
65
|
+
if ( (key_ttl >= 0) and ((current_time + key_ttl) <= expire_as_if_at) ) then
|
66
|
+
redis.call('del', k)
|
67
|
+
counter = counter + 1
|
68
|
+
end
|
69
|
+
end
|
70
|
+
return counter
|
71
|
+
EXPIRE_AS_IF_AT
|
72
|
+
|
73
|
+
def self.before_all(options = {})
|
74
|
+
redis = options[:redis]
|
75
|
+
@expire_as_if_at_sha = redis.script(:load, ExpireAsIfAtScript)
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.before_each(options = {})
|
79
|
+
@redis = options[:redis]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.time_travel_to(dest_time)
|
83
|
+
# puts "travelling to #{Time.now.in_time_zone}, real time is #{Time.now_without_delorean.in_time_zone}"
|
84
|
+
old_maybe_fake_time = Time.now.in_time_zone
|
85
|
+
|
86
|
+
Delorean.time_travel_to(dest_time)
|
87
|
+
# puts "travelled to #{Time.now.in_time_zone}, real time is #{Time.now_without_delorean.in_time_zone}"
|
88
|
+
return if dest_time < old_maybe_fake_time
|
89
|
+
|
90
|
+
# dumps the first offset -- we're not interested in time difference from
|
91
|
+
# real, only with context to the fake frame of reference...
|
92
|
+
# This may mean all scenarios using this code should have an initial
|
93
|
+
# Given it is *datetime*
|
94
|
+
# step
|
95
|
+
offsets = Delorean.send(:time_travel_offsets).dup
|
96
|
+
offsets.shift
|
97
|
+
delta = -offsets.inject(0){ |sum, val| sum + val }.floor
|
98
|
+
|
99
|
+
real_time = Time.now_without_delorean.to_i
|
100
|
+
# puts "delta #{delta}, expire before real time #{Time.at(real_time + delta)}"
|
101
|
+
|
102
|
+
result = @redis.evalsha(@expire_as_if_at_sha, ['*'],
|
103
|
+
[real_time, real_time + delta])
|
104
|
+
# puts "Expired #{result} key#{(result == 1) ? '' : 's'}"
|
105
|
+
end
|
106
|
+
|
107
|
+
end
|
108
|
+
|
46
109
|
redis_opts = { :db => 14, :driver => :ruby }
|
47
110
|
redis = ::Redis.new(redis_opts)
|
48
111
|
redis.flushdb
|
112
|
+
RedisDelorean.before_all(:redis => redis)
|
49
113
|
redis.quit
|
50
114
|
|
51
115
|
# NB: this seems to execute outside the Before/After hooks
|
@@ -63,7 +127,8 @@ Before do
|
|
63
127
|
# Use a separate database whilst testing
|
64
128
|
@app = Flapjack::Executive.new(:logger => @logger,
|
65
129
|
:config => {'email_queue' => 'email_notifications',
|
66
|
-
'sms_queue' => 'sms_notifications'
|
130
|
+
'sms_queue' => 'sms_notifications',
|
131
|
+
'default_contact_timezone' => 'America/New_York'},
|
67
132
|
:redis_config => redis_opts)
|
68
133
|
@redis = @app.instance_variable_get('@redis')
|
69
134
|
end
|
@@ -77,6 +142,11 @@ end
|
|
77
142
|
|
78
143
|
Before('@resque') do
|
79
144
|
ResqueSpec.reset!
|
145
|
+
@queues = {:email => 'email_queue'}
|
146
|
+
end
|
147
|
+
|
148
|
+
Before('@time') do
|
149
|
+
RedisDelorean.before_each(:redis => @redis)
|
80
150
|
end
|
81
151
|
|
82
152
|
After('@time') do
|