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.
Files changed (48) hide show
  1. data/README.rdoc +2 -0
  2. data/lib/right_agent.rb +1 -0
  3. data/lib/right_agent/actor.rb +0 -28
  4. data/lib/right_agent/actors/agent_manager.rb +20 -18
  5. data/lib/right_agent/agent.rb +70 -87
  6. data/lib/right_agent/agent_config.rb +1 -1
  7. data/lib/right_agent/agent_tag_manager.rb +1 -1
  8. data/lib/right_agent/clients/api_client.rb +2 -1
  9. data/lib/right_agent/clients/auth_client.rb +2 -6
  10. data/lib/right_agent/clients/balanced_http_client.rb +22 -11
  11. data/lib/right_agent/clients/base_retry_client.rb +14 -22
  12. data/lib/right_agent/clients/non_blocking_client.rb +1 -0
  13. data/lib/right_agent/clients/right_http_client.rb +4 -8
  14. data/lib/right_agent/clients/router_client.rb +10 -16
  15. data/lib/right_agent/command/command_parser.rb +3 -3
  16. data/lib/right_agent/command/command_runner.rb +1 -1
  17. data/lib/right_agent/command/command_serializer.rb +0 -32
  18. data/lib/right_agent/connectivity_checker.rb +7 -11
  19. data/lib/right_agent/core_payload_types/dev_repository.rb +32 -0
  20. data/lib/right_agent/dispatcher.rb +8 -45
  21. data/lib/right_agent/enrollment_result.rb +2 -2
  22. data/lib/right_agent/error_tracker.rb +230 -0
  23. data/lib/right_agent/exceptions.rb +1 -1
  24. data/lib/right_agent/log.rb +8 -6
  25. data/lib/right_agent/packets.rb +5 -3
  26. data/lib/right_agent/pending_requests.rb +10 -4
  27. data/lib/right_agent/pid_file.rb +3 -3
  28. data/lib/right_agent/platform.rb +14 -14
  29. data/lib/right_agent/protocol_version_mixin.rb +6 -3
  30. data/lib/right_agent/scripts/agent_deployer.rb +13 -1
  31. data/lib/right_agent/sender.rb +16 -35
  32. data/lib/right_agent/serialize/secure_serializer.rb +6 -9
  33. data/lib/right_agent/serialize/serializer.rb +7 -3
  34. data/right_agent.gemspec +5 -5
  35. data/spec/agent_spec.rb +5 -5
  36. data/spec/clients/auth_client_spec.rb +1 -1
  37. data/spec/clients/balanced_http_client_spec.rb +20 -28
  38. data/spec/clients/base_retry_client_spec.rb +5 -6
  39. data/spec/clients/non_blocking_client_spec.rb +4 -0
  40. data/spec/clients/router_client_spec.rb +1 -4
  41. data/spec/dispatcher_spec.rb +6 -55
  42. data/spec/error_tracker_spec.rb +346 -0
  43. data/spec/log_spec.rb +4 -0
  44. data/spec/pending_requests_spec.rb +2 -2
  45. data/spec/sender_spec.rb +3 -3
  46. data/spec/serialize/serializer_spec.rb +14 -0
  47. data/spec/spec_helper.rb +4 -2
  48. metadata +13 -11
@@ -493,10 +493,10 @@ describe RightScale::Agent do
493
493
  it "should ack if request dispatch fails" do
494
494
  run_in_em do
495
495
  request = RightScale::Request.new("/foo/bar", "payload")
496
- @log.should_receive(:error).with(/Failed to dispatch request/, Exception, :trace).once
496
+ @log.should_receive(:error).with(/Failed to dispatch request/, StandardError, :trace).once
497
497
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
498
498
  and_return(@broker_ids).and_yield(@broker_id, request, @header).once
499
- @dispatcher.should_receive(:dispatch).and_raise(Exception)
499
+ @dispatcher.should_receive(:dispatch).and_raise(StandardError)
500
500
  @header.should_receive(:ack).once
501
501
  @agent.run
502
502
  end
@@ -505,10 +505,10 @@ describe RightScale::Agent do
505
505
  it "should ack if response delivery fails" do
506
506
  run_in_em do
507
507
  result = RightScale::Result.new("token", "to", "results", "from")
508
- @log.should_receive(:error).with(/Failed to deliver response/, Exception, :trace).once
508
+ @log.should_receive(:error).with(/Failed to deliver response/, StandardError, :trace).once
509
509
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
510
510
  and_return(@broker_ids).and_yield(@broker_id, result, @header).once
511
- @sender.should_receive(:handle_response).and_raise(Exception)
511
+ @sender.should_receive(:handle_response).and_raise(StandardError)
512
512
  @header.should_receive(:ack).once
513
513
  @agent.run
514
514
  end
@@ -528,7 +528,7 @@ describe RightScale::Agent do
528
528
  run_in_em do
529
529
  @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
530
530
  @broker.should_receive(:nil?).and_return(true)
531
- @log.should_receive(:error).with("[stop] Terminating because just because", nil, :trace).once
531
+ @log.should_receive(:error).with("[stop] Terminating because just because").once
532
532
  @agent.terminate("just because")
533
533
  end
534
534
  end
@@ -269,7 +269,7 @@ describe RightScale::AuthClient do
269
269
  end
270
270
 
271
271
  it "log error if callback fails" do
272
- @log.should_receive(:error).with("Failed status callback", StandardError).once
272
+ @log.should_receive(:error).with("Failed status callback", StandardError, :caller).once
273
273
  @client.status { |t, s| raise StandardError, "test" }
274
274
  @client.send(:state=, state).should == state
275
275
  end
@@ -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) }.should raise_error(bad_request)
415
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.
416
+ should raise_error(RightScale::HttpExceptions::BadRequest)
416
417
  @yielded.should == bad_request
417
418
  end
418
419
 
@@ -458,7 +459,8 @@ describe RightScale::BalancedHttpClient do
458
459
  it "raises last exception in details if not retryable" do
459
460
  bad_request = RightScale::HttpExceptions.create(400, "bad data")
460
461
  @no_result = RightSupport::Net::NoResult.new("no result", {@url => bad_request})
461
- lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.should raise_error(bad_request)
462
+ lambda { @client.send(:handle_no_result, @no_result, @url, &@proc) }.
463
+ should raise_error(RightScale::HttpExceptions::BadRequest)
462
464
  @yielded.should == bad_request
463
465
  end
464
466
  end
@@ -528,27 +530,10 @@ describe RightScale::BalancedHttpClient do
528
530
  end
529
531
 
530
532
  context "when no exception" do
531
-
532
- context "and Log.level is :info with no host" do
533
- it "generates text containing path" do
534
- text = @client.send(:log_text, @path, {:value => 123}, [])
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
@@ -26,7 +26,6 @@ class Foo
26
26
  include RightScale::Actor
27
27
  expose_idempotent :bar, :index, :i_kill_you
28
28
  expose_non_idempotent :bar_non
29
- on_exception :handle_exception
30
29
 
31
30
  def index(payload)
32
31
  bar(payload)
@@ -47,34 +46,17 @@ class Foo
47
46
  def i_kill_you(payload)
48
47
  raise RuntimeError.new('I kill you!')
49
48
  end
50
-
51
- def handle_exception(method, deliverable, error)
52
- end
53
49
  end
54
50
 
55
51
  class Bar
56
52
  include RightScale::Actor
57
53
  expose :i_kill_you
58
- on_exception do |method, deliverable, error|
59
- @scope = self
60
- @called_with = [method, deliverable, error]
61
- end
62
54
 
63
55
  def i_kill_you(payload)
64
56
  raise RuntimeError.new('I kill you!')
65
57
  end
66
58
  end
67
59
 
68
- # No specs, simply ensures multiple methods for assigning on_exception callback,
69
- # on_exception raises exception when called with an invalid argument.
70
- class Doomed
71
- include RightScale::Actor
72
- on_exception do
73
- end
74
- on_exception lambda {}
75
- on_exception :doh
76
- end
77
-
78
60
  describe "RightScale::Dispatcher" do
79
61
 
80
62
  include FlexMock::ArgumentTypes
@@ -89,7 +71,7 @@ describe "RightScale::Dispatcher" do
89
71
  @registry = RightScale::ActorRegistry.new
90
72
  @registry.register(@actor, nil)
91
73
  @agent_id = "rs-agent-1-1"
92
- @agent = flexmock("Agent", :identity => @agent_id, :registry => @registry, :exception_callback => nil).by_default
74
+ @agent = flexmock("Agent", :identity => @agent_id, :registry => @registry).by_default
93
75
  @cache = RightScale::DispatchedCache.new(@agent_id)
94
76
  @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
95
77
  end
@@ -157,41 +139,17 @@ describe "RightScale::Dispatcher" do
157
139
  lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::InvalidRequestType)
158
140
  end
159
141
 
160
- it "should call the on_exception callback if something goes wrong" do
161
- @log.should_receive(:error).once
142
+ it "should log exception if dispatch fails" do
143
+ @log.should_receive(:error).with(/Failed dispatching/, RuntimeError, :trace).once
162
144
  req = RightScale::Request.new('/foo/i_kill_you', nil)
163
- flexmock(@actor).should_receive(:handle_exception).with(:i_kill_you, req, Exception).once
164
- res = @dispatcher.dispatch(req)
165
- res.results.error?.should be_true
166
- (res.results.content =~ /Could not handle \/foo\/i_kill_you request/).should be_true
167
- end
168
-
169
- it "should call on_exception Procs defined in a subclass with the correct arguments" do
170
- @log.should_receive(:error).once
171
- actor = Bar.new
172
- @registry.register(actor, nil)
173
- req = RightScale::Request.new('/bar/i_kill_you', nil)
174
145
  @dispatcher.dispatch(req)
175
- called_with = actor.instance_variable_get("@called_with")
176
- called_with[0].should == :i_kill_you
177
- called_with[1].should == req
178
- called_with[2].should be_kind_of(RuntimeError)
179
- called_with[2].message.should == 'I kill you!'
180
146
  end
181
147
 
182
- it "should call on_exception Procs defined in a subclass in the scope of the actor" do
148
+ it "should return error result if dispatch fails" do
183
149
  @log.should_receive(:error).once
184
- actor = Bar.new
185
- @registry.register(actor, nil)
186
- req = RightScale::Request.new('/bar/i_kill_you', nil)
187
- @dispatcher.dispatch(req)
188
- actor.instance_variable_get("@scope").should == actor
189
- end
190
-
191
- it "should log error if dispatch fails" do
192
- RightScale::Log.should_receive(:error).once
193
150
  req = RightScale::Request.new('/foo/i_kill_you', nil)
194
- @dispatcher.dispatch(req)
151
+ res = @dispatcher.dispatch(req)
152
+ res.results.error?.should be_true
195
153
  end
196
154
 
197
155
  it "should reject requests whose time-to-live has expired" do
@@ -312,13 +270,6 @@ describe "RightScale::Dispatcher" do
312
270
  end
313
271
  end
314
272
 
315
- it "should return error result if dispatch fails" do
316
- @log.should_receive(:error).with(/Could not handle/, Exception, :trace).once
317
- req = RightScale::Request.new('/foo/i_kill_you', nil)
318
- res = @dispatcher.dispatch(req)
319
- res.results.error?.should be_true
320
- end
321
-
322
273
  end
323
274
 
324
275
  end # RightScale::Dispatcher
@@ -0,0 +1,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