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.
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