right_agent 2.2.1 → 2.3.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.
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