right_agent 2.2.1-x86-mingw32 → 2.4.3-x86-mingw32
Sign up to get free protection for your applications and to get access to all the features.
- 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
|