right_agent 0.10.13 → 0.13.5

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 (39) hide show
  1. data/lib/right_agent.rb +2 -0
  2. data/lib/right_agent/actor.rb +45 -10
  3. data/lib/right_agent/actor_registry.rb +5 -5
  4. data/lib/right_agent/actors/agent_manager.rb +4 -4
  5. data/lib/right_agent/agent.rb +97 -37
  6. data/lib/right_agent/agent_tag_manager.rb +1 -2
  7. data/lib/right_agent/command/command_io.rb +1 -3
  8. data/lib/right_agent/command/command_runner.rb +9 -3
  9. data/lib/right_agent/dispatched_cache.rb +110 -0
  10. data/lib/right_agent/dispatcher.rb +119 -180
  11. data/lib/right_agent/history.rb +136 -0
  12. data/lib/right_agent/log.rb +6 -3
  13. data/lib/right_agent/monkey_patches/ruby_patch.rb +0 -1
  14. data/lib/right_agent/pid_file.rb +1 -1
  15. data/lib/right_agent/platform.rb +2 -2
  16. data/lib/right_agent/platform/linux.rb +8 -1
  17. data/lib/right_agent/platform/windows.rb +1 -1
  18. data/lib/right_agent/sender.rb +57 -41
  19. data/right_agent.gemspec +4 -4
  20. data/spec/actor_registry_spec.rb +7 -8
  21. data/spec/actor_spec.rb +87 -24
  22. data/spec/agent_spec.rb +107 -8
  23. data/spec/command/command_runner_spec.rb +12 -1
  24. data/spec/dispatched_cache_spec.rb +142 -0
  25. data/spec/dispatcher_spec.rb +110 -129
  26. data/spec/history_spec.rb +234 -0
  27. data/spec/idempotent_request_spec.rb +1 -1
  28. data/spec/log_spec.rb +15 -0
  29. data/spec/operation_result_spec.rb +4 -2
  30. data/spec/platform/darwin_spec.rb +13 -0
  31. data/spec/platform/linux_spec.rb +38 -0
  32. data/spec/platform/platform_spec.rb +46 -51
  33. data/spec/platform/windows_spec.rb +13 -0
  34. data/spec/sender_spec.rb +81 -38
  35. metadata +12 -9
  36. data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +0 -45
  37. data/spec/platform/darwin.rb +0 -11
  38. data/spec/platform/linux.rb +0 -23
  39. data/spec/platform/windows.rb +0 -11
data/spec/agent_spec.rb CHANGED
@@ -50,6 +50,9 @@ describe RightScale::Agent do
50
50
  :non_delivery => true).by_default
51
51
  @broker.should_receive(:connection_status).and_yield(:connected)
52
52
  flexmock(RightAMQP::HABrokerClient).should_receive(:new).and_return(@broker)
53
+ @history = flexmock("history")
54
+ @history.should_receive(:update).and_return(true).by_default
55
+ flexmock(RightScale::History).should_receive(:new).and_return(@history)
53
56
  flexmock(RightScale::PidFile).should_receive(:new).
54
57
  and_return(flexmock("pid file", :check=>true, :write=>true, :remove=>true))
55
58
  @identity = "rs-instance-123-1"
@@ -102,9 +105,9 @@ describe RightScale::Agent do
102
105
  @agent.options[:root_dir].should == Dir.pwd
103
106
  end
104
107
 
105
- it "for heartbeat is 60" do
108
+ it "for heartbeat is 0" do
106
109
  @agent.options.should include(:heartbeat)
107
- @agent.options[:heartbeat].should == 60
110
+ @agent.options[:heartbeat].should == 0
108
111
  end
109
112
 
110
113
  end
@@ -139,6 +142,9 @@ describe RightScale::Agent do
139
142
  :non_delivery => true).by_default
140
143
  @broker.should_receive(:connection_status).and_yield(:connected)
141
144
  flexmock(RightAMQP::HABrokerClient).should_receive(:new).and_return(@broker)
145
+ @history = flexmock("history")
146
+ @history.should_receive(:update).and_return(true).by_default
147
+ flexmock(RightScale::History).should_receive(:new).and_return(@history)
142
148
  flexmock(RightScale::PidFile).should_receive(:new).
143
149
  and_return(flexmock("pid file", :check=>true, :write=>true, :remove=>true))
144
150
  @identity = "rs-instance-123-1"
@@ -271,6 +277,12 @@ describe RightScale::Agent do
271
277
  flexmock(RightAMQP::HABrokerClient).should_receive(:new).and_return(@broker)
272
278
  flexmock(RightScale::PidFile).should_receive(:new).
273
279
  and_return(flexmock("pid file", :check=>true, :write=>true, :remove=>true))
280
+ @history = flexmock("history")
281
+ @history.should_receive(:update).and_return(true).by_default
282
+ @history.should_receive(:analyze_service).and_return({}).by_default
283
+ flexmock(RightScale::History).should_receive(:new).and_return(@history)
284
+ @header = flexmock("amqp header")
285
+ @header.should_receive(:ack).by_default
274
286
  @sender = flexmock("sender", :pending_requests => [], :request_age => nil,
275
287
  :message_received => true, :stats => "").by_default
276
288
  @sender.should_receive(:terminate).and_return([0, 0]).by_default
@@ -330,6 +342,13 @@ describe RightScale::Agent do
330
342
  end
331
343
  end
332
344
 
345
+ it "should update history with start and then run when agent is for service" do
346
+ @history.should_receive(:update).with("start").and_return(true).ordered.once
347
+ flexmock(@agent).should_receive(:setup_queues).ordered.once
348
+ @history.should_receive(:update).with("run").and_return(true).ordered.once
349
+ @agent.run
350
+ end
351
+
333
352
  it "should log error if fail to connect to broker" do
334
353
  run_in_em do
335
354
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, hsh(:brokers => nil), Proc).
@@ -408,8 +427,8 @@ describe RightScale::Agent do
408
427
  run_in_em do
409
428
  request = RightScale::Request.new("/foo/bar", "payload")
410
429
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
411
- and_return(@broker_ids).and_yield(@broker_id, request).once
412
- @dispatcher.should_receive(:dispatch).with(request).once
430
+ and_return(@broker_ids).and_yield(@broker_id, request, @header).once
431
+ @dispatcher.should_receive(:dispatch).with(request, @header).once
413
432
  @agent.run
414
433
  end
415
434
  end
@@ -418,8 +437,8 @@ describe RightScale::Agent do
418
437
  run_in_em do
419
438
  result = RightScale::Result.new("token", "to", "results", "from")
420
439
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
421
- and_return(@broker_ids).and_yield(@broker_id, result).once
422
- @sender.should_receive(:handle_response).with(result).once
440
+ and_return(@broker_ids).and_yield(@broker_id, result, @header).once
441
+ @sender.should_receive(:handle_response).with(result, @header).once
423
442
  @agent.run
424
443
  end
425
444
  end
@@ -428,13 +447,34 @@ describe RightScale::Agent do
428
447
  run_in_em do
429
448
  result = RightScale::Result.new("token", "to", "results", "from")
430
449
  @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
431
- and_return(@broker_ids).and_yield(@broker_id, result).once
432
- @sender.should_receive(:handle_response).with(result).once
450
+ and_return(@broker_ids).and_yield(@broker_id, result, @header).once
451
+ @sender.should_receive(:handle_response).with(result, @header).once
433
452
  @sender.should_receive(:message_received).once
434
453
  @agent.run
435
454
  end
436
455
  end
437
456
 
457
+ it "should ignore and ack unrecognized messages" do
458
+ run_in_em do
459
+ request = RightScale::Stats.new(nil, nil)
460
+ @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
461
+ and_return(@broker_ids).and_yield(@broker_id, request, @header).once
462
+ @dispatcher.should_receive(:dispatch).never
463
+ @header.should_receive(:ack).once
464
+ @agent.run
465
+ end
466
+ end
467
+
468
+ it "should ignore unrecognized messages and not attempt to ack if there is no header" do
469
+ run_in_em do
470
+ request = RightScale::Stats.new(nil, nil)
471
+ @broker.should_receive(:subscribe).with(hsh(:name => @identity), nil, Hash, Proc).
472
+ and_return(@broker_ids).and_yield(@broker_id, request, nil).once
473
+ @dispatcher.should_receive(:dispatch).never
474
+ @agent.run
475
+ end
476
+ end
477
+
438
478
  end
439
479
 
440
480
  describe "Tuning heartbeat" do
@@ -571,6 +611,24 @@ describe RightScale::Agent do
571
611
 
572
612
  describe "Terminating" do
573
613
 
614
+ it "should log error for abnormal termination" do
615
+ run_in_em do
616
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
617
+ @broker.should_receive(:nil?).and_return(true)
618
+ @log.should_receive(:error).with("[stop] Terminating because just because", nil, :trace).once
619
+ @agent.terminate("just because")
620
+ end
621
+ end
622
+
623
+ it "should log error plus exception for abnormal termination" do
624
+ run_in_em do
625
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
626
+ @broker.should_receive(:nil?).and_return(true)
627
+ @log.should_receive(:error).with(/Terminating because just because/, Exception, :trace).once
628
+ @agent.terminate("just because", Exception.new("error"))
629
+ end
630
+ end
631
+
574
632
  it "should close unusable broker connections at start of termination" do
575
633
  @broker.should_receive(:unusable).and_return(["rs-broker-123-1"]).once
576
634
  @broker.should_receive(:close_one).with("rs-broker-123-1", false).once
@@ -694,6 +752,14 @@ describe RightScale::Agent do
694
752
  end
695
753
  end
696
754
 
755
+ it "should terminate immediately if broker not initialized" do
756
+ run_in_em do
757
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
758
+ @log.should_receive(:info).with("[stop] Terminating immediately").once
759
+ @agent.terminate
760
+ end
761
+ end
762
+
697
763
  it "should terminate immediately if called a second time but should still execute block" do
698
764
  @sender.should_receive(:terminate).and_return([1, 10]).once
699
765
  @dispatcher.should_receive(:dispatch_age).and_return(10).once
@@ -712,6 +778,39 @@ describe RightScale::Agent do
712
778
  end
713
779
  end
714
780
 
781
+ it "should update history with stop and graceful exit if broker not initialized" do
782
+ run_in_em do
783
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
784
+ @log.should_receive(:error).once
785
+ @history.should_receive(:update).with("stop").and_return(true).ordered.once
786
+ @history.should_receive(:update).with("graceful exit").and_return(true).ordered.once
787
+ @agent.terminate("just because")
788
+ end
789
+ end
790
+
791
+ it "should update history with stop but not graceful exit if called a second time to terminate immediately" do
792
+ run_in_em do
793
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
794
+ flexmock(@agent).should_receive(:load_actors).and_return(true)
795
+ @agent.run
796
+ @agent.instance_variable_set(:@terminating, true)
797
+ @history.should_receive(:update).with("stop").and_return(true).once
798
+ @history.should_receive(:update).with("graceful exit").never
799
+ @agent.terminate
800
+ end
801
+ end
802
+
803
+ it "should update history with stop and graceful exit if gracefully terminate" do
804
+ run_in_em do
805
+ @agent = RightScale::Agent.new(:user => "me", :identity => @identity)
806
+ flexmock(@agent).should_receive(:load_actors).and_return(true)
807
+ @agent.run
808
+ @history.should_receive(:update).with("stop").and_return(true).ordered.once
809
+ @history.should_receive(:update).with("graceful exit").and_return(true).ordered.once
810
+ @agent.terminate
811
+ end
812
+ end
813
+
715
814
  end
716
815
 
717
816
  end
@@ -26,7 +26,7 @@ module RightScale
26
26
 
27
27
  class CommandIOMock < CommandIO
28
28
 
29
- include Singleton
29
+ include RightSupport::Ruby::EasySingleton
30
30
 
31
31
  def trigger_listen(payload)
32
32
  @test_callback.call(payload)
@@ -93,4 +93,15 @@ describe RightScale::CommandRunner do
93
93
  @opt.should == payload
94
94
  end
95
95
 
96
+ it 'should run commands using fiber pool if provided' do
97
+ commands = { :test => lambda { |opt, _| @opt = opt } }
98
+ fiber_pool = flexmock("fiber pool")
99
+ fiber_pool.should_receive(:spawn).and_return(true).and_yield.once
100
+ flexmock(RightScale::CommandIO).should_receive(:instance).and_return(RightScale::CommandIOMock.instance)
101
+ cmd_options = RightScale::CommandRunner.start(@socket_port, RightScale::AgentIdentity.generate, commands, fiber_pool)
102
+ payload = @command_payload.merge(cmd_options)
103
+ RightScale::CommandIOMock.instance.trigger_listen(payload)
104
+ @opt.should == payload
105
+ end
106
+
96
107
  end
@@ -0,0 +1,142 @@
1
+ #
2
+ # Copyright (c) 2012 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 File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
24
+
25
+ describe "RightScale::DispatchedCache" do
26
+
27
+ include FlexMock::ArgumentTypes
28
+
29
+ before(:each) do
30
+ flexmock(RightScale::Log).should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
31
+ flexmock(RightScale::Log).should_receive(:info).by_default
32
+ @now = Time.at(1000000)
33
+ flexmock(Time).should_receive(:now).and_return(@now).by_default
34
+ @agent_id = "rs-agent-1-1"
35
+ @cache = RightScale::DispatchedCache.new(@agent_id)
36
+ @token1 = "token1"
37
+ @token2 = "token2"
38
+ @token3 = "token3"
39
+ end
40
+
41
+ context "initialize" do
42
+
43
+ it "should initialize cache" do
44
+ @cache.instance_variable_get(:@cache).should == {}
45
+ @cache.instance_variable_get(:@lru).should == []
46
+ end
47
+
48
+ it "should initialize agent identity" do
49
+ @cache.instance_variable_get(:@identity).should == @agent_id
50
+ end
51
+
52
+ end
53
+
54
+ context "store" do
55
+
56
+ it "should store request token" do
57
+ @cache.store(@token1, nil)
58
+ @cache.instance_variable_get(:@cache)[@token1].should == @now.to_i
59
+ @cache.instance_variable_get(:@lru).should == [@token1]
60
+ end
61
+
62
+ it "should update lru list when store to existing entry" do
63
+ @cache.store(@token1, nil)
64
+ @cache.instance_variable_get(:@cache)[@token1].should == @now.to_i
65
+ @cache.instance_variable_get(:@lru).should == [@token1]
66
+ @cache.store(@token2, nil)
67
+ @cache.instance_variable_get(:@cache)[@token2].should == @now.to_i
68
+ @cache.instance_variable_get(:@lru).should == [@token1, @token2]
69
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
70
+ @cache.store(@token1, nil)
71
+ @cache.instance_variable_get(:@cache)[@token1].should == @now.to_i
72
+ @cache.instance_variable_get(:@lru).should == [@token2, @token1]
73
+ end
74
+
75
+ it "should remove old cache entries when store new one" do
76
+ @cache.store(@token1, nil)
77
+ @cache.store(@token2, nil)
78
+ @cache.instance_variable_get(:@cache).keys.should =~ [@token1, @token2]
79
+ @cache.instance_variable_get(:@lru).should == [@token1, @token2]
80
+ flexmock(Time).should_receive(:now).and_return(@now += RightScale::DispatchedCache::MAX_AGE + 1)
81
+ @cache.store(@token3, nil)
82
+ @cache.instance_variable_get(:@cache).keys.should == [@token3]
83
+ @cache.instance_variable_get(:@lru).should == [@token3]
84
+ end
85
+
86
+ it "should not store anything if this is a shared queue request" do
87
+ @cache.store(@token1, "shared")
88
+ @cache.instance_variable_get(:@cache)[@token1].should be_nil
89
+ @cache.instance_variable_get(:@lru).should be_empty
90
+ end
91
+
92
+ it "should not store anything if token is nil" do
93
+ @cache.store(nil, nil)
94
+ @cache.instance_variable_get(:@cache).should be_empty
95
+ @cache.instance_variable_get(:@lru).should be_empty
96
+ end
97
+
98
+ end
99
+
100
+ context "serviced_by" do
101
+
102
+ it "should return who request was serviced by and make it the most recently used" do
103
+ @cache.store(@token1, nil)
104
+ @cache.store(@token2, nil)
105
+ @cache.instance_variable_get(:@lru).should == [@token1, @token2]
106
+ @cache.serviced_by(@token1).should == @agent_id
107
+ @cache.instance_variable_get(:@lru).should == [@token2, @token1]
108
+ end
109
+
110
+ it "should return nil if request was not previously serviced" do
111
+ @cache.serviced_by(@token1).should be_nil
112
+ @cache.store(@token1, nil)
113
+ @cache.serviced_by(@token1).should == @agent_id
114
+ @cache.serviced_by(@token2).should be_nil
115
+ end
116
+
117
+ end
118
+
119
+ context "stats" do
120
+
121
+ it "should return nil if cache empty" do
122
+ @cache.stats.should be_nil
123
+ end
124
+
125
+ it "should return total and max age" do
126
+ @cache.store(@token1, nil)
127
+ flexmock(Time).should_receive(:now).and_return(@now += 10)
128
+ @cache.store(@token2, nil)
129
+ @cache.stats.should == {
130
+ "local total" => 2,
131
+ "local max age" => "10 sec"
132
+ }
133
+ @cache.serviced_by(@token1)
134
+ @cache.stats.should == {
135
+ "local total" => 2,
136
+ "local max age" => "0 sec"
137
+ }
138
+ end
139
+
140
+ end
141
+
142
+ end # RightScale::Dispatcher
@@ -24,7 +24,8 @@ require File.expand_path(File.join(File.dirname(__FILE__), 'spec_helper'))
24
24
 
25
25
  class Foo
26
26
  include RightScale::Actor
27
- expose :bar, :index, :i_kill_you
27
+ expose_idempotent :bar, :index, :i_kill_you
28
+ expose_non_idempotent :bar_non
28
29
  on_exception :handle_exception
29
30
 
30
31
  def index(payload)
@@ -39,6 +40,10 @@ class Foo
39
40
  ['hello', payload, request]
40
41
  end
41
42
 
43
+ def bar_non(payload)
44
+ @i = (@i || 0) + payload
45
+ end
46
+
42
47
  def i_kill_you(payload)
43
48
  raise RuntimeError.new('I kill you!')
44
49
  end
@@ -97,100 +102,19 @@ describe "RightScale::Dispatcher" do
97
102
  @actor = Foo.new
98
103
  @registry = RightScale::ActorRegistry.new
99
104
  @registry.register(@actor, nil)
100
- @agent = flexmock("Agent", :identity => "agent", :broker => @broker, :registry => @registry, :options => {}).by_default
101
- @dispatcher = RightScale::Dispatcher.new(@agent)
105
+ @agent_id = "rs-agent-1-1"
106
+ @agent = flexmock("Agent", :identity => @agent_id, :broker => @broker, :registry => @registry, :options => {}).by_default
107
+ @cache = RightScale::DispatchedCache.new(@agent_id)
108
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
102
109
  @dispatcher.em = EMMock
103
110
  @response_queue = RightScale::Dispatcher::RESPONSE_QUEUE
111
+ @header = flexmock("amqp header")
112
+ @header.should_receive(:ack).once.by_default
104
113
  end
105
114
 
106
- describe "Dispatched cache" do
107
-
108
- before(:each) do
109
- @dispatched = RightScale::Dispatcher::Dispatched.new
110
- @token1 = "token1"
111
- @token2 = "token2"
112
- @token3 = "token3"
113
- end
114
-
115
- context "when storing" do
116
-
117
- it "should store request token" do
118
- @dispatched.store(@token1)
119
- @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
120
- @dispatched.instance_variable_get(:@lru).should == [@token1]
121
- end
122
-
123
- it "should update lru list when store to existing entry" do
124
- @dispatched.store(@token1)
125
- @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
126
- @dispatched.instance_variable_get(:@lru).should == [@token1]
127
- @dispatched.store(@token2)
128
- @dispatched.instance_variable_get(:@cache)[@token2].should == @now.to_i
129
- @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
130
- flexmock(Time).should_receive(:now).and_return(@now += 10)
131
- @dispatched.store(@token1)
132
- @dispatched.instance_variable_get(:@cache)[@token1].should == @now.to_i
133
- @dispatched.instance_variable_get(:@lru).should == [@token2, @token1]
134
- end
135
-
136
- it "should remove old cache entries when store new one" do
137
- @dispatched.store(@token1)
138
- @dispatched.store(@token2)
139
- @dispatched.instance_variable_get(:@cache).keys.should == [@token1, @token2]
140
- @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
141
- flexmock(Time).should_receive(:now).and_return(@now += RightScale::Dispatcher::Dispatched::MAX_AGE + 1)
142
- @dispatched.store(@token3)
143
- @dispatched.instance_variable_get(:@cache).keys.should == [@token3]
144
- @dispatched.instance_variable_get(:@lru).should == [@token3]
145
- end
146
-
147
- end
148
-
149
- context "when fetching" do
150
-
151
- it "should fetch request and make it the most recently used" do
152
- @dispatched.store(@token1)
153
- @dispatched.store(@token2)
154
- @dispatched.instance_variable_get(:@lru).should == [@token1, @token2]
155
- @dispatched.fetch(@token1).should be_true
156
- @dispatched.instance_variable_get(:@lru).should == [@token2, @token1]
157
- end
158
-
159
- it "should return false if fetch non-existent request" do
160
- @dispatched.fetch(@token1).should be_false
161
- @dispatched.store(@token1)
162
- @dispatched.fetch(@token1).should be_true
163
- @dispatched.fetch(@token2).should be_false
164
- end
165
-
166
- end
167
-
168
- context "when retrieving stats" do
169
-
170
- it "should return nil if cache empty" do
171
- @dispatched.stats.should be_nil
172
- end
173
-
174
- it "should return total, youngest age, and oldest age" do
175
- @dispatched.store(@token1)
176
- flexmock(Time).should_receive(:now).and_return(@now += 10)
177
- @dispatched.store(@token2)
178
- stats = @dispatched.stats
179
- stats["total"].should == 2
180
- stats["youngest age"].should == 0
181
- stats["oldest age"].should == 10
182
- @dispatched.fetch(@token1)
183
- stats = @dispatched.stats
184
- stats["oldest age"].should == 0
185
- end
186
-
187
- end
188
-
189
- end # Dispatched
190
-
191
115
  it "should dispatch a request" do
192
116
  req = RightScale::Request.new('/foo/bar', 'you', :token => 'token')
193
- res = @dispatcher.dispatch(req)
117
+ res = @dispatcher.dispatch(req, @header)
194
118
  res.should(be_kind_of(RightScale::Result))
195
119
  res.token.should == 'token'
196
120
  res.results.should == ['hello', 'you']
@@ -198,7 +122,7 @@ describe "RightScale::Dispatcher" do
198
122
 
199
123
  it "should dispatch a request with required arity" do
200
124
  req = RightScale::Request.new('/foo/bar2', 'you', :token => 'token')
201
- res = @dispatcher.dispatch(req)
125
+ res = @dispatcher.dispatch(req, @header)
202
126
  res.should(be_kind_of(RightScale::Result))
203
127
  res.token.should == 'token'
204
128
  res.results.should == ['hello', 'you', req]
@@ -206,7 +130,7 @@ describe "RightScale::Dispatcher" do
206
130
 
207
131
  it "should dispatch a request to the default action" do
208
132
  req = RightScale::Request.new('/foo', 'you', :token => 'token')
209
- res = @dispatcher.dispatch(req)
133
+ res = @dispatcher.dispatch(req, @header)
210
134
  res.should(be_kind_of(RightScale::Result))
211
135
  res.token.should == req.token
212
136
  res.results.should == ['hello', 'you']
@@ -220,13 +144,13 @@ describe "RightScale::Dispatcher" do
220
144
  arg.to == "rs-mapper-1-1" &&
221
145
  arg.results == ['hello', 'you']},
222
146
  hsh(:persistent => true, :mandatory => true)).once
223
- res = @dispatcher.dispatch(req)
147
+ @dispatcher.dispatch(req, @header)
224
148
  end
225
149
 
226
150
  it "should handle custom prefixes" do
227
151
  @registry.register(Foo.new, 'umbongo')
228
152
  req = RightScale::Request.new('/umbongo/bar', 'you')
229
- res = @dispatcher.dispatch(req)
153
+ res = @dispatcher.dispatch(req, @header)
230
154
  res.should(be_kind_of(RightScale::Result))
231
155
  res.token.should == req.token
232
156
  res.results.should == ['hello', 'you']
@@ -236,7 +160,9 @@ describe "RightScale::Dispatcher" do
236
160
  flexmock(RightScale::Log).should_receive(:error).once
237
161
  req = RightScale::Request.new('/foo/i_kill_you', nil)
238
162
  flexmock(@actor).should_receive(:handle_exception).with(:i_kill_you, req, Exception).once
239
- @dispatcher.dispatch(req)
163
+ res = @dispatcher.dispatch(req, @header)
164
+ res.results.error?.should be_true
165
+ (res.results.content =~ /Could not handle \/foo\/i_kill_you request/).should be_true
240
166
  end
241
167
 
242
168
  it "should call on_exception Procs defined in a subclass with the correct arguments" do
@@ -244,7 +170,7 @@ describe "RightScale::Dispatcher" do
244
170
  actor = Bar.new
245
171
  @registry.register(actor, nil)
246
172
  req = RightScale::Request.new('/bar/i_kill_you', nil)
247
- @dispatcher.dispatch(req)
173
+ @dispatcher.dispatch(req, @header)
248
174
  called_with = actor.instance_variable_get("@called_with")
249
175
  called_with[0].should == :i_kill_you
250
176
  called_with[1].should == req
@@ -257,25 +183,25 @@ describe "RightScale::Dispatcher" do
257
183
  actor = Bar.new
258
184
  @registry.register(actor, nil)
259
185
  req = RightScale::Request.new('/bar/i_kill_you', nil)
260
- @dispatcher.dispatch(req)
186
+ @dispatcher.dispatch(req, @header)
261
187
  actor.instance_variable_get("@scope").should == actor
262
188
  end
263
189
 
264
190
  it "should log error if something goes wrong" do
265
191
  RightScale::Log.should_receive(:error).once
266
192
  req = RightScale::Request.new('/foo/i_kill_you', nil)
267
- @dispatcher.dispatch(req)
193
+ @dispatcher.dispatch(req, @header)
268
194
  end
269
195
 
270
196
  it "should reject requests whose time-to-live has expired" do
271
197
  flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
272
198
  flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED.*TTL 2 sec ago/})
273
199
  @broker.should_receive(:publish).never
274
- @dispatcher = RightScale::Dispatcher.new(@agent)
200
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
275
201
  @dispatcher.em = EMMock
276
202
  req = RightScale::Push.new('/foo/bar', 'you', :expires_at => @now.to_i + 8)
277
203
  flexmock(Time).should_receive(:now).and_return(@now += 10)
278
- @dispatcher.dispatch(req).should be_nil
204
+ @dispatcher.dispatch(req, @header).should be_nil
279
205
  end
280
206
 
281
207
  it "should send non-delivery result if Request is rejected because its time-to-live has expired" do
@@ -287,11 +213,11 @@ describe "RightScale::Dispatcher" do
287
213
  arg.results.non_delivery? &&
288
214
  arg.results.content == RightScale::OperationResult::TTL_EXPIRATION},
289
215
  hsh(:persistent => true, :mandatory => true)).once
290
- @dispatcher = RightScale::Dispatcher.new(@agent)
216
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
291
217
  @dispatcher.em = EMMock
292
218
  req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => @response_queue, :expires_at => @now.to_i + 8})
293
219
  flexmock(Time).should_receive(:now).and_return(@now += 10)
294
- @dispatcher.dispatch(req).should be_nil
220
+ @dispatcher.dispatch(req, @header).should be_nil
295
221
  end
296
222
 
297
223
  it "should send error result instead of non-delivery if agent does not know about non-delivery" do
@@ -303,30 +229,30 @@ describe "RightScale::Dispatcher" do
303
229
  arg.results.error? &&
304
230
  arg.results.content =~ /Could not deliver/},
305
231
  hsh(:persistent => true, :mandatory => true)).once
306
- @dispatcher = RightScale::Dispatcher.new(@agent)
232
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
307
233
  @dispatcher.em = EMMock
308
234
  req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => "rs-mapper-1-1", :expires_at => @now.to_i + 8}, [12, 13])
309
235
  flexmock(Time).should_receive(:now).and_return(@now += 10)
310
- @dispatcher.dispatch(req).should be_nil
236
+ @dispatcher.dispatch(req, @header).should be_nil
311
237
  end
312
238
 
313
239
  it "should not reject requests whose time-to-live has not expired" do
314
240
  flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
315
- @dispatcher = RightScale::Dispatcher.new(@agent)
241
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
316
242
  @dispatcher.em = EMMock
317
243
  req = RightScale::Request.new('/foo/bar', 'you', :expires_at => @now.to_i + 11)
318
244
  flexmock(Time).should_receive(:now).and_return(@now += 10)
319
- res = @dispatcher.dispatch(req)
245
+ res = @dispatcher.dispatch(req, @header)
320
246
  res.should(be_kind_of(RightScale::Result))
321
247
  res.token.should == req.token
322
248
  res.results.should == ['hello', 'you']
323
249
  end
324
250
 
325
251
  it "should not check age of requests with time-to-live check disabled" do
326
- @dispatcher = RightScale::Dispatcher.new(@agent)
252
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
327
253
  @dispatcher.em = EMMock
328
254
  req = RightScale::Request.new('/foo/bar', 'you', :expires_at => 0)
329
- res = @dispatcher.dispatch(req)
255
+ res = @dispatcher.dispatch(req, @header)
330
256
  res.should(be_kind_of(RightScale::Result))
331
257
  res.token.should == req.token
332
258
  res.results.should == ['hello', 'you']
@@ -335,12 +261,11 @@ describe "RightScale::Dispatcher" do
335
261
  it "should reject duplicate requests" do
336
262
  flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT DUP/})
337
263
  EM.run do
338
- @agent.should_receive(:options).and_return(:dup_check => true)
339
- @dispatcher = RightScale::Dispatcher.new(@agent)
264
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
340
265
  @dispatcher.em = EMMock
341
- req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
342
- @dispatcher.instance_variable_get(:@dispatched).store(req.token)
343
- @dispatcher.dispatch(req).should be_nil
266
+ req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
267
+ @cache.store(req.token, nil)
268
+ @dispatcher.dispatch(req, @header).should be_nil
344
269
  EM.stop
345
270
  end
346
271
  end
@@ -348,57 +273,113 @@ describe "RightScale::Dispatcher" do
348
273
  it "should reject duplicate retry requests" do
349
274
  flexmock(RightScale::Log).should_receive(:info).once.with(on {|arg| arg =~ /REJECT RETRY DUP/})
350
275
  EM.run do
351
- @agent.should_receive(:options).and_return(:dup_check => true)
352
- @dispatcher = RightScale::Dispatcher.new(@agent)
276
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
353
277
  @dispatcher.em = EMMock
354
- req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
278
+ req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
355
279
  req.tries.concat(["try1", "try2"])
356
- @dispatcher.instance_variable_get(:@dispatched).store("try2")
357
- @dispatcher.dispatch(req).should be_nil
280
+ @cache.store("try2", nil)
281
+ @dispatcher.dispatch(req, @header).should be_nil
358
282
  EM.stop
359
283
  end
360
284
  end
361
285
 
362
286
  it "should not reject non-duplicate requests" do
363
287
  EM.run do
364
- @agent.should_receive(:options).and_return(:dup_check => true)
365
- @dispatcher = RightScale::Dispatcher.new(@agent)
288
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
366
289
  @dispatcher.em = EMMock
367
- req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
290
+ req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
368
291
  req.tries.concat(["try1", "try2"])
369
- @dispatcher.instance_variable_get(:@dispatched).store("try3")
370
- @dispatcher.dispatch(req).should_not be_nil
292
+ @cache.store("try3", nil)
293
+ @dispatcher.dispatch(req, @header).should_not be_nil
371
294
  EM.stop
372
295
  end
373
296
  end
374
297
 
375
- it "should not check for duplicates if dup_check disabled" do
298
+ it "should not reject duplicate idempotent requests" do
376
299
  EM.run do
377
- @dispatcher = RightScale::Dispatcher.new(@agent)
300
+ @dispatcher = RightScale::Dispatcher.new(@agent, @cache)
378
301
  @dispatcher.em = EMMock
379
302
  req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
303
+ @cache.store(req.token, nil)
304
+ @dispatcher.dispatch(req, @header).should_not be_nil
305
+ EM.stop
306
+ end
307
+ end
308
+
309
+ it "should not check for duplicates if duplicate checking is disabled" do
310
+ EM.run do
311
+ @dispatcher = RightScale::Dispatcher.new(@agent, dispatched_cache = nil)
312
+ @dispatcher.em = EMMock
313
+ req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
314
+ req.tries.concat(["try1", "try2"])
315
+ @dispatcher.instance_variable_get(:@dispatched_cache).should be_nil
316
+ @dispatcher.dispatch(req, @header).should_not be_nil
317
+ EM.stop
318
+ end
319
+ end
320
+
321
+ it "should not check for duplicates if actor method is idempotent" do
322
+ EM.run do
323
+ @dispatcher = RightScale::Dispatcher.new(@agent, dispatched_cache = nil)
324
+ @dispatcher.em = EMMock
325
+ req = RightScale::Request.new('/foo/bar', 1, :token => "try")
380
326
  req.tries.concat(["try1", "try2"])
381
- @dispatcher.instance_variable_get(:@dispatched).should be_nil
382
- @dispatcher.dispatch(req).should_not be_nil
327
+ @dispatcher.instance_variable_get(:@dispatched_cache).should be_nil
328
+ @dispatcher.dispatch(req, @header).should_not be_nil
383
329
  EM.stop
384
330
  end
385
331
  end
386
332
 
387
333
  it "should return dispatch age of youngest unfinished request" do
334
+ @header.should_receive(:ack).never
388
335
  @dispatcher.em = EMMockNoCallback
389
336
  @dispatcher.dispatch_age.should be_nil
390
- @dispatcher.dispatch(RightScale::Push.new('/foo/bar', 'you'))
337
+ @dispatcher.dispatch(RightScale::Push.new('/foo/bar', 'you'), @header)
391
338
  @dispatcher.dispatch_age.should == 0
392
- @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'))
339
+ @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'), @header)
393
340
  flexmock(Time).should_receive(:now).and_return(@now += 100)
394
341
  @dispatcher.dispatch_age.should == 100
395
342
  end
396
343
 
397
344
  it "should return dispatch age of nil if all requests finished" do
398
345
  @dispatcher.dispatch_age.should be_nil
399
- @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'))
346
+ @dispatcher.dispatch(RightScale::Request.new('/foo/bar', 'you'), @header)
400
347
  flexmock(Time).should_receive(:now).and_return(@now += 100)
401
348
  @dispatcher.dispatch_age.should be_nil
402
349
  end
403
350
 
351
+ it "should ack request even if fail while dispatching" do
352
+ RightScale::Log.should_receive(:error).and_raise(Exception).once
353
+ req = RightScale::Request.new('/foo/i_kill_you', nil)
354
+ lambda { @dispatcher.dispatch(req, @header) }.should raise_error(Exception)
355
+ end
356
+
357
+ it "should not attempt to ack request if fail while dispatching and there is no header" do
358
+ @header.should_receive(:ack).never
359
+ RightScale::Log.should_receive(:error).and_raise(Exception).once
360
+ req = RightScale::Request.new('/foo/i_kill_you', nil)
361
+ lambda { @dispatcher.dispatch(req, nil) }.should raise_error(Exception)
362
+ end
363
+
364
+ it "should ack request even if fail while doing final setup for processing request" do
365
+ @dispatcher.em = EMMock
366
+ flexmock(EMMock).should_receive(:defer).and_raise(Exception).once
367
+ req = RightScale::Request.new('/foo/bar', 'you')
368
+ lambda { @dispatcher.dispatch(req, @header) }.should raise_error(Exception)
369
+ end
370
+
371
+ it "should not attempt to ack request if fail while doing final setup for processing request and there is no header" do
372
+ @header.should_receive(:ack).never
373
+ @dispatcher.em = EMMock
374
+ flexmock(EMMock).should_receive(:defer).and_raise(Exception).once
375
+ req = RightScale::Request.new('/foo/bar', 'you')
376
+ lambda { @dispatcher.dispatch(req, nil) }.should raise_error(Exception)
377
+ end
378
+
379
+ it "should not attempt to ack request if dispatch a request and there is no header" do
380
+ @header.should_receive(:ack).never
381
+ req = RightScale::Request.new('/foo/bar', 'you', :token => 'token')
382
+ @dispatcher.dispatch(req, nil)
383
+ end
384
+
404
385
  end # RightScale::Dispatcher