right_agent 0.13.5 → 0.14.0
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/actors/agent_manager.rb +1 -32
- data/lib/right_agent/agent.rb +243 -230
- data/lib/right_agent/dispatched_cache.rb +4 -5
- data/lib/right_agent/dispatcher.rb +146 -157
- data/lib/right_agent/pid_file.rb +1 -1
- data/lib/right_agent/platform.rb +14 -14
- data/lib/right_agent/scripts/agent_controller.rb +2 -4
- data/lib/right_agent/sender.rb +214 -223
- data/lib/right_agent/serialize/secure_serializer.rb +2 -2
- data/right_agent.gemspec +3 -3
- data/spec/agent_spec.rb +50 -171
- data/spec/dispatched_cache_spec.rb +13 -19
- data/spec/dispatcher_spec.rb +192 -254
- data/spec/sender_spec.rb +212 -168
- metadata +7 -4
data/spec/dispatcher_spec.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Copyright (c) 2009-
|
2
|
+
# Copyright (c) 2009-2012 RightScale Inc
|
3
3
|
#
|
4
4
|
# Permission is hereby granted, free of charge, to any person obtaining
|
5
5
|
# a copy of this software and associated documentation files (the
|
@@ -75,311 +75,249 @@ class Doomed
|
|
75
75
|
on_exception :doh
|
76
76
|
end
|
77
77
|
|
78
|
-
# Mock the EventMachine deferrer.
|
79
|
-
class EMMock
|
80
|
-
def self.defer(op = nil, callback = nil)
|
81
|
-
callback.call(op.call)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Mock the EventMachine deferrer but do not do callback.
|
86
|
-
class EMMockNoCallback
|
87
|
-
def self.defer(op = nil, callback = nil)
|
88
|
-
op.call
|
89
|
-
end
|
90
|
-
end
|
91
|
-
|
92
78
|
describe "RightScale::Dispatcher" do
|
93
79
|
|
94
80
|
include FlexMock::ArgumentTypes
|
95
81
|
|
96
82
|
before(:each) do
|
97
|
-
flexmock(RightScale::Log)
|
98
|
-
|
83
|
+
@log = flexmock(RightScale::Log)
|
84
|
+
@log.should_receive(:error).by_default.and_return { |m| raise RightScale::Log.format(*m) }
|
85
|
+
@log.should_receive(:info).by_default
|
99
86
|
@now = Time.at(1000000)
|
100
87
|
flexmock(Time).should_receive(:now).and_return(@now).by_default
|
101
|
-
@broker = flexmock("Broker", :subscribe => true, :publish => true).by_default
|
102
88
|
@actor = Foo.new
|
103
89
|
@registry = RightScale::ActorRegistry.new
|
104
90
|
@registry.register(@actor, nil)
|
105
91
|
@agent_id = "rs-agent-1-1"
|
106
|
-
@agent = flexmock("Agent", :identity => @agent_id, :
|
92
|
+
@agent = flexmock("Agent", :identity => @agent_id, :registry => @registry).by_default
|
107
93
|
@cache = RightScale::DispatchedCache.new(@agent_id)
|
108
94
|
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
109
|
-
@dispatcher.em = EMMock
|
110
|
-
@response_queue = RightScale::Dispatcher::RESPONSE_QUEUE
|
111
|
-
@header = flexmock("amqp header")
|
112
|
-
@header.should_receive(:ack).once.by_default
|
113
95
|
end
|
114
96
|
|
115
|
-
|
116
|
-
req = RightScale::Request.new('/foo/bar', 'you', :token => 'token')
|
117
|
-
res = @dispatcher.dispatch(req, @header)
|
118
|
-
res.should(be_kind_of(RightScale::Result))
|
119
|
-
res.token.should == 'token'
|
120
|
-
res.results.should == ['hello', 'you']
|
121
|
-
end
|
97
|
+
context "routable?" do
|
122
98
|
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
res.should(be_kind_of(RightScale::Result))
|
127
|
-
res.token.should == 'token'
|
128
|
-
res.results.should == ['hello', 'you', req]
|
129
|
-
end
|
99
|
+
it "should return false if actor is not available for routing" do
|
100
|
+
@dispatcher.routable?("foo").should be_true
|
101
|
+
end
|
130
102
|
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
res.should(be_kind_of(RightScale::Result))
|
135
|
-
res.token.should == req.token
|
136
|
-
res.results.should == ['hello', 'you']
|
137
|
-
end
|
103
|
+
it "should return true if actor is available for routing" do
|
104
|
+
@dispatcher.routable?("bar").should be_false
|
105
|
+
end
|
138
106
|
|
139
|
-
it "should publish result of request to response queue" do
|
140
|
-
req = RightScale::Request.new('/foo', 'you', :token => 'token')
|
141
|
-
req.reply_to = "rs-mapper-1-1"
|
142
|
-
@broker.should_receive(:publish).with(hsh(:name => @response_queue),
|
143
|
-
on {|arg| arg.class == RightScale::Result &&
|
144
|
-
arg.to == "rs-mapper-1-1" &&
|
145
|
-
arg.results == ['hello', 'you']},
|
146
|
-
hsh(:persistent => true, :mandatory => true)).once
|
147
|
-
@dispatcher.dispatch(req, @header)
|
148
107
|
end
|
149
108
|
|
150
|
-
|
151
|
-
@registry.register(Foo.new, 'umbongo')
|
152
|
-
req = RightScale::Request.new('/umbongo/bar', 'you')
|
153
|
-
res = @dispatcher.dispatch(req, @header)
|
154
|
-
res.should(be_kind_of(RightScale::Result))
|
155
|
-
res.token.should == req.token
|
156
|
-
res.results.should == ['hello', 'you']
|
157
|
-
end
|
109
|
+
context "dispatch" do
|
158
110
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
end
|
111
|
+
it "should dispatch a request" do
|
112
|
+
req = RightScale::Request.new('/foo/bar', 'you', :token => 'token')
|
113
|
+
res = @dispatcher.dispatch(req)
|
114
|
+
res.should(be_kind_of(RightScale::Result))
|
115
|
+
res.token.should == 'token'
|
116
|
+
res.results.should == ['hello', 'you']
|
117
|
+
end
|
167
118
|
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
called_with[0].should == :i_kill_you
|
176
|
-
called_with[1].should == req
|
177
|
-
called_with[2].should be_kind_of(RuntimeError)
|
178
|
-
called_with[2].message.should == 'I kill you!'
|
179
|
-
end
|
119
|
+
it "should dispatch a request with required arity" do
|
120
|
+
req = RightScale::Request.new('/foo/bar2', 'you', :token => 'token')
|
121
|
+
res = @dispatcher.dispatch(req)
|
122
|
+
res.should(be_kind_of(RightScale::Result))
|
123
|
+
res.token.should == 'token'
|
124
|
+
res.results.should == ['hello', 'you', req]
|
125
|
+
end
|
180
126
|
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
end
|
127
|
+
it "should dispatch a request to the default action" do
|
128
|
+
req = RightScale::Request.new('/foo', 'you', :token => 'token')
|
129
|
+
res = @dispatcher.dispatch(req)
|
130
|
+
res.should(be_kind_of(RightScale::Result))
|
131
|
+
res.token.should == req.token
|
132
|
+
res.results.should == ['hello', 'you']
|
133
|
+
end
|
189
134
|
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
135
|
+
it "should return nil for successful push" do
|
136
|
+
req = RightScale::Push.new('/foo', 'you', :token => 'token')
|
137
|
+
res = @dispatcher.dispatch(req)
|
138
|
+
res.should be_nil
|
139
|
+
end
|
195
140
|
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
@dispatcher.dispatch(req, @header).should be_nil
|
205
|
-
end
|
141
|
+
it "should handle custom prefixes" do
|
142
|
+
@registry.register(Foo.new, 'umbongo')
|
143
|
+
req = RightScale::Request.new('/umbongo/bar', 'you')
|
144
|
+
res = @dispatcher.dispatch(req)
|
145
|
+
res.should(be_kind_of(RightScale::Result))
|
146
|
+
res.token.should == req.token
|
147
|
+
res.results.should == ['hello', 'you']
|
148
|
+
end
|
206
149
|
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
on {|arg| arg.class == RightScale::Result &&
|
212
|
-
arg.to == @response_queue &&
|
213
|
-
arg.results.non_delivery? &&
|
214
|
-
arg.results.content == RightScale::OperationResult::TTL_EXPIRATION},
|
215
|
-
hsh(:persistent => true, :mandatory => true)).once
|
216
|
-
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
217
|
-
@dispatcher.em = EMMock
|
218
|
-
req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => @response_queue, :expires_at => @now.to_i + 8})
|
219
|
-
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
220
|
-
@dispatcher.dispatch(req, @header).should be_nil
|
221
|
-
end
|
150
|
+
it "should raise exception if actor is unknown" do
|
151
|
+
req = RightScale::Request.new('/bad', 'you', :token => 'token')
|
152
|
+
lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::InvalidRequestType)
|
153
|
+
end
|
222
154
|
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
on {|arg| arg.class == RightScale::Result &&
|
228
|
-
arg.to == "rs-mapper-1-1" &&
|
229
|
-
arg.results.error? &&
|
230
|
-
arg.results.content =~ /Could not deliver/},
|
231
|
-
hsh(:persistent => true, :mandatory => true)).once
|
232
|
-
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
233
|
-
@dispatcher.em = EMMock
|
234
|
-
req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => "rs-mapper-1-1", :expires_at => @now.to_i + 8}, [12, 13])
|
235
|
-
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
236
|
-
@dispatcher.dispatch(req, @header).should be_nil
|
237
|
-
end
|
155
|
+
it "should raise exception if actor method is unknown" do
|
156
|
+
req = RightScale::Request.new('/foo/bar-none', 'you', :token => 'token')
|
157
|
+
lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::InvalidRequestType)
|
158
|
+
end
|
238
159
|
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
res.token.should == req.token
|
248
|
-
res.results.should == ['hello', 'you']
|
249
|
-
end
|
160
|
+
it "should call the on_exception callback if something goes wrong" do
|
161
|
+
@log.should_receive(:error).once
|
162
|
+
req = RightScale::Request.new('/foo/i_kill_you', nil)
|
163
|
+
flexmock(@actor).should_receive(:handle_exception).with(:i_kill_you, req, Exception).once
|
164
|
+
res = @dispatcher.dispatch(req)
|
165
|
+
res.results.error?.should be_true
|
166
|
+
(res.results.content =~ /Could not handle \/foo\/i_kill_you request/).should be_true
|
167
|
+
end
|
250
168
|
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
169
|
+
it "should call on_exception Procs defined in a subclass with the correct arguments" do
|
170
|
+
@log.should_receive(:error).once
|
171
|
+
actor = Bar.new
|
172
|
+
@registry.register(actor, nil)
|
173
|
+
req = RightScale::Request.new('/bar/i_kill_you', nil)
|
174
|
+
@dispatcher.dispatch(req)
|
175
|
+
called_with = actor.instance_variable_get("@called_with")
|
176
|
+
called_with[0].should == :i_kill_you
|
177
|
+
called_with[1].should == req
|
178
|
+
called_with[2].should be_kind_of(RuntimeError)
|
179
|
+
called_with[2].message.should == 'I kill you!'
|
180
|
+
end
|
181
|
+
|
182
|
+
it "should call on_exception Procs defined in a subclass in the scope of the actor" do
|
183
|
+
@log.should_receive(:error).once
|
184
|
+
actor = Bar.new
|
185
|
+
@registry.register(actor, nil)
|
186
|
+
req = RightScale::Request.new('/bar/i_kill_you', nil)
|
187
|
+
@dispatcher.dispatch(req)
|
188
|
+
actor.instance_variable_get("@scope").should == actor
|
189
|
+
end
|
260
190
|
|
261
|
-
|
262
|
-
|
263
|
-
|
191
|
+
it "should log error if dispatch fails" do
|
192
|
+
RightScale::Log.should_receive(:error).once
|
193
|
+
req = RightScale::Request.new('/foo/i_kill_you', nil)
|
194
|
+
@dispatcher.dispatch(req)
|
195
|
+
end
|
196
|
+
|
197
|
+
it "should reject requests whose time-to-live has expired" do
|
198
|
+
flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
|
199
|
+
@log.should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED.*TTL 2 sec ago/})
|
264
200
|
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
265
|
-
@
|
266
|
-
|
267
|
-
@
|
268
|
-
@dispatcher.dispatch(req, @header).should be_nil
|
269
|
-
EM.stop
|
201
|
+
req = RightScale::Push.new('/foo/bar', 'you', :expires_at => @now.to_i + 8)
|
202
|
+
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
203
|
+
@dispatcher.dispatch(req).should be_nil
|
270
204
|
end
|
271
|
-
end
|
272
205
|
|
273
|
-
|
274
|
-
|
275
|
-
|
206
|
+
it "should return non-delivery result if Request is rejected because its time-to-live has expired" do
|
207
|
+
flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
|
208
|
+
@log.should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED/})
|
276
209
|
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
277
|
-
@
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
EM.stop
|
210
|
+
req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => @response_queue, :expires_at => @now.to_i + 8})
|
211
|
+
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
212
|
+
res = @dispatcher.dispatch(req)
|
213
|
+
res.results.non_delivery?.should be_true
|
214
|
+
res.results.content.should == RightScale::OperationResult::TTL_EXPIRATION
|
283
215
|
end
|
284
|
-
end
|
285
216
|
|
286
|
-
|
287
|
-
|
217
|
+
it "should return error result instead of non-delivery if agent does not know about non-delivery" do
|
218
|
+
flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
|
219
|
+
@log.should_receive(:info).once.with(on {|arg| arg =~ /REJECT EXPIRED/})
|
288
220
|
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
289
|
-
@
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
EM.stop
|
221
|
+
req = RightScale::Request.new('/foo/bar', 'you', {:reply_to => "rs-mapper-1-1", :expires_at => @now.to_i + 8}, [12, 13])
|
222
|
+
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
223
|
+
res = @dispatcher.dispatch(req)
|
224
|
+
res.results.error?.should be_true
|
225
|
+
res.results.content.should =~ /Could not deliver/
|
295
226
|
end
|
296
|
-
end
|
297
227
|
|
298
|
-
|
299
|
-
|
228
|
+
it "should not reject requests whose time-to-live has not expired" do
|
229
|
+
flexmock(Time).should_receive(:now).and_return(Time.at(1000000)).by_default
|
300
230
|
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
301
|
-
@
|
302
|
-
|
303
|
-
@
|
304
|
-
|
305
|
-
|
231
|
+
req = RightScale::Request.new('/foo/bar', 'you', :expires_at => @now.to_i + 11)
|
232
|
+
flexmock(Time).should_receive(:now).and_return(@now += 10)
|
233
|
+
res = @dispatcher.dispatch(req)
|
234
|
+
res.should(be_kind_of(RightScale::Result))
|
235
|
+
res.token.should == req.token
|
236
|
+
res.results.should == ['hello', 'you']
|
306
237
|
end
|
307
|
-
end
|
308
238
|
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
@dispatcher.
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
@dispatcher.dispatch(req, @header).should_not be_nil
|
317
|
-
EM.stop
|
239
|
+
it "should not check age of requests with time-to-live check disabled" do
|
240
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
241
|
+
req = RightScale::Request.new('/foo/bar', 'you', :expires_at => 0)
|
242
|
+
res = @dispatcher.dispatch(req)
|
243
|
+
res.should(be_kind_of(RightScale::Result))
|
244
|
+
res.token.should == req.token
|
245
|
+
res.results.should == ['hello', 'you']
|
318
246
|
end
|
319
|
-
end
|
320
247
|
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
248
|
+
it "should reject duplicate request by raising exception" do
|
249
|
+
@log.should_receive(:info).once.with(on {|arg| arg =~ /REJECT DUP/})
|
250
|
+
EM.run do
|
251
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
252
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
253
|
+
@cache.store(req.token)
|
254
|
+
lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::DuplicateRequest)
|
255
|
+
EM.stop
|
256
|
+
end
|
330
257
|
end
|
331
|
-
end
|
332
258
|
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
259
|
+
it "should reject duplicate request from a retry by raising exception" do
|
260
|
+
@log.should_receive(:info).once.with(on {|arg| arg =~ /REJECT RETRY DUP/})
|
261
|
+
EM.run do
|
262
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
263
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
264
|
+
req.tries.concat(["try1", "try2"])
|
265
|
+
@cache.store("try2")
|
266
|
+
lambda { @dispatcher.dispatch(req) }.should raise_error(RightScale::Dispatcher::DuplicateRequest)
|
267
|
+
EM.stop
|
268
|
+
end
|
269
|
+
end
|
343
270
|
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
271
|
+
it "should not reject non-duplicate requests" do
|
272
|
+
EM.run do
|
273
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
274
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
275
|
+
req.tries.concat(["try1", "try2"])
|
276
|
+
@cache.store("try3")
|
277
|
+
@dispatcher.dispatch(req).should_not be_nil
|
278
|
+
EM.stop
|
279
|
+
end
|
280
|
+
end
|
350
281
|
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
282
|
+
it "should not reject duplicate idempotent requests" do
|
283
|
+
EM.run do
|
284
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, @cache)
|
285
|
+
req = RightScale::Request.new('/foo/bar', 'you', :token => "try")
|
286
|
+
@cache.store(req.token)
|
287
|
+
@dispatcher.dispatch(req).should_not be_nil
|
288
|
+
EM.stop
|
289
|
+
end
|
290
|
+
end
|
356
291
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
292
|
+
it "should not check for duplicates if duplicate checking is disabled" do
|
293
|
+
EM.run do
|
294
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, dispatched_cache = nil)
|
295
|
+
req = RightScale::Request.new('/foo/bar_non', 1, :token => "try")
|
296
|
+
req.tries.concat(["try1", "try2"])
|
297
|
+
@dispatcher.instance_variable_get(:@dispatched_cache).should be_nil
|
298
|
+
@dispatcher.dispatch(req).should_not be_nil
|
299
|
+
EM.stop
|
300
|
+
end
|
301
|
+
end
|
363
302
|
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
303
|
+
it "should not check for duplicates if actor method is idempotent" do
|
304
|
+
EM.run do
|
305
|
+
@dispatcher = RightScale::Dispatcher.new(@agent, dispatched_cache = nil)
|
306
|
+
req = RightScale::Request.new('/foo/bar', 1, :token => "try")
|
307
|
+
req.tries.concat(["try1", "try2"])
|
308
|
+
@dispatcher.instance_variable_get(:@dispatched_cache).should be_nil
|
309
|
+
@dispatcher.dispatch(req).should_not be_nil
|
310
|
+
EM.stop
|
311
|
+
end
|
312
|
+
end
|
370
313
|
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
end
|
314
|
+
it "should return error result if dispatch fails" do
|
315
|
+
@log.should_receive(:error).with(/Could not handle/, Exception, :trace).once
|
316
|
+
req = RightScale::Request.new('/foo/i_kill_you', nil)
|
317
|
+
res = @dispatcher.dispatch(req)
|
318
|
+
res.results.error?.should be_true
|
319
|
+
end
|
378
320
|
|
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
321
|
end
|
384
322
|
|
385
323
|
end # RightScale::Dispatcher
|