right_agent 0.10.13 → 0.13.5

Sign up to get free protection for your applications and to get access to all the features.
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