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