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.
- checksums.yaml +7 -0
- data/lib/right_agent.rb +1 -0
- data/lib/right_agent/actor.rb +0 -28
- data/lib/right_agent/actors/agent_manager.rb +20 -18
- data/lib/right_agent/agent.rb +69 -87
- data/lib/right_agent/agent_tag_manager.rb +1 -1
- data/lib/right_agent/clients/api_client.rb +0 -1
- data/lib/right_agent/clients/auth_client.rb +2 -6
- data/lib/right_agent/clients/balanced_http_client.rb +2 -2
- data/lib/right_agent/clients/base_retry_client.rb +12 -19
- data/lib/right_agent/clients/right_http_client.rb +1 -5
- data/lib/right_agent/clients/router_client.rb +8 -15
- data/lib/right_agent/command/command_parser.rb +3 -3
- data/lib/right_agent/command/command_runner.rb +1 -1
- data/lib/right_agent/connectivity_checker.rb +7 -11
- data/lib/right_agent/dispatcher.rb +7 -42
- data/lib/right_agent/enrollment_result.rb +2 -2
- data/lib/right_agent/error_tracker.rb +202 -0
- data/lib/right_agent/log.rb +0 -2
- data/lib/right_agent/packets.rb +1 -1
- data/lib/right_agent/pending_requests.rb +10 -4
- data/lib/right_agent/pid_file.rb +3 -3
- data/lib/right_agent/protocol_version_mixin.rb +3 -3
- data/lib/right_agent/scripts/agent_deployer.rb +13 -1
- data/lib/right_agent/sender.rb +14 -30
- data/lib/right_agent/serialize/secure_serializer.rb +4 -4
- data/right_agent.gemspec +2 -2
- data/spec/agent_spec.rb +5 -5
- data/spec/clients/auth_client_spec.rb +1 -1
- data/spec/clients/balanced_http_client_spec.rb +4 -2
- data/spec/clients/base_retry_client_spec.rb +5 -6
- data/spec/clients/router_client_spec.rb +1 -4
- data/spec/dispatcher_spec.rb +6 -55
- data/spec/error_tracker_spec.rb +293 -0
- data/spec/pending_requests_spec.rb +2 -2
- data/spec/sender_spec.rb +3 -3
- data/spec/spec_helper.rb +4 -2
- metadata +33 -66
@@ -28,9 +28,9 @@ module RightScale
|
|
28
28
|
|
29
29
|
include ProtocolVersionMixin
|
30
30
|
|
31
|
-
class MissingPrivateKey <
|
32
|
-
class MissingCertificate <
|
33
|
-
class InvalidSignature <
|
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
|
-
|
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)
|
data/right_agent.gemspec
CHANGED
@@ -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.
|
29
|
-
spec.date = '2014-05-
|
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'
|
data/spec/agent_spec.rb
CHANGED
@@ -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/,
|
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(
|
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/,
|
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(
|
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"
|
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) }.
|
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) }.
|
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
|
data/spec/dispatcher_spec.rb
CHANGED
@@ -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
|
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
|
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
|
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
|