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.
- data/lib/right_agent.rb +2 -0
- data/lib/right_agent/actor.rb +45 -10
- data/lib/right_agent/actor_registry.rb +5 -5
- data/lib/right_agent/actors/agent_manager.rb +4 -4
- data/lib/right_agent/agent.rb +97 -37
- data/lib/right_agent/agent_tag_manager.rb +1 -2
- data/lib/right_agent/command/command_io.rb +1 -3
- data/lib/right_agent/command/command_runner.rb +9 -3
- data/lib/right_agent/dispatched_cache.rb +110 -0
- data/lib/right_agent/dispatcher.rb +119 -180
- data/lib/right_agent/history.rb +136 -0
- data/lib/right_agent/log.rb +6 -3
- data/lib/right_agent/monkey_patches/ruby_patch.rb +0 -1
- data/lib/right_agent/pid_file.rb +1 -1
- data/lib/right_agent/platform.rb +2 -2
- data/lib/right_agent/platform/linux.rb +8 -1
- data/lib/right_agent/platform/windows.rb +1 -1
- data/lib/right_agent/sender.rb +57 -41
- data/right_agent.gemspec +4 -4
- data/spec/actor_registry_spec.rb +7 -8
- data/spec/actor_spec.rb +87 -24
- data/spec/agent_spec.rb +107 -8
- data/spec/command/command_runner_spec.rb +12 -1
- data/spec/dispatched_cache_spec.rb +142 -0
- data/spec/dispatcher_spec.rb +110 -129
- data/spec/history_spec.rb +234 -0
- data/spec/idempotent_request_spec.rb +1 -1
- data/spec/log_spec.rb +15 -0
- data/spec/operation_result_spec.rb +4 -2
- data/spec/platform/darwin_spec.rb +13 -0
- data/spec/platform/linux_spec.rb +38 -0
- data/spec/platform/platform_spec.rb +46 -51
- data/spec/platform/windows_spec.rb +13 -0
- data/spec/sender_spec.rb +81 -38
- metadata +12 -9
- data/lib/right_agent/monkey_patches/ruby_patch/singleton_patch.rb +0 -45
- data/spec/platform/darwin.rb +0 -11
- data/spec/platform/linux.rb +0 -23
- 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
|
108
|
+
it "for heartbeat is 0" do
|
106
109
|
@agent.options.should include(:heartbeat)
|
107
|
-
@agent.options[:heartbeat].should ==
|
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
|
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
|
data/spec/dispatcher_spec.rb
CHANGED
@@ -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
|
-
|
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
|
-
@
|
101
|
-
@
|
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
|
-
|
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
|
-
@
|
339
|
-
@dispatcher = RightScale::Dispatcher.new(@agent)
|
264
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
340
265
|
@dispatcher.em = EMMock
|
341
|
-
req = RightScale::Request.new('/foo/
|
342
|
-
@
|
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
|
-
@
|
352
|
-
@dispatcher = RightScale::Dispatcher.new(@agent)
|
276
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
353
277
|
@dispatcher.em = EMMock
|
354
|
-
req = RightScale::Request.new('/foo/
|
278
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
355
279
|
req.tries.concat(["try1", "try2"])
|
356
|
-
@
|
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
|
-
@
|
365
|
-
@dispatcher = RightScale::Dispatcher.new(@agent)
|
288
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
366
289
|
@dispatcher.em = EMMock
|
367
|
-
req = RightScale::Request.new('/foo/
|
290
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
368
291
|
req.tries.concat(["try1", "try2"])
|
369
|
-
@
|
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
|
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(:@
|
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
|