right_agent 2.2.1-x86-mingw32 → 2.4.3-x86-mingw32
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/README.rdoc +2 -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 +70 -87
- data/lib/right_agent/agent_config.rb +1 -1
- data/lib/right_agent/agent_tag_manager.rb +1 -1
- data/lib/right_agent/clients/api_client.rb +2 -1
- data/lib/right_agent/clients/auth_client.rb +2 -6
- data/lib/right_agent/clients/balanced_http_client.rb +22 -11
- data/lib/right_agent/clients/base_retry_client.rb +14 -22
- data/lib/right_agent/clients/non_blocking_client.rb +1 -0
- data/lib/right_agent/clients/right_http_client.rb +4 -8
- data/lib/right_agent/clients/router_client.rb +10 -16
- data/lib/right_agent/command/command_parser.rb +3 -3
- data/lib/right_agent/command/command_runner.rb +1 -1
- data/lib/right_agent/command/command_serializer.rb +0 -32
- data/lib/right_agent/connectivity_checker.rb +7 -11
- data/lib/right_agent/core_payload_types/dev_repository.rb +32 -0
- data/lib/right_agent/dispatcher.rb +8 -45
- data/lib/right_agent/enrollment_result.rb +2 -2
- data/lib/right_agent/error_tracker.rb +230 -0
- data/lib/right_agent/exceptions.rb +1 -1
- data/lib/right_agent/log.rb +8 -6
- data/lib/right_agent/packets.rb +5 -3
- data/lib/right_agent/pending_requests.rb +10 -4
- data/lib/right_agent/pid_file.rb +3 -3
- data/lib/right_agent/platform.rb +14 -14
- data/lib/right_agent/protocol_version_mixin.rb +6 -3
- data/lib/right_agent/scripts/agent_deployer.rb +13 -1
- data/lib/right_agent/sender.rb +16 -35
- data/lib/right_agent/serialize/secure_serializer.rb +6 -9
- data/lib/right_agent/serialize/serializer.rb +7 -3
- data/right_agent.gemspec +5 -5
- data/spec/agent_spec.rb +5 -5
- data/spec/clients/auth_client_spec.rb +1 -1
- data/spec/clients/balanced_http_client_spec.rb +20 -28
- data/spec/clients/base_retry_client_spec.rb +5 -6
- data/spec/clients/non_blocking_client_spec.rb +4 -0
- data/spec/clients/router_client_spec.rb +1 -4
- data/spec/dispatcher_spec.rb +6 -55
- data/spec/error_tracker_spec.rb +346 -0
- data/spec/log_spec.rb +4 -0
- data/spec/pending_requests_spec.rb +2 -2
- data/spec/sender_spec.rb +3 -3
- data/spec/serialize/serializer_spec.rb +14 -0
- data/spec/spec_helper.rb +4 -2
- metadata +13 -11
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
|
@@ -123,26 +123,26 @@ describe RightScale::BalancedHttpClient do
|
|
123
123
|
end
|
124
124
|
|
125
125
|
it "uses specified request UUID" do
|
126
|
-
@log.should_receive(:info).with("Requesting POST <my uuid> /foo/bar").once
|
126
|
+
@log.should_receive(:info).with("Requesting POST <my uuid> /foo/bar {}").once
|
127
127
|
@log.should_receive(:info).with("Completed <my uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes")
|
128
128
|
@client.request(:post, @path, @params, :request_uuid => "my uuid")
|
129
129
|
end
|
130
130
|
|
131
131
|
it "generates request UUID if none specified" do
|
132
|
-
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
132
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar {}").once
|
133
133
|
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
134
134
|
@client.request(:post, @path)
|
135
135
|
end
|
136
136
|
|
137
137
|
it "logs request before sending it" do
|
138
|
-
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar").once
|
138
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar {}").once
|
139
139
|
flexmock(@http_client).should_receive(:request).and_raise(RuntimeError).once
|
140
140
|
@client.request(:post, @path) rescue nil
|
141
141
|
end
|
142
142
|
|
143
143
|
it "logs using specified :log_level" do
|
144
144
|
@params = {:some => "data"}
|
145
|
-
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar").once
|
145
|
+
@log.should_receive(:debug).with("Requesting POST <random uuid> /foo/bar {:some=>\"data\"}").once
|
146
146
|
@log.should_receive(:debug).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
147
147
|
@client.request(:post, @path, @params, :log_level => :debug)
|
148
148
|
end
|
@@ -170,7 +170,7 @@ describe RightScale::BalancedHttpClient do
|
|
170
170
|
end
|
171
171
|
|
172
172
|
it "logs successful completion of request" do
|
173
|
-
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar")
|
173
|
+
@log.should_receive(:info).with("Requesting POST <random uuid> /foo/bar {}")
|
174
174
|
@log.should_receive(:info).with("Completed <random uuid> in 10ms | 200 [http://my.com/foo/bar] | 11 bytes").once
|
175
175
|
@client.request(:post, @path, @params, @options)
|
176
176
|
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
|
@@ -528,27 +530,10 @@ describe RightScale::BalancedHttpClient do
|
|
528
530
|
end
|
529
531
|
|
530
532
|
context "when no exception" do
|
531
|
-
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
text.should == "/foo/bar"
|
536
|
-
end
|
537
|
-
end
|
538
|
-
|
539
|
-
context "and Log.level is :info with host" do
|
540
|
-
it "generates text containing host and path" do
|
541
|
-
text = @client.send(:log_text, @path, {:value => 123}, [], @url)
|
542
|
-
text.should == "[http://my.com/foo/bar]"
|
543
|
-
end
|
544
|
-
end
|
545
|
-
|
546
|
-
context "and Log.level is :debug" do
|
547
|
-
it "generates text containing containing host, path, and filtered parameters" do
|
548
|
-
@log.should_receive(:level).and_return(:debug)
|
549
|
-
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url)
|
550
|
-
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}]"
|
551
|
-
end
|
533
|
+
it "generates text containing containing host, path, and filtered parameters" do
|
534
|
+
@log.should_receive(:level).and_return(:debug)
|
535
|
+
text = @client.send(:log_text, @path, {:some => "data", :secret => "data"}, ["secret"], @url)
|
536
|
+
text.should == "[http://my.com/foo/bar {:some=>\"data\", :secret=>\"<hidden>\"}]"
|
552
537
|
end
|
553
538
|
end
|
554
539
|
|
@@ -591,6 +576,13 @@ describe RightScale::BalancedHttpClient do
|
|
591
576
|
it "does not filter if params is not a hash" do
|
592
577
|
@client.send(:filter, "params", ["secret"]).should == "params"
|
593
578
|
end
|
579
|
+
|
580
|
+
it "also applies filters to any parameter named :payload or 'payload'" do
|
581
|
+
filter = ["secret", "very_secret"]
|
582
|
+
params = {:some => 1, :payload => {:secret => "data"}, "payload" => {:very_secret => "data"}, :secret => "data"}
|
583
|
+
@client.send(:filter, params, filter).should == {:some => 1, :payload => {:secret => "<hidden>"},
|
584
|
+
"payload" => { :very_secret => "<hidden>"}, :secret => "<hidden>"}
|
585
|
+
end
|
594
586
|
end
|
595
587
|
|
596
588
|
context :split do
|
@@ -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
|
@@ -404,6 +404,10 @@ describe RightScale::NonBlockingClient do
|
|
404
404
|
@client.send(:handle_error, :get, Errno::ETIMEDOUT).should == [408, "Request timeout"]
|
405
405
|
end
|
406
406
|
|
407
|
+
it "converts Errno::ECONNREFUSED to 503 ServiceUnavailable" do
|
408
|
+
@client.send(:handle_error, :get, Errno::ECONNREFUSED).should == [503, "Connection refused"]
|
409
|
+
end
|
410
|
+
|
407
411
|
it "converts error to 500 InternalServerError by default" do
|
408
412
|
@client.send(:handle_error, :get, "failed").should == [500, "failed"]
|
409
413
|
end
|
@@ -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,346 @@
|
|
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
|
+
@options = {
|
44
|
+
:shard_id => @shard_id,
|
45
|
+
:trace_level => @trace_level,
|
46
|
+
:airbrake_endpoint => @endpoint,
|
47
|
+
:airbrake_api_key => @api_key,
|
48
|
+
:filter_params => [:password] }
|
49
|
+
end
|
50
|
+
|
51
|
+
context :init do
|
52
|
+
it "initializes the tracker" do
|
53
|
+
flexmock(@tracker).should_receive(:notify_init).with(@agent_name, @options).once
|
54
|
+
@tracker.init(@agent, @agent_name, @options).should be true
|
55
|
+
@tracker.instance_variable_get(:@agent).should == @agent
|
56
|
+
@tracker.instance_variable_get(:@trace_level).should == @trace_level
|
57
|
+
@tracker.exception_stats.should be_a RightSupport::Stats::Exceptions
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
context :log do
|
62
|
+
before(:each) do
|
63
|
+
@tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
|
64
|
+
end
|
65
|
+
|
66
|
+
context "exception nil" do
|
67
|
+
it "logs description" do
|
68
|
+
@log.should_receive(:error).with("failed").once
|
69
|
+
@tracker.log(self, "failed").should be true
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context "exception string" do
|
74
|
+
it "logs error string" do
|
75
|
+
@log.should_receive(:error).with("failed", "error").once
|
76
|
+
@tracker.log(self, "failed", "error").should be true
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
context "exception" do
|
81
|
+
it "logs exception" do
|
82
|
+
@log.should_receive(:error).with("failed", RuntimeError, :trace).once
|
83
|
+
@tracker.log(self, "failed", RuntimeError.new("error")).should be true
|
84
|
+
end
|
85
|
+
|
86
|
+
it "applies trace level configured for selected exceptions" do
|
87
|
+
@log.should_receive(:error).with("failed", RightScale::BalancedHttpClient::NotResponding, :no_trace).once
|
88
|
+
@tracker.log(self, "failed", RightScale::BalancedHttpClient::NotResponding.new("error")).should be true
|
89
|
+
end
|
90
|
+
|
91
|
+
it "applies specified trace level" do
|
92
|
+
@log.should_receive(:error).with("failed", RuntimeError, :caller).once
|
93
|
+
@tracker.log(self, "failed", RuntimeError.new("error"), nil, :caller).should be true
|
94
|
+
end
|
95
|
+
|
96
|
+
it "tracks exception statistics when component is not a string" do
|
97
|
+
request = RightScale::Request.new("/foo/bar", "payload")
|
98
|
+
@log.should_receive(:error).with("failed", RuntimeError, :trace).once
|
99
|
+
flexmock(@tracker).should_receive(:track).with(self, RuntimeError, request).once
|
100
|
+
@tracker.log(self, "failed", RuntimeError.new("error"), request).should be true
|
101
|
+
end
|
102
|
+
|
103
|
+
it "tracks exception statistics when component is a string" do
|
104
|
+
request = RightScale::Request.new("/foo/bar", "payload")
|
105
|
+
@log.should_receive(:error).once
|
106
|
+
flexmock(@tracker).should_receive(:track).with("test", RuntimeError, request).once
|
107
|
+
@tracker.log("test", "failed", RuntimeError.new("error"), request).should be true
|
108
|
+
end
|
109
|
+
|
110
|
+
it "does not track exception statistics for :no_trace" do
|
111
|
+
@log.should_receive(:error).once
|
112
|
+
flexmock(@tracker).should_receive(:track).never
|
113
|
+
@tracker.log(self, "failed", RuntimeError.new("error"), nil, :no_trace).should be true
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
it "logs error if error logging fails" do
|
118
|
+
@log.should_receive(:error).with("failed", RuntimeError, :trace).once.ordered
|
119
|
+
@log.should_receive(:error).with("Failed to log error", RuntimeError, :trace).once.ordered
|
120
|
+
flexmock(@tracker).should_receive(:track).and_raise(RuntimeError).once
|
121
|
+
@tracker.log(self, "failed", RuntimeError.new("error")).should be false
|
122
|
+
end
|
123
|
+
|
124
|
+
it "does not raise exception even if exception logging fails" do
|
125
|
+
@log.should_receive(:error).and_raise(RuntimeError).twice
|
126
|
+
@tracker.log(self, "failed").should be false
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
context :track do
|
131
|
+
before(:each) do
|
132
|
+
@now = Time.at(1000000)
|
133
|
+
flexmock(Time).should_receive(:now).and_return(@now)
|
134
|
+
@exception = RuntimeError.new("error")
|
135
|
+
@tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "records exception in stats when component is an object" do
|
139
|
+
@tracker.track(@agent, @exception).should be true
|
140
|
+
@tracker.exception_stats.all.should == {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1, "when" => 1000000,
|
141
|
+
"type" => "RuntimeError", "message" => "error", "where" => nil}]}}
|
142
|
+
end
|
143
|
+
|
144
|
+
it "records exception in stats when component is a string" do
|
145
|
+
request = RightScale::Request.new("/foo/bar", "payload")
|
146
|
+
@tracker.track("test", @exception, request).should be true
|
147
|
+
@tracker.exception_stats.all.should == {"test" => {"total" => 1, "recent" => [{"count" => 1, "when" => 1000000,
|
148
|
+
"type" => "RuntimeError", "message" => "error", "where" => nil}]}}
|
149
|
+
end
|
150
|
+
|
151
|
+
it "only tracks if stats container exists" do
|
152
|
+
@tracker.instance_variable_set(:@exception_stats, nil)
|
153
|
+
request = RightScale::Request.new("/foo/bar", "payload")
|
154
|
+
@tracker.track("test", @exception, request).should be true
|
155
|
+
@tracker.exception_stats.should be nil
|
156
|
+
end
|
157
|
+
end
|
158
|
+
|
159
|
+
context :notify do
|
160
|
+
before(:each) do
|
161
|
+
@exception = RuntimeError.new("error")
|
162
|
+
@tracker.init(@agent, @agent_name, @options)
|
163
|
+
@cgi_data = @tracker.instance_variable_get(:@cgi_data)
|
164
|
+
end
|
165
|
+
|
166
|
+
it "sends notification using HydraulicBrake" do
|
167
|
+
@brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
|
168
|
+
a[:error_class] == "RuntimeError" &&
|
169
|
+
a[:backtrace].nil? &&
|
170
|
+
["test", "development"].include?(a[:environment_name]) &&
|
171
|
+
a[:cgi_data].should == @cgi_data &&
|
172
|
+
a.keys & [:component, :action, :parameters, :session_data] == [] }).once
|
173
|
+
@tracker.notify(@exception).should be true
|
174
|
+
end
|
175
|
+
|
176
|
+
it "includes packet data in notification" do
|
177
|
+
request = RightScale::Request.new("/foo/bar", {:pay => "load"}, :token => "token")
|
178
|
+
@brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
|
179
|
+
a[:error_class] == "RuntimeError" &&
|
180
|
+
a[:backtrace].nil? &&
|
181
|
+
a[:action] == "bar" &&
|
182
|
+
a[:parameters] == {:pay => "load"} &&
|
183
|
+
["test", "development"].include?(a[:environment_name]) &&
|
184
|
+
a[:cgi_data] == @cgi_data &&
|
185
|
+
a[:session_data] == {:uuid => "token"} }).once
|
186
|
+
@tracker.notify(@exception, request).should be true
|
187
|
+
end
|
188
|
+
|
189
|
+
it "includes event data in notification" do
|
190
|
+
@brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
|
191
|
+
a[:error_class] == "RuntimeError" &&
|
192
|
+
a[:backtrace].nil? &&
|
193
|
+
a[:action] == "bar" &&
|
194
|
+
a[:parameters] == {:pay => "load"} &&
|
195
|
+
["test", "development"].include?(a[:environment_name]) &&
|
196
|
+
a[:cgi_data] == @cgi_data &&
|
197
|
+
a[:session_data] == {:uuid => "token"} }).once
|
198
|
+
@tracker.notify(@exception, {"uuid" => "token", "path" => "/foo/bar", "data" => {:pay => "load"}}).should be true
|
199
|
+
end
|
200
|
+
|
201
|
+
it "adds agent class to :cgi_data in notification" do
|
202
|
+
@brake.should_receive(:notify).with(on { |a| a[:cgi_data] == @cgi_data.merge(:agent_class => "AgentMock") }).once
|
203
|
+
@tracker.notify(@exception, packet = nil, @agent).should be true
|
204
|
+
end
|
205
|
+
|
206
|
+
it "adds component to notification" do
|
207
|
+
@brake.should_receive(:notify).with(on { |a| a[:component] == "component" }).once
|
208
|
+
@tracker.notify(@exception, packet = nil, agent = nil, "component").should be true
|
209
|
+
end
|
210
|
+
|
211
|
+
it "converts non-nil, non-hash payload in packet to a hash" do
|
212
|
+
request = RightScale::Request.new("/foo/bar", "payload", :token => "token")
|
213
|
+
@brake.should_receive(:notify).with(on { |a| a[:parameters] == {:param => "payload"} }).once
|
214
|
+
@tracker.notify(@exception, request).should be true
|
215
|
+
end
|
216
|
+
|
217
|
+
it "converts non-nil, non-hash data in event to a hash" do
|
218
|
+
@brake.should_receive(:notify).with(on { |a| a[:parameters] == {:param => "payload"} }).once
|
219
|
+
@tracker.notify(@exception, {"uuid" => "token", "path" => "/foo/bar", "data" => "payload"}).should be true
|
220
|
+
end
|
221
|
+
|
222
|
+
it "omits :parameters from notification if payload in packet is nil" do
|
223
|
+
request = RightScale::Request.new("/foo/bar", nil, :token => "token")
|
224
|
+
@brake.should_receive(:notify).with(on { |a| !a.has_key?(:parameters) }).once
|
225
|
+
@tracker.notify(@exception, request).should be true
|
226
|
+
end
|
227
|
+
|
228
|
+
it "omits :parameters from notification if data in packet is nil" do
|
229
|
+
@brake.should_receive(:notify).with(on { |a| !a.has_key?(:parameters) }).once
|
230
|
+
@tracker.notify(@exception, {"uuid" => "token", "path" => "/foo/bar", "data" => nil}).should be true
|
231
|
+
end
|
232
|
+
|
233
|
+
it "functions even if cgi_data has not been initialized by notify_init" do
|
234
|
+
@tracker.instance_variable_set(:@cgi_data, nil)
|
235
|
+
@brake.should_receive(:notify).with(on { |a| a[:error_message] == "error" &&
|
236
|
+
a[:error_class] == "RuntimeError" &&
|
237
|
+
a[:backtrace].nil? &&
|
238
|
+
["test", "development"].include?(a[:environment_name]) &&
|
239
|
+
a.keys & [:cgi_data, :component, :action, :parameters, :session_data] == [] }).once
|
240
|
+
@tracker.notify(@exception).should be true
|
241
|
+
end
|
242
|
+
|
243
|
+
it "does nothing if notify is disabled" do
|
244
|
+
@tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
|
245
|
+
@brake.should_receive(:notify).never
|
246
|
+
@tracker.notify(@exception).should be true
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
context :notify_callback do
|
251
|
+
it "returns proc that calls notify" do
|
252
|
+
@tracker.init(@agent, @agent_name, @options)
|
253
|
+
flexmock(@tracker).should_receive(:notify).with("exception", "packet", "agent", "component").once
|
254
|
+
@tracker.notify_callback.call("exception", "packet", "agent", "component")
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
context :stats do
|
259
|
+
before(:each) do
|
260
|
+
@now = Time.at(1000000)
|
261
|
+
flexmock(Time).should_receive(:now).and_return(@now)
|
262
|
+
@exception = RuntimeError.new("error")
|
263
|
+
@tracker.init(@agent, @agent_name, :trace_level => @trace_level, :shard_id => @shard_id)
|
264
|
+
end
|
265
|
+
|
266
|
+
it "returns exception stats" do
|
267
|
+
@tracker.track(@agent, @exception).should be true
|
268
|
+
@tracker.stats.should == {"exceptions" => {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1,
|
269
|
+
"when" => 1000000, "type" => "RuntimeError", "message" => "error", "where" => nil}]}}}
|
270
|
+
end
|
271
|
+
|
272
|
+
it "returns no exception stats if stats container not initialized" do
|
273
|
+
@tracker.instance_variable_set(:@exception_stats, nil)
|
274
|
+
@tracker.track(@agent, @exception).should be true
|
275
|
+
@tracker.stats.should == {"exceptions" => nil}
|
276
|
+
end
|
277
|
+
|
278
|
+
it "resets stats after collecting current stats" do
|
279
|
+
@tracker.track(@agent, @exception).should be true
|
280
|
+
@tracker.stats(true).should == {"exceptions" => {"agent_mock" => {"total" => 1, "recent" => [{"count" => 1,
|
281
|
+
"when" => 1000000, "type" => "RuntimeError", "message" => "error", "where" => nil}]}}}
|
282
|
+
@tracker.stats.should == {"exceptions" => nil}
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
context :notify_init do
|
287
|
+
class ConfigMock
|
288
|
+
attr_accessor :secure, :host, :port, :api_key, :project_root
|
289
|
+
end
|
290
|
+
|
291
|
+
it "does not initialize if Airbrake endpoint or API key is undefined" do
|
292
|
+
@brake.should_receive(:configure).never
|
293
|
+
@tracker.send(:notify_init, @agent_name, @options.merge(:airbrake_endpoint => nil))
|
294
|
+
@tracker.instance_variable_get(:@notify_enabled).should be false
|
295
|
+
@tracker.send(:notify_init, @agent_name, @options.merge(:airbrake_api_key => nil))
|
296
|
+
@tracker.instance_variable_get(:@notify_enabled).should be false
|
297
|
+
end
|
298
|
+
|
299
|
+
it "initializes cgi data and configures HydraulicBrake" do
|
300
|
+
config = ConfigMock.new
|
301
|
+
@brake.should_receive(:configure).and_yield(config).once
|
302
|
+
@tracker.send(:notify_init, @agent_name, @options).should be true
|
303
|
+
cgi_data = @tracker.instance_variable_get(:@cgi_data)
|
304
|
+
cgi_data[:agent_name].should == @agent_name
|
305
|
+
cgi_data[:pid].should be_a Integer
|
306
|
+
cgi_data[:process].should be_a String
|
307
|
+
cgi_data[:shard_id].should == @shard_id
|
308
|
+
config.secure.should be true
|
309
|
+
config.host.should == "airbrake.com"
|
310
|
+
config.port.should == 443
|
311
|
+
config.api_key.should == @api_key
|
312
|
+
config.project_root.should be_a String
|
313
|
+
@tracker.instance_variable_get(:@notify_enabled).should be true
|
314
|
+
@tracker.instance_variable_get(:@filter_params).should == ["password"]
|
315
|
+
end
|
316
|
+
|
317
|
+
it "raises exception if hydraulic_gem is not available" do
|
318
|
+
flexmock(@tracker).should_receive(:require_succeeds?).with("hydraulic_brake").and_return(false)
|
319
|
+
lambda do
|
320
|
+
@tracker.send(:notify_init, @agent_name, @options)
|
321
|
+
end.should raise_error(RuntimeError, /hydraulic_brake gem missing/)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
context :filter do
|
326
|
+
|
327
|
+
before(:each) do
|
328
|
+
@params = {:user => "me", :password => "secret"}
|
329
|
+
end
|
330
|
+
|
331
|
+
it "applies filters" do
|
332
|
+
@tracker.init(@agent, @agent_name, @options)
|
333
|
+
@tracker.send(:filter, @params).should == {:user => "me", :password => "<hidden>"}
|
334
|
+
end
|
335
|
+
|
336
|
+
it "converts parameter names to string form before comparison" do
|
337
|
+
@tracker.init(@agent, @agent_name, @options)
|
338
|
+
@tracker.send(:filter, :user => "me", "password" => "secret").should == {:user => "me", "password" => "<hidden>"}
|
339
|
+
end
|
340
|
+
|
341
|
+
it "does not filter if no filters are specified" do
|
342
|
+
@tracker.init(@agent, @agent_name, @options.merge(:filter_params => nil))
|
343
|
+
@tracker.send(:filter, @params).should == @params
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|