right_agent 2.2.1 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/lib/right_agent.rb +1 -0
  3. data/lib/right_agent/actor.rb +0 -28
  4. data/lib/right_agent/actors/agent_manager.rb +20 -18
  5. data/lib/right_agent/agent.rb +69 -87
  6. data/lib/right_agent/agent_tag_manager.rb +1 -1
  7. data/lib/right_agent/clients/api_client.rb +0 -1
  8. data/lib/right_agent/clients/auth_client.rb +2 -6
  9. data/lib/right_agent/clients/balanced_http_client.rb +2 -2
  10. data/lib/right_agent/clients/base_retry_client.rb +12 -19
  11. data/lib/right_agent/clients/right_http_client.rb +1 -5
  12. data/lib/right_agent/clients/router_client.rb +8 -15
  13. data/lib/right_agent/command/command_parser.rb +3 -3
  14. data/lib/right_agent/command/command_runner.rb +1 -1
  15. data/lib/right_agent/connectivity_checker.rb +7 -11
  16. data/lib/right_agent/dispatcher.rb +7 -42
  17. data/lib/right_agent/enrollment_result.rb +2 -2
  18. data/lib/right_agent/error_tracker.rb +202 -0
  19. data/lib/right_agent/log.rb +0 -2
  20. data/lib/right_agent/packets.rb +1 -1
  21. data/lib/right_agent/pending_requests.rb +10 -4
  22. data/lib/right_agent/pid_file.rb +3 -3
  23. data/lib/right_agent/protocol_version_mixin.rb +3 -3
  24. data/lib/right_agent/scripts/agent_deployer.rb +13 -1
  25. data/lib/right_agent/sender.rb +14 -30
  26. data/lib/right_agent/serialize/secure_serializer.rb +4 -4
  27. data/right_agent.gemspec +2 -2
  28. data/spec/agent_spec.rb +5 -5
  29. data/spec/clients/auth_client_spec.rb +1 -1
  30. data/spec/clients/balanced_http_client_spec.rb +4 -2
  31. data/spec/clients/base_retry_client_spec.rb +5 -6
  32. data/spec/clients/router_client_spec.rb +1 -4
  33. data/spec/dispatcher_spec.rb +6 -55
  34. data/spec/error_tracker_spec.rb +293 -0
  35. data/spec/pending_requests_spec.rb +2 -2
  36. data/spec/sender_spec.rb +3 -3
  37. data/spec/spec_helper.rb +4 -2
  38. metadata +33 -66
@@ -28,9 +28,9 @@ module RightScale
28
28
 
29
29
  include ProtocolVersionMixin
30
30
 
31
- class MissingPrivateKey < Exception; end
32
- class MissingCertificate < Exception; end
33
- class InvalidSignature < Exception; end
31
+ class MissingPrivateKey < StandardError; end
32
+ class MissingCertificate < StandardError; end
33
+ class InvalidSignature < StandardError; end
34
34
 
35
35
  # Create the one and only SecureSerializer
36
36
  def self.init(serializer, identity, store, encrypt = true)
@@ -105,7 +105,7 @@ module RightScale
105
105
  msg = EncryptedDocument.new(msg, certs).encrypted_data(encode_format)
106
106
  else
107
107
  target = obj.target_for_encryption if obj.respond_to?(:target_for_encryption)
108
- Log.error("No certs available for object #{obj.class} being sent to #{target.inspect}\n") if target
108
+ ErrorTracker.log(self, "No certs available for object #{obj.class} being sent to #{target.inspect}") if target
109
109
  end
110
110
  end
111
111
  sig = Signature.new(msg, @cert, @key).data(encode_format)
@@ -25,8 +25,8 @@ require 'rbconfig'
25
25
 
26
26
  Gem::Specification.new do |spec|
27
27
  spec.name = 'right_agent'
28
- spec.version = '2.2.1'
29
- spec.date = '2014-05-07'
28
+ spec.version = '2.3.0'
29
+ spec.date = '2014-05-27'
30
30
  spec.authors = ['Lee Kirchhoff', 'Raphael Simon', 'Tony Spataro', 'Scott Messier']
31
31
  spec.email = 'lee@rightscale.com'
32
32
  spec.homepage = 'https://github.com/rightscale/right_agent'
@@ -493,10 +493,10 @@ describe RightScale::Agent do
493
493
  it "should ack if request dispatch fails" do
494
494
  run_in_em do
495
495
  request = RightScale::Request.new("/foo/bar", "payload")
496
- @log.should_receive(:error).with(/Failed to dispatch request/, Exception, :trace).once
496
+ @log.should_receive(:error).with(/Failed to dispatch request/, StandardError, :trace).once
497
497
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
498
498
  and_return(@broker_ids).and_yield(@broker_id, request, @header).once
499
- @dispatcher.should_receive(:dispatch).and_raise(Exception)
499
+ @dispatcher.should_receive(:dispatch).and_raise(StandardError)
500
500
  @header.should_receive(:ack).once
501
501
  @agent.run
502
502
  end
@@ -505,10 +505,10 @@ describe RightScale::Agent do
505
505
  it "should ack if response delivery fails" do
506
506
  run_in_em do
507
507
  result = RightScale::Result.new("token", "to", "results", "from")
508
- @log.should_receive(:error).with(/Failed to deliver response/, Exception, :trace).once
508
+ @log.should_receive(:error).with(/Failed to deliver response/, StandardError, :trace).once
509
509
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
510
510
  and_return(@broker_ids).and_yield(@broker_id, result, @header).once
511
- @sender.should_receive(:handle_response).and_raise(Exception)
511
+ @sender.should_receive(:handle_response).and_raise(StandardError)
512
512
  @header.should_receive(:ack).once
513
513
  @agent.run
514
514
  end
@@ -528,7 +528,7 @@ describe RightScale::Agent do
528
528
  run_in_em do
529
529
  @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
530
530
  @broker.should_receive(:nil?).and_return(true)
531
- @log.should_receive(:error).with("[stop] Terminating because just because", nil, :trace).once
531
+ @log.should_receive(:error).with("[stop] Terminating because just because").once
532
532
  @agent.terminate("just because")
533
533
  end
534
534
  end
@@ -269,7 +269,7 @@ describe RightScale::AuthClient do
269
269
  end
270
270
 
271
271
  it "log error if callback fails" do
272
- @log.should_receive(:error).with("Failed status callback", StandardError).once
272
+ @log.should_receive(:error).with("Failed status callback", StandardError, :caller).once
273
273
  @client.status { |t, s| raise StandardError, "test" }
274
274
  @client.send(:state=, state).should == state
275
275
  end
@@ -412,7 +412,8 @@ describe RightScale::BalancedHttpClient do
412
412
  gateway_timeout = RightScale::HttpExceptions.create(504, "server timeout")
413
413
  bad_request = RightScale::HttpExceptions.create(400, "bad data")
414
414
  @no_result = RightSupport::Net::NoResult.new("no result", {@url => gateway_timeout, @url => bad_request})
415
- lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
415
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.
416
+ should raise_error(RightScale::HttpExceptions::BadRequest)
416
417
  @yielded.should == bad_request
417
418
  end
418
419
 
@@ -458,7 +459,8 @@ describe RightScale::BalancedHttpClient do
458
459
  it "raises last exception in details if not retryable" do
459
460
  bad_request = RightScale::HttpExceptions.create(400, "bad data")
460
461
  @no_result = RightSupport::Net::NoResult.new("no result", {@url => bad_request})
461
- lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
462
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.
463
+ should raise_error(RightScale::HttpExceptions::BadRequest)
462
464
  @yielded.should == bad_request
463
465
  end
464
466
  end
@@ -89,7 +89,6 @@ describe RightScale::BaseRetryClient do
89
89
  options = @client.instance_variable_get(:@options)
90
90
  options[:retry_enabled].should be_nil
91
91
  options[:filter_params].should be_nil
92
- options[:exception_callback].should be_nil
93
92
  end
94
93
 
95
94
  it "initiates connection and enables use if connected" do
@@ -214,7 +213,7 @@ describe RightScale::BaseRetryClient do
214
213
  end
215
214
 
216
215
  it "log error if callback fails" do
217
- @log.should_receive(:error).with("Failed status callback", StandardError).once
216
+ @log.should_receive(:error).with("Failed status callback", StandardError, :caller).once
218
217
  @client.status { |t, s| raise StandardError, "test" }
219
218
  @client.send(:state=, state).should == state
220
219
  end
@@ -321,13 +320,13 @@ describe RightScale::BaseRetryClient do
321
320
  it "sets state to :disconnected and logs if server not responding" do
322
321
  e = RightScale::BalancedHttpClient::NotResponding.new("not responding", RightScale::HttpExceptions.create(503))
323
322
  @http_client.should_receive(:check_health).and_raise(e).once
324
- @log.should_receive(:error).with("Failed test health check", RightScale::HttpException).once
323
+ @log.should_receive(:error).with("Failed test health check", RightScale::HttpException, :trace).once
325
324
  @client.send(:check_health).should == :disconnected
326
325
  @client.state.should == :disconnected
327
326
  end
328
327
 
329
328
  it "sets state to :disconnected and logs if exception unexpected" do
330
- @log.should_receive(:error).with("Failed test health check", StandardError).once
329
+ @log.should_receive(:error).with("Failed test health check", StandardError, :caller).once
331
330
  @http_client.should_receive(:check_health).and_raise(StandardError).once
332
331
  @client.send(:check_health).should == :disconnected
333
332
  @client.state.should == :disconnected
@@ -387,14 +386,14 @@ describe RightScale::BaseRetryClient do
387
386
  it "logs error if exception is raised" do
388
387
  flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
389
388
  flexmock(@client).should_receive(:enable_use).and_raise(StandardError).once
390
- @log.should_receive(:error).with("Failed test reconnect", StandardError).once
389
+ @log.should_receive(:error).with("Failed test reconnect", StandardError, :caller).once
391
390
  @client.send(:reconnect).should be_true
392
391
  @client.state.should == :disconnected
393
392
  end
394
393
 
395
394
  it "resets the timer interval to the configured value" do
396
395
  @client.send(:reconnect); @client.instance_variable_set(:@reconnecting, nil) # to get @reconnect_timer initialized
397
- @log.should_receive(:error).with("Failed test health check", StandardError).once
396
+ @log.should_receive(:error).with("Failed test health check", StandardError, :caller).once
398
397
  @http_client.should_receive(:check_health).and_raise(StandardError).once
399
398
  @timer.should_receive(:interval=).with(15).once
400
399
  flexmock(EM::PeriodicTimer).should_receive(:new).and_return(@timer).and_yield
@@ -470,7 +470,7 @@ describe RightScale::RouterClient do
470
470
  end
471
471
 
472
472
  it "adjusts connect interval if websocket creation fails and sets state to :long_poll" do
473
- @log.should_receive(:error).with("Failed creating WebSocket", RuntimeError).once
473
+ @log.should_receive(:error).with("Failed creating WebSocket", RuntimeError, :caller).once
474
474
  flexmock(Faye::WebSocket::Client).should_receive(:new).and_raise(RuntimeError).once
475
475
  @client.send(:try_connect, @routing_keys, &@handler)
476
476
  @client.instance_variable_get(:@connect_interval).should == 60
@@ -661,7 +661,6 @@ describe RightScale::RouterClient do
661
661
  end
662
662
 
663
663
  it "handles exception if there is a long-polling failure" do
664
- @log.should_receive(:error).with("Failed long-polling", RuntimeError, :trace).once
665
664
  @defer_callback_proc.call(RuntimeError.new).should be nil
666
665
  @client.instance_variable_get(:@listen_state).should == :choose
667
666
  @client.instance_variable_get(:@listen_interval).should == 4
@@ -745,7 +744,6 @@ describe RightScale::RouterClient do
745
744
  RightScale::Exceptions::RetryableError.new("error"),
746
745
  RightScale::Exceptions::InternalServerError.new("error", "server")].each do |e|
747
746
  it "does not trace #{e} exceptions but sets state to :choose" do
748
- @log.should_receive(:error).with("Failed long-polling", e, :no_trace).once
749
747
  @client.send(:process_long_poll, e).should be nil
750
748
  @client.instance_variable_get(:@listen_state).should == :choose
751
749
  @client.instance_variable_get(:@listen_interval).should == 4
@@ -754,7 +752,6 @@ describe RightScale::RouterClient do
754
752
 
755
753
  it "traces unexpected exceptions and sets state to :choose" do
756
754
  e = RuntimeError.new
757
- @log.should_receive(:error).with("Failed long-polling", e, :trace).once
758
755
  @client.send(:process_long_poll, e).should be nil
759
756
  @client.instance_variable_get(:@listen_state).should == :choose
760
757
  @client.instance_variable_get(:@listen_interval).should == 4
@@ -26,7 +26,6 @@ class Foo
26
26
  include RightScale::Actor
27
27
  expose_idempotent :bar, :index, :i_kill_you
28
28
  expose_non_idempotent :bar_non
29
- on_exception :handle_exception
30
29
 
31
30
  def index(payload)
32
31
  bar(payload)
@@ -47,34 +46,17 @@ class Foo
47
46
  def i_kill_you(payload)
48
47
  raise RuntimeError.new('I kill you!')
49
48
  end
50
-
51
- def handle_exception(method, deliverable, error)
52
- end
53
49
  end
54
50
 
55
51
  class Bar
56
52
  include RightScale::Actor
57
53
  expose :i_kill_you
58
- on_exception do |method, deliverable, error|
59
- @scope = self
60
- @called_with = [method, deliverable, error]
61
- end
62
54
 
63
55
  def i_kill_you(payload)
64
56
  raise RuntimeError.new('I kill you!')
65
57
  end
66
58
  end
67
59
 
68
- # No specs, simply ensures multiple methods for assigning on_exception callback,
69
- # on_exception raises exception when called with an invalid argument.
70
- class Doomed
71
- include RightScale::Actor
72
- on_exception do
73
- end
74
- on_exception lambda {}
75
- on_exception :doh
76
- end
77
-
78
60
  describe "RightScale::Dispatcher" do
79
61
 
80
62
  include FlexMock::ArgumentTypes
@@ -89,7 +71,7 @@ describe "RightScale::Dispatcher" do
89
71
  @registry = RightScale::ActorRegistry.new
90
72
  @registry.register(@actor, nil)
91
73
  @agent_id = "rs-agent-1-1"
92
- @agent = flexmock("Agent", :identity => @agent_id, :registry => @registry, :exception_callback => nil).by_default
74
+ @agent = flexmock("Agent", :identity => @agent_id, :registry => @registry).by_default
93
75
  @cache = RightScale::DispatchedCache.new(@agent_id)
94
76
  @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
95
77
  end
@@ -157,41 +139,17 @@ describe "RightScale::Dispatcher" do
157
139
  lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::InvalidRequestType)
158
140
  end
159
141
 
160
- it "should call the on_exception callback if something goes wrong" do
161
- @log.should_receive(:error).once
142
+ it "should log exception if dispatch fails" do
143
+ @log.should_receive(:error).with(/Failed dispatching/, RuntimeError, :trace).once
162
144
  req = RightScale::Request.new('/foo/i_kill_you', nil)
163
- flexmock(@actor).should_receive(:handle_exception).with(:i_kill_you, req, Exception).once
164
- res = @dispatcher.dispatch(req)
165
- res.results.error?.should be_true
166
- (res.results.content =~ /Could not handle \/foo\/i_kill_you request/).should be_true
167
- end
168
-
169
- it "should call on_exception Procs defined in a subclass with the correct arguments" do
170
- @log.should_receive(:error).once
171
- actor = Bar.new
172
- @registry.register(actor, nil)
173
- req = RightScale::Request.new('/bar/i_kill_you', nil)
174
145
  @dispatcher.dispatch(req)
175
- called_with = actor.instance_variable_get("@called_with")
176
- called_with[0].should == :i_kill_you
177
- called_with[1].should == req
178
- called_with[2].should be_kind_of(RuntimeError)
179
- called_with[2].message.should == 'I kill you!'
180
146
  end
181
147
 
182
- it "should call on_exception Procs defined in a subclass in the scope of the actor" do
148
+ it "should return error result if dispatch fails" do
183
149
  @log.should_receive(:error).once
184
- actor = Bar.new
185
- @registry.register(actor, nil)
186
- req = RightScale::Request.new('/bar/i_kill_you', nil)
187
- @dispatcher.dispatch(req)
188
- actor.instance_variable_get("@scope").should == actor
189
- end
190
-
191
- it "should log error if dispatch fails" do
192
- RightScale::Log.should_receive(:error).once
193
150
  req = RightScale::Request.new('/foo/i_kill_you', nil)
194
- @dispatcher.dispatch(req)
151
+ res = @dispatcher.dispatch(req)
152
+ res.results.error?.should be_true
195
153
  end
196
154
 
197
155
  it "should reject requests whose time-to-live has expired" do
@@ -312,13 +270,6 @@ describe "RightScale::Dispatcher" do
312
270
  end
313
271
  end
314
272
 
315
- it "should return error result if dispatch fails" do
316
- @log.should_receive(:error).with(/Could not handle/, Exception, :trace).once
317
- req = RightScale::Request.new('/foo/i_kill_you', nil)
318
- res = @dispatcher.dispatch(req)
319
- res.results.error?.should be_true
320
- end
321
-
322
273
  end
323
274
 
324
275
  end # RightScale::Dispatcher
@@ -0,0 +1,293 @@
1
+ #
2
+ # Copyright (c) 2014 RightScale Inc
3
+ #
4
+ # Permission is hereby granted, free of charge, to any person obtaining
5
+ # a copy of this software and associated documentation files (the
6
+ # "Software"), to deal in the Software without restriction, including
7
+ # without limitation the rights to use, copy, modify, merge, publish,
8
+ # distribute, sublicense, and/or sell copies of the Software, and to
9
+ # permit persons to whom the Software is furnished to do so, subject to
10
+ # the following conditions:
11
+ #
12
+ # The above copyright notice and this permission notice shall be
13
+ # included in all copies or substantial portions of the Software.
14
+ #
15
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
19
+ # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
20
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
21
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
+
23
+ require 'hydraulic_brake'
24
+
25
+ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
26
+
27
+ describe RightScale::ErrorTracker do
28
+
29
+ include FlexMock::ArgumentTypes
30
+
31
+ class AgentMock; end
32
+
33
+ before(:each) do
34
+ @agent = AgentMock.new
35
+ @agent_name = "test_agent"
36
+ @shard_id = 9
37
+ @endpoint = "https://airbrake.com"
38
+ @api_key = "secret"
39
+ @trace_level = RightScale::Agent::TRACE_LEVEL
40
+ @tracker = RightScale::ErrorTracker.instance
41
+ @log = flexmock(RightScale::Log)
42
+ @brake = flexmock(HydraulicBrake)
43
+ end
44
+
45
+ context :init do
46
+ it "initializes the tracker" do
47
+ flexmock(@tracker).should_receive(:notify_init).with(@agent_name, @shard_id, @endpoint, @api_key).once
48
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id,
49
+ :airbrake_endpoint => @endpoint, :airbrake_api_key => @api_key).should be true
50
+ @tracker.instance_variable_get(:@agent).should == @agent
51
+ @tracker.instance_variable_get(:@trace_level).should == @trace_level
52
+ @tracker.exception_stats.should be_a RightSupport::Stats::Exceptions
53
+ end
54
+ end
55
+
56
+ context :log do
57
+ before(:each) do
58
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
59
+ end
60
+
61
+ context "exception nil" do
62
+ it "logs description" do
63
+ @log.should_receive(:error).with("failed").once
64
+ @tracker.log(self, "failed").should be true
65
+ end
66
+ end
67
+
68
+ context "exception string" do
69
+ it "logs error string" do
70
+ @log.should_receive(:error).with("failed", "error").once
71
+ @tracker.log(self, "failed", "error").should be true
72
+ end
73
+ end
74
+
75
+ context "exception" do
76
+ it "logs exception" do
77
+ @log.should_receive(:error).with("failed", RuntimeError, :trace).once
78
+ @tracker.log(self, "failed", RuntimeError.new("error")).should be true
79
+ end
80
+
81
+ it "applies trace level configured for selected exceptions" do
82
+ @log.should_receive(:error).with("failed", RightScale::BalancedHttpClient::NotResponding, :no_trace).once
83
+ @tracker.log(self, "failed", RightScale::BalancedHttpClient::NotResponding.new("error")).should be true
84
+ end
85
+
86
+ it "applies specified trace level" do
87
+ @log.should_receive(:error).with("failed", RuntimeError, :caller).once
88
+ @tracker.log(self, "failed", RuntimeError.new("error"), nil, :caller).should be true
89
+ end
90
+
91
+ it "tracks exception statistics when component is not a string" do
92
+ request = RightScale::Request.new("/foo/bar", "payload")
93
+ @log.should_receive(:error).with("failed", RuntimeError, :trace).once
94
+ flexmock(@tracker).should_receive(:track).with(self, RuntimeError, request).once
95
+ @tracker.log(self, "failed", RuntimeError.new("error"), request).should be true
96
+ end
97
+
98
+ it "tracks exception statistics when component is a string" do
99
+ request = RightScale::Request.new("/foo/bar", "payload")
100
+ @log.should_receive(:error).once
101
+ flexmock(@tracker).should_receive(:track).with("test", RuntimeError, request).once
102
+ @tracker.log("test", "failed", RuntimeError.new("error"), request).should be true
103
+ end
104
+
105
+ it "does not track exception statistics for :no_trace" do
106
+ @log.should_receive(:error).once
107
+ flexmock(@tracker).should_receive(:track).never
108
+ @tracker.log(self, "failed", RuntimeError.new("error"), nil, :no_trace).should be true
109
+ end
110
+ end
111
+
112
+ it "logs error if error logging fails" do
113
+ @log.should_receive(:error).with("failed", RuntimeError, :trace).once.ordered
114
+ @log.should_receive(:error).with("Failed to log error", RuntimeError, :trace).once.ordered
115
+ flexmock(@tracker).should_receive(:track).and_raise(RuntimeError).once
116
+ @tracker.log(self, "failed", RuntimeError.new("error")).should be false
117
+ end
118
+
119
+ it "does not raise exception even if exception logging fails" do
120
+ @log.should_receive(:error).and_raise(RuntimeError).twice
121
+ @tracker.log(self, "failed").should be false
122
+ end
123
+ end
124
+
125
+ context :track do
126
+ before(:each) do
127
+ @now = Time.at(1000000)
128
+ flexmock(Time).should_receive(:now).and_return(@now)
129
+ @exception = RuntimeError.new("error")
130
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
131
+ end
132
+
133
+ it "records exception in stats when component is an object" do
134
+ @tracker.track(@agent, @exception).should be true
135
+ @tracker.exception_stats.all.should == {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1, "when" => 1000000,
136
+ "type" => "RuntimeError", "message" => "error", "where" => nil}]}}
137
+ end
138
+
139
+ it "records exception in stats when component is a string" do
140
+ request = RightScale::Request.new("/foo/bar", "payload")
141
+ @tracker.track("test", @exception, request).should be true
142
+ @tracker.exception_stats.all.should == {"test" => {"total" => 1, "recent" => [{"count" => 1, "when" => 1000000,
143
+ "type" => "RuntimeError", "message" => "error", "where" => nil}]}}
144
+ end
145
+ end
146
+
147
+ context :notify do
148
+ before(:each) do
149
+ @exception = RuntimeError.new("error")
150
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id,
151
+ :airbrake_endpoint => @endpoint, :airbrake_api_key => @api_key)
152
+ @cgi_data = @tracker.instance_variable_get(:@cgi_data)
153
+ end
154
+
155
+ it "sends notification using HydraulicBrake" do
156
+ @brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
157
+ a[:error_class] == "RuntimeError" &&
158
+ a[:backtrace].nil? &&
159
+ ["test", "development"].include?(a[:environment_name]) &&
160
+ a[:cgi_data].should == @cgi_data &&
161
+ a.keys & [:component, :action, :parameters, :session_data] == [] }).once
162
+ @tracker.notify(@exception).should be true
163
+ end
164
+
165
+ it "includes packet data in notification" do
166
+ request = RightScale::Request.new("/foo/bar", {:pay => "load"}, :token => "token")
167
+ @brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
168
+ a[:error_class] == "RuntimeError" &&
169
+ a[:backtrace].nil? &&
170
+ a[:action] == "bar" &&
171
+ a[:parameters] == {:pay => "load"} &&
172
+ ["test", "development"].include?(a[:environment_name]) &&
173
+ a[:cgi_data] == @cgi_data &&
174
+ a[:session_data] == {:uuid => "token"} }).once
175
+ @tracker.notify(@exception, request).should be true
176
+ end
177
+
178
+ it "includes event data in notification" do
179
+ @brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
180
+ a[:error_class] == "RuntimeError" &&
181
+ a[:backtrace].nil? &&
182
+ a[:action] == "bar" &&
183
+ a[:parameters] == {:pay => "load"} &&
184
+ ["test", "development"].include?(a[:environment_name]) &&
185
+ a[:cgi_data] == @cgi_data &&
186
+ a[:session_data] == {:uuid => "token"} }).once
187
+ @tracker.notify(@exception, {"uuid" => "token", "path" => "/foo/bar", "data" => {:pay => "load"}}).should be true
188
+ end
189
+
190
+ it "adds agent class to :cgi_data in notification" do
191
+ @brake.should_receive(:notify).with(on { |a| a[:cgi_data] == @cgi_data.merge(:agent_class => "AgentMock") }).once
192
+ @tracker.notify(@exception, packet = nil, @agent).should be true
193
+ end
194
+
195
+ it "adds component to notification" do
196
+ @brake.should_receive(:notify).with(on { |a| a[:component] == "component" }).once
197
+ @tracker.notify(@exception, packet = nil, agent = nil, "component").should be true
198
+ end
199
+
200
+ it "functions even if cgi_data has not been initialized by notify_init" do
201
+ @tracker.instance_variable_set(:@cgi_data, nil)
202
+ @brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
203
+ a[:error_class] == "RuntimeError" &&
204
+ a[:backtrace].nil? &&
205
+ ["test", "development"].include?(a[:environment_name]) &&
206
+ a.keys & [:cgi_data, :component, :action, :parameters, :session_data] == [] }).once
207
+ @tracker.notify(@exception).should be true
208
+ end
209
+
210
+ it "does nothing if notify is disabled" do
211
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
212
+ @brake.should_receive(:notify).never
213
+ @tracker.notify(@exception).should be true
214
+ end
215
+ end
216
+
217
+ context :notify_callback do
218
+ it "returns proc that calls notify" do
219
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id,
220
+ :airbrake_endpoint => @endpoint, :airbrake_api_key => @api_key)
221
+ flexmock(@tracker).should_receive(:notify).with("exception", "packet", "agent", "component").once
222
+ @tracker.notify_callback.call("exception", "packet", "agent", "component")
223
+ end
224
+ end
225
+
226
+ context :stats do
227
+ before(:each) do
228
+ @now = Time.at(1000000)
229
+ flexmock(Time).should_receive(:now).and_return(@now)
230
+ @exception = RuntimeError.new("error")
231
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
232
+ end
233
+
234
+ it "returns exception stats" do
235
+ @tracker.track(@agent, @exception).should be true
236
+ @tracker.stats.should == {"exceptions" => {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1,
237
+ "when" => 1000000, "type" => "RuntimeError", "message" => "error", "where" => nil}]}}}
238
+ end
239
+
240
+ it "resets stats after collecting current stats" do
241
+ @tracker.track(@agent, @exception).should be true
242
+ @tracker.stats(true).should == {"exceptions" => {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1,
243
+ "when" => 1000000, "type" => "RuntimeError", "message" => "error", "where" => nil}]}}}
244
+ @tracker.stats.should == {"exceptions" => nil}
245
+ end
246
+ end
247
+
248
+ context :notify_init do
249
+ class ConfigMock
250
+ attr_accessor :secure, :host, :port, :api_key, :project_root
251
+ end
252
+
253
+ it "does not initialize if Airbrake endpoint or API key is undefined" do
254
+ @brake.should_receive(:configure).never
255
+ @tracker.send(:notify_init, @agent_name, @shard_id, @endpoint, nil)
256
+ @tracker.instance_variable_get(:@notify_enabled).should be false
257
+ @tracker.send(:notify_init, @agent_name, @shard_id, nil, @api_key)
258
+ @tracker.instance_variable_get(:@notify_enabled).should be false
259
+ @tracker.instance_variable_get(:@cgi_data).should be_empty
260
+ end
261
+
262
+ it "initializes cgi data and configures HydraulicBrake" do
263
+ config = ConfigMock.new
264
+ @brake.should_receive(:configure).and_yield(config).once
265
+ @tracker.send(:notify_init, @agent_name, @shard_id, @endpoint, @api_key).should be true
266
+ cgi_data = @tracker.instance_variable_get(:@cgi_data)
267
+ cgi_data[:agent_name].should == @agent_name
268
+ cgi_data[:pid].should be_a Integer
269
+ cgi_data[:process].should be_a String
270
+ cgi_data[:shard_id].should == @shard_id
271
+ config.secure.should be true
272
+ config.host.should == "airbrake.com"
273
+ config.port.should == 443
274
+ config.api_key.should == @api_key
275
+ config.project_root.should be_a String
276
+ @tracker.instance_variable_get(:@notify_enabled).should be true
277
+ end
278
+
279
+ it "raises exception if hydraulic_gem is not available" do
280
+ flexmock(@tracker).should_receive(:require_succeeds?).with("hydraulic_brake").and_return(false)
281
+ lambda do
282
+ @tracker.send(:notify_init, @agent_name, @shard_id, @endpoint, @api_key)
283
+ end.should raise_error(RuntimeError, /hydraulic_brake gem missing/)
284
+ end
285
+ end
286
+
287
+ context "notify_init" do
288
+ before(:each) do
289
+ @tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id,
290
+ :airbrake_endpoint => @endpoint, :airbrake_api_key => @api_key)
291
+ end
292
+ end
293
+ end