shift-nanite 0.4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +430 -0
  3. data/Rakefile +76 -0
  4. data/TODO +24 -0
  5. data/bin/nanite-admin +65 -0
  6. data/bin/nanite-agent +79 -0
  7. data/bin/nanite-mapper +50 -0
  8. data/lib/nanite.rb +74 -0
  9. data/lib/nanite/actor.rb +71 -0
  10. data/lib/nanite/actor_registry.rb +26 -0
  11. data/lib/nanite/admin.rb +138 -0
  12. data/lib/nanite/agent.rb +264 -0
  13. data/lib/nanite/amqp.rb +58 -0
  14. data/lib/nanite/cluster.rb +250 -0
  15. data/lib/nanite/config.rb +112 -0
  16. data/lib/nanite/console.rb +39 -0
  17. data/lib/nanite/daemonize.rb +13 -0
  18. data/lib/nanite/identity.rb +16 -0
  19. data/lib/nanite/job.rb +104 -0
  20. data/lib/nanite/local_state.rb +38 -0
  21. data/lib/nanite/log.rb +66 -0
  22. data/lib/nanite/log/formatter.rb +39 -0
  23. data/lib/nanite/mapper.rb +309 -0
  24. data/lib/nanite/mapper_proxy.rb +67 -0
  25. data/lib/nanite/nanite_dispatcher.rb +92 -0
  26. data/lib/nanite/packets.rb +365 -0
  27. data/lib/nanite/pid_file.rb +52 -0
  28. data/lib/nanite/reaper.rb +39 -0
  29. data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
  30. data/lib/nanite/security/certificate.rb +55 -0
  31. data/lib/nanite/security/certificate_cache.rb +66 -0
  32. data/lib/nanite/security/distinguished_name.rb +34 -0
  33. data/lib/nanite/security/encrypted_document.rb +46 -0
  34. data/lib/nanite/security/rsa_key_pair.rb +53 -0
  35. data/lib/nanite/security/secure_serializer.rb +68 -0
  36. data/lib/nanite/security/signature.rb +46 -0
  37. data/lib/nanite/security/static_certificate_store.rb +35 -0
  38. data/lib/nanite/security_provider.rb +47 -0
  39. data/lib/nanite/serializer.rb +52 -0
  40. data/lib/nanite/state.rb +168 -0
  41. data/lib/nanite/streaming.rb +125 -0
  42. data/lib/nanite/util.rb +58 -0
  43. data/spec/actor_registry_spec.rb +60 -0
  44. data/spec/actor_spec.rb +77 -0
  45. data/spec/agent_spec.rb +240 -0
  46. data/spec/cached_certificate_store_proxy_spec.rb +34 -0
  47. data/spec/certificate_cache_spec.rb +49 -0
  48. data/spec/certificate_spec.rb +27 -0
  49. data/spec/cluster_spec.rb +622 -0
  50. data/spec/distinguished_name_spec.rb +24 -0
  51. data/spec/encrypted_document_spec.rb +21 -0
  52. data/spec/job_spec.rb +251 -0
  53. data/spec/local_state_spec.rb +130 -0
  54. data/spec/nanite_dispatcher_spec.rb +136 -0
  55. data/spec/packet_spec.rb +220 -0
  56. data/spec/rsa_key_pair_spec.rb +33 -0
  57. data/spec/secure_serializer_spec.rb +41 -0
  58. data/spec/serializer_spec.rb +107 -0
  59. data/spec/signature_spec.rb +30 -0
  60. data/spec/spec_helper.rb +33 -0
  61. data/spec/static_certificate_store_spec.rb +30 -0
  62. data/spec/util_spec.rb +63 -0
  63. metadata +129 -0
@@ -0,0 +1,49 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::CertificateCache do
4
+
5
+ before(:each) do
6
+ @cache = Nanite::CertificateCache.new(2)
7
+ end
8
+
9
+ it 'should allow storing and retrieving objects' do
10
+ @cache['some_id'].should be_nil
11
+ @cache['some_id'] = 'some_value'
12
+ @cache['some_id'].should == 'some_value'
13
+ end
14
+
15
+ it 'should not store more than required' do
16
+ @cache[1] = 'oldest'
17
+ @cache[2] = 'older'
18
+ @cache[1].should == 'oldest'
19
+ @cache[2].should == 'older'
20
+
21
+ @cache[3] = 'new'
22
+ @cache[3].should == 'new'
23
+
24
+ @cache[1].should be_nil
25
+ @cache[2].should == 'older'
26
+ end
27
+
28
+ it 'should use LRU to remove entries' do
29
+ @cache[1] = 'oldest'
30
+ @cache[2] = 'older'
31
+ @cache[1].should == 'oldest'
32
+ @cache[2].should == 'older'
33
+
34
+ @cache[1] = 'new'
35
+ @cache[3] = 'newer'
36
+ @cache[1].should == 'new'
37
+ @cache[3].should == 'newer'
38
+
39
+ @cache[2].should be_nil
40
+ end
41
+
42
+ it 'should store items returned by block' do
43
+ @cache[1].should be_nil
44
+ item = @cache.get(1) { 'item' }
45
+ item.should == 'item'
46
+ @cache[1].should == 'item'
47
+ end
48
+
49
+ end
@@ -0,0 +1,27 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::Certificate do
4
+
5
+ include SpecHelpers
6
+
7
+ before(:all) do
8
+ @certificate, key = issue_cert
9
+ end
10
+
11
+ it 'should save' do
12
+ filename = File.join(File.dirname(__FILE__), "cert.pem")
13
+ @certificate.save(filename)
14
+ File.size(filename).should be > 0
15
+ File.delete(filename)
16
+ end
17
+
18
+ it 'should load' do
19
+ filename = File.join(File.dirname(__FILE__), "cert.pem")
20
+ @certificate.save(filename)
21
+ cert = Nanite::Certificate.load(filename)
22
+ File.delete(filename)
23
+ cert.should_not be_nil
24
+ cert.data.should == @certificate.data
25
+ end
26
+
27
+ end
@@ -0,0 +1,622 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::Cluster do
4
+
5
+ include SpecHelpers
6
+
7
+ describe "Intialization" do
8
+
9
+ before(:each) do
10
+ @fanout = mock("fanout")
11
+ @binding = mock("binding", :subscribe => true)
12
+ @queue = mock("queue", :bind => @binding)
13
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
14
+ @serializer = mock("Serializer")
15
+ @reaper = mock("Reaper")
16
+ @mapper = mock("Mapper")
17
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
18
+ end
19
+
20
+ describe "of Heartbeat (Queue)" do
21
+
22
+ it "should setup the heartbeat (queue) for id" do
23
+ @amq.should_receive(:queue).with("heartbeat-the_identity", anything()).and_return(@queue)
24
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
25
+ end
26
+
27
+ it "should make the heartbeat (queue) exclusive" do
28
+ @amq.should_receive(:queue).with("heartbeat-the_identity", { :exclusive => true }).and_return(@queue)
29
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
30
+ end
31
+
32
+ it "should bind the heartbeat (queue) to 'heartbeat' fanout" do
33
+ @amq.should_receive(:fanout).with("heartbeat", { :durable => true }).and_return(@fanout)
34
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
35
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
36
+ end
37
+
38
+ end # of Heartbeat (Queue)
39
+
40
+
41
+ describe "of Registration (Queue)" do
42
+
43
+ it "should setup the registration (queue) for id" do
44
+ @amq.should_receive(:queue).with("registration-the_identity", anything()).and_return(@queue)
45
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
46
+ end
47
+
48
+ it "should make the registration (queue) exclusive" do
49
+ @amq.should_receive(:queue).with("registration-the_identity", { :exclusive => true }).and_return(@queue)
50
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
51
+ end
52
+
53
+ it "should bind the registration (queue) to 'registration' fanout" do
54
+ @amq.should_receive(:fanout).with("registration", { :durable => true }).and_return(@fanout)
55
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
56
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
57
+ end
58
+
59
+ end # of Registration (Queue)
60
+
61
+ describe "of Request (Queue)" do
62
+
63
+ it "should setup the request (queue) for id" do
64
+ @amq.should_receive(:queue).with("request-the_identity", anything()).and_return(@queue)
65
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
66
+ end
67
+
68
+ it "should make the request (queue) exclusive" do
69
+ @amq.should_receive(:queue).with("request-the_identity", { :exclusive => true }).and_return(@queue)
70
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
71
+ end
72
+
73
+ it "should bind the request (queue) to 'request' fanout" do
74
+ @amq.should_receive(:fanout).with("request", { :durable => true }).and_return(@fanout)
75
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
76
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
77
+ end
78
+
79
+ end # of Request (Queue)
80
+
81
+
82
+ describe "Reaper" do
83
+
84
+ it "should be created" do
85
+ Nanite::Reaper.should_receive(:new).with(anything()).and_return(@reaper)
86
+ cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
87
+ end
88
+
89
+ it "should use the agent timeout" do
90
+ Nanite::Reaper.should_receive(:new).with(443).and_return(@reaper)
91
+ cluster = Nanite::Cluster.new(@amq, 443, "the_identity", @serializer, @mapper)
92
+ end
93
+
94
+ end # Reaper
95
+
96
+ describe "State" do
97
+ begin
98
+ require 'nanite/state'
99
+ rescue LoadError
100
+ end
101
+
102
+ if defined?(Redis)
103
+ it "should use a local state by default" do
104
+ cluster = Nanite::Cluster.new(@amq, 443, "the_identity", @serializer, @mapper)
105
+ cluster.nanites.instance_of?(Nanite::LocalState).should == true
106
+ end
107
+
108
+ it "should set up a redis state when requested" do
109
+ state = Nanite::State.new("")
110
+ Nanite::State.should_receive(:new).with("localhost:1234").and_return(state)
111
+ cluster = Nanite::Cluster.new(@amq, 443, "the_identity", @serializer, @mapper, "localhost:1234")
112
+ cluster.nanites.instance_of?(Nanite::State).should == true
113
+ end
114
+ end
115
+ end
116
+ end # Intialization
117
+
118
+
119
+ describe "Target Selection" do
120
+
121
+ before(:each) do
122
+ @fanout = mock("fanout")
123
+ @binding = mock("binding", :subscribe => true)
124
+ @queue = mock("queue", :bind => @binding)
125
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
126
+ @serializer = mock("Serializer")
127
+ @reaper = mock("Reaper")
128
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
129
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
130
+
131
+ @all_known_nanites = [
132
+ ['nanite-1', {
133
+ :status => 0.21,
134
+ :services => ['/foo/bar', '/you/too'],
135
+ :tags => ['a', 'b', 'c'],
136
+ :timestamp => Time.now
137
+ }],
138
+ ['nanite-2', {
139
+ :status => 1.99,
140
+ :services => ['/foo/bar', '/you/too', '/maybe/he'],
141
+ :tags => ['b', 'c', 'e'],
142
+ :timestamp => Time.now
143
+ }],
144
+ ['nanite-3', {
145
+ :status => 0.5,
146
+ :services => ['/foo/bar', '/maybe/he'],
147
+ :tags => [],
148
+ :timestamp => Time.now - 60 * 10
149
+ }],
150
+ ['nanite-4', {
151
+ :status => 2.01,
152
+ :services => ['/foo/bar', '/you/too'],
153
+ :tags => ['a', 'b', 'c'],
154
+ :timestamp => Time.now - 10
155
+ }],
156
+ ]
157
+ end
158
+
159
+ it "should return array containing targets for request" do
160
+ target = mock("Supplied Target")
161
+ request = mock("Request", :target => target)
162
+ @cluster.targets_for(request).should be_instance_of(Array)
163
+ end
164
+
165
+ it "should use target from request" do
166
+ target = mock("Supplied Target")
167
+ request = mock("Request", :target => target)
168
+ @cluster.targets_for(request).should == [target]
169
+ end
170
+
171
+ it "should use targets choosen by least loaded selector (:least_loaded)" do
172
+ request = mock("Request", :target => nil, :selector => :least_loaded, :type => "/foo/bar", :tags => [])
173
+ @cluster.should_receive(:nanites_providing).with('/foo/bar', []).and_return(@all_known_nanites)
174
+
175
+ @cluster.targets_for(request).should == ["nanite-1"]
176
+ end
177
+
178
+ it "should use targets choosen by all selector (:all)" do
179
+ request = mock("Request", :target => nil, :selector => :all, :type => "/foo/bar", :tags => [])
180
+ @cluster.should_receive(:nanites_providing).with('/foo/bar', []).and_return(@all_known_nanites)
181
+
182
+ @cluster.targets_for(request).should == ["nanite-1", "nanite-2", "nanite-3", "nanite-4"]
183
+ end
184
+
185
+ it "should use targets choosen by random selector (:random)" do
186
+ request = mock("Request", :target => nil, :selector => :random, :type => "/foo/bar", :tags => [])
187
+ @cluster.should_receive(:nanites_providing).with('/foo/bar', []).and_return(@all_known_nanites)
188
+
189
+ @cluster.should_receive(:rand).with(4).and_return(2)
190
+ @cluster.targets_for(request).should == ["nanite-3"]
191
+ end
192
+
193
+ it "should use targets choosen by round-robin selector (:rr)" do
194
+ request = mock("Request", :target => nil, :selector => :rr, :type => "/foo/bar", :tags => [])
195
+ @cluster.stub!(:nanites_providing).with('/foo/bar', []).and_return(@all_known_nanites)
196
+
197
+ @cluster.instance_variable_set("@last", {})
198
+
199
+ @cluster.targets_for(request).should == ["nanite-1"]
200
+ @cluster.targets_for(request).should == ["nanite-2"]
201
+ @cluster.targets_for(request).should == ["nanite-3"]
202
+ @cluster.targets_for(request).should == ["nanite-4"]
203
+ @cluster.targets_for(request).should == ["nanite-1"]
204
+ @cluster.targets_for(request).should == ["nanite-2"]
205
+ @cluster.targets_for(request).should == ["nanite-3"]
206
+ @cluster.targets_for(request).should == ["nanite-4"]
207
+ end
208
+
209
+ it "should pass the tag filter down" do
210
+ request = mock("Request", :target => nil, :selector => :least_loaded, :type => "/foo/bar", :tags => ['a'])
211
+ @cluster.should_receive(:nanites_providing).with('/foo/bar', ['a']).and_return(@all_known_nanites)
212
+
213
+ @cluster.targets_for(request).should == ["nanite-1"]
214
+ end
215
+
216
+ it "should ignore timedout nanites" do
217
+ @all_known_nanites[0][1][:timestamp] = Time.local(2000)
218
+
219
+ nanites = mock("Nanites", :nanites_for => @all_known_nanites)
220
+ @cluster.should_receive(:nanites).and_return(nanites)
221
+
222
+ request = mock("Request", :target => nil, :selector => :least_loaded, :type => "/foo/bar", :tags => [])
223
+
224
+ @cluster.targets_for(request).should == ["nanite-2"]
225
+ end
226
+
227
+ it "should ignore timedout nanites - even when loading all nanites" do
228
+ @all_known_nanites[0][1][:timestamp] = Time.local(2000)
229
+
230
+ nanites = mock("Nanites", :nanites_for => @all_known_nanites)
231
+ @cluster.should_receive(:nanites).and_return(nanites)
232
+
233
+ request = mock("Request", :target => nil, :selector => :all, :type => "/foo/bar", :tags => [])
234
+
235
+ @cluster.targets_for(request).should == ["nanite-2", "nanite-4"]
236
+ end
237
+
238
+ end # Target Selection
239
+
240
+
241
+ describe "Nanite Registration" do
242
+
243
+ before(:each) do
244
+ @fanout = mock("fanout")
245
+ @binding = mock("binding", :subscribe => true)
246
+ @queue = mock("queue", :bind => @binding)
247
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
248
+ @serializer = mock("Serializer")
249
+ @reaper = mock("Reaper", :register => true)
250
+ Nanite::Log.stub!(:info)
251
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
252
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
253
+ @register_packet = Nanite::Register.new("nanite_id", ["the_nanite_services"], "nanite_status",[])
254
+ end
255
+
256
+ it "should add the Nanite to the nanites map" do
257
+ @cluster.register(@register_packet)
258
+ @cluster.nanites['nanite_id'].should_not be_nil
259
+ end
260
+
261
+ it "should use hash of the Nanite's services and status as value" do
262
+ @cluster.register(@register_packet)
263
+ @cluster.nanites['nanite_id'].keys.size == 2
264
+ @cluster.nanites['nanite_id'].keys.should include(:services)
265
+ @cluster.nanites['nanite_id'].keys.should include(:status)
266
+ @cluster.nanites['nanite_id'][:services].should == ["the_nanite_services"]
267
+ @cluster.nanites['nanite_id'][:status].should == "nanite_status"
268
+ end
269
+
270
+ it "should add nanite to reaper" do
271
+ @reaper.should_receive(:register).with('nanite_id', 33)
272
+ @cluster.register(@register_packet)
273
+ end
274
+
275
+ it "should log info message that nanite was registered" do
276
+ Nanite::Log.should_receive(:info)
277
+ @cluster.register(@register_packet)
278
+ end
279
+
280
+ it "should save the timestamp that the nanite was updated" do
281
+ @cluster.register(@register_packet)
282
+ @cluster.nanites['nanite_id'][:timestamp].should be_close(Time.now.utc.to_i, 1)
283
+ end
284
+
285
+ describe "with registered callbacks" do
286
+ before(:each) do
287
+ @register_callback = lambda {|request, mapper|}
288
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper, nil, :register => @register_callback)
289
+ end
290
+
291
+ it "should call the registration callback" do
292
+ @register_callback.should_receive(:call).with("nanite_id", @mapper)
293
+ @cluster.register(@register_packet)
294
+ end
295
+ end
296
+
297
+ describe "when sending an invalid packet to the registration queue" do
298
+ it "should log a message statement" do
299
+ Nanite::Log.logger.should_receive(:warn).with("RECV [register] Invalid packet type: Nanite::Ping")
300
+ @cluster.register(Nanite::Ping.new(nil, nil))
301
+ end
302
+ end
303
+ end # Nanite Registration
304
+
305
+ describe "Unregister" do
306
+ before(:each) do
307
+ @fanout = mock("fanout")
308
+ @binding = mock("binding", :subscribe => true)
309
+ @queue = mock("queue", :bind => @binding)
310
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
311
+ @serializer = mock("Serializer")
312
+ @reaper = mock("Reaper", :timeout => true, :unregister => nil)
313
+ Nanite::Log.stub!(:info)
314
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
315
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
316
+ @cluster.nanites["nanite_id"] = "nanite_id"
317
+ @unregister_packet = Nanite::UnRegister.new("nanite_id")
318
+ end
319
+
320
+ it "should delete the nanite" do
321
+ @cluster.register(@unregister_packet)
322
+ @cluster.nanites["nanite_id"].should == nil
323
+ end
324
+
325
+ it "should unregister the nanite from the reaper" do
326
+ @reaper.should_receive(:unregister).with('nanite_id')
327
+ @cluster.register(@unregister_packet)
328
+ end
329
+
330
+ describe "with registered callbacks" do
331
+ before(:each) do
332
+ @unregister_callback = lambda {|request, mapper| }
333
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper, nil, :unregister => @unregister_callback)
334
+ @cluster.nanites["nanite_id"] = "nanite_id"
335
+ end
336
+
337
+ it "should call the unregister callback" do
338
+ @unregister_callback.should_receive(:call).with("nanite_id", @mapper)
339
+ @cluster.register(@unregister_packet)
340
+ end
341
+ end
342
+ end
343
+
344
+ describe "Nanite timed out" do
345
+ before(:each) do
346
+ @fanout = mock("fanout")
347
+ @binding = mock("binding", :subscribe => true)
348
+ @queue = mock("queue", :bind => @binding)
349
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
350
+ @serializer = mock("Serializer")
351
+ Nanite::Log.stub!(:info)
352
+ @register_packet = Nanite::Register.new("nanite_id", ["the_nanite_services"], "nanite_status",[])
353
+ end
354
+
355
+ it "should remove the nanite when timed out" do
356
+ EM.run do
357
+ @cluster = Nanite::Cluster.new(@amq, 0.01, "the_identity", @serializer, @mapper)
358
+ @cluster.register(@register_packet)
359
+ EM.add_timer(1.1) {
360
+ @cluster.nanites["nanite_id"].should == nil
361
+ EM.stop_event_loop
362
+ }
363
+ end
364
+ end
365
+
366
+ it "should call the timed out callback handler when registered" do
367
+ EM.run do
368
+ @cluster = Nanite::Cluster.new(@amq, 0.01, "the_identity", @serializer, @mapper)
369
+ @cluster.register(@register_packet)
370
+ @cluster.should_receive(:nanite_timed_out).twice
371
+ EM.add_timer(1.1) {
372
+ EM.stop_event_loop
373
+ }
374
+ end
375
+ end
376
+
377
+ it "should not remove the agent when the callback returned false" do
378
+ EM.run do
379
+ @cluster = Nanite::Cluster.new(@amq, 0.01, "the_identity", @serializer, @mapper)
380
+ @cluster.register(@register_packet)
381
+ @cluster.stub!(:nanite_timed_out).and_return(false)
382
+ EM.add_timer(1.1) {
383
+ @cluster.nanites["nanite_id"].should_not == nil
384
+ EM.stop_event_loop
385
+ }
386
+ end
387
+ end
388
+ end
389
+
390
+ describe "nanite_timed_out" do
391
+ before(:each) do
392
+ @fanout = mock("fanout")
393
+ @binding = mock("binding", :subscribe => true)
394
+ @queue = mock("queue", :bind => @binding)
395
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
396
+ @serializer = mock("Serializer")
397
+ end
398
+
399
+ it "should remove the nanite from the list" do
400
+ run_in_em do
401
+ cluster = Nanite::Cluster.new(@amq, 0, "the_identity", @serializer, @mapper)
402
+ cluster.nanites['123456'] = { :services => ['/ls/al'], :status => 0.03, :tags => nil, :timestamp => Time.now.utc.to_i - 1 }
403
+
404
+ cluster.nanite_timed_out("123456")
405
+ cluster.nanites.should == {}
406
+ end
407
+ end
408
+
409
+ it "should not remove the nanite from the list when it was updated with the timeout timeframe" do
410
+ run_in_em do
411
+ cluster = Nanite::Cluster.new(@amq, 5, "the_identity", @serializer, @mapper)
412
+ cluster.nanites['123456'] = { :services => ['/ls/al'], :status => 0.03, :tags => nil, :timestamp => Time.now.utc.to_i - 2 }
413
+ cluster.nanite_timed_out("123456")
414
+ cluster.nanites.should_not == {}
415
+ end
416
+ end
417
+
418
+ it "should not fail when the Nanite wasn't found in the state storage" do
419
+ run_in_em do
420
+ cluster = Nanite::Cluster.new(@amq, 5, "the_identity", @serializer, @mapper)
421
+ cluster.nanites['654321'] = {}
422
+ lambda do
423
+ cluster.nanite_timed_out("123456")
424
+ end.should_not raise_error
425
+ end
426
+ end
427
+ end
428
+
429
+ describe "Route" do
430
+
431
+ before(:each) do
432
+ @fanout = mock("fanout")
433
+ @binding = mock("binding", :subscribe => true)
434
+ @queue = mock("queue", :bind => @binding)
435
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
436
+ @serializer = mock("Serializer")
437
+ @reaper = mock("Reaper")
438
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
439
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
440
+ @request = mock("Request")
441
+ end
442
+
443
+ it "should publish request to all targets" do
444
+ target1 = mock("Target 1")
445
+ target2 = mock("Target 2")
446
+ @cluster.should_receive(:publish).with(@request, target1)
447
+ @cluster.should_receive(:publish).with(@request, target2)
448
+ EM.run {
449
+ @cluster.route(@request, [target1, target2])
450
+ EM.stop
451
+ }
452
+ end
453
+
454
+ end # Route
455
+
456
+
457
+ describe "Publish" do
458
+
459
+ before(:each) do
460
+ @fanout = mock("fanout")
461
+ @binding = mock("binding", :subscribe => true)
462
+ @queue = mock("queue", :bind => @binding, :publish => true)
463
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
464
+ @serializer = mock("Serializer", :dump => "dumped_value")
465
+ @reaper = mock("Reaper")
466
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
467
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
468
+ @request = mock("Request", :persistent => true, :target => nil, :target= => nil, :to_s => nil)
469
+ @target = mock("Target of Request")
470
+ end
471
+
472
+ it "should serialize request before publishing it" do
473
+ @request.should_receive(:target=).with(@target)
474
+ @request.should_receive(:target=)
475
+ @request.should_receive(:target)
476
+ @serializer.should_receive(:dump).with(@request).and_return("serialized_request")
477
+ @cluster.publish(@request, @target)
478
+ end
479
+
480
+ it "should publish request to target queue" do
481
+ @request.should_receive(:target=).with(@target)
482
+ @request.should_receive(:target=)
483
+ @request.should_receive(:target)
484
+ @queue.should_receive(:publish).with("dumped_value", anything())
485
+ @cluster.publish(@request, @target)
486
+ end
487
+
488
+ it "should persist request based on request setting" do
489
+ @request.should_receive(:target=).with(@target)
490
+ @request.should_receive(:target=)
491
+ @request.should_receive(:target)
492
+ @request.should_receive(:persistent).and_return(false)
493
+ @queue.should_receive(:publish).with(anything(), { :persistent => false })
494
+ @cluster.publish(@request, @target)
495
+ end
496
+
497
+ end # Publish
498
+
499
+ describe "Agent Request Handling" do
500
+
501
+ before(:each) do
502
+ @fanout = mock("fanout")
503
+ @binding = mock("binding", :subscribe => true)
504
+ @queue = mock("queue", :bind => @binding, :publish => true)
505
+ @amq = mock("AMPQueue", :queue => @queue, :fanout => @fanout)
506
+ @serializer = mock("Serializer", :dump => "dumped_value")
507
+ @target = mock("Target of Request")
508
+ @reaper = mock("Reaper")
509
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
510
+ @request_without_target = mock("Request", :target => nil, :token => "Token",
511
+ :reply_to => "Reply To", :from => "From", :persistent => true, :identity => "Identity",
512
+ :payload => "Payload", :to_s => nil)
513
+ @request_with_target = mock("Request", :target => "Target", :token => "Token",
514
+ :reply_to => "Reply To", :from => "From", :persistent => true, :payload => "Payload", :to_s => nil)
515
+ @mapper_with_target = mock("Mapper", :identity => "id")
516
+ @mapper_without_target = mock("Mapper", :request => false, :identity => @request_without_target.identity)
517
+ @cluster_with_target = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper_with_target)
518
+ @cluster_without_target = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper_without_target)
519
+ Nanite::Cluster.stub!(:mapper).and_return(@mapper)
520
+ end
521
+
522
+ it "should forward requests with targets" do
523
+ @mapper_with_target.should_receive(:send_request).with(@request_with_target, anything())
524
+ @cluster_with_target.__send__(:handle_request, @request_with_target)
525
+ end
526
+
527
+ it "should reply back with nil results for requests with no target when offline queue is disabled" do
528
+ @mapper_without_target.should_receive(:send_request).with(@request_without_target, anything())
529
+ Nanite::Result.should_receive(:new).with(@request_without_target.token, @request_without_target.from, nil, @request_without_target.identity)
530
+ @cluster_without_target.__send__(:handle_request, @request_without_target)
531
+ end
532
+
533
+ it "should hand in an intermediate handler" do
534
+ @mapper_with_target.should_receive(:send_request) do |request, opts|
535
+ opts[:intermediate_handler].should be_instance_of(Proc)
536
+ end
537
+
538
+ @cluster_with_target.__send__(:handle_request, @request_with_target)
539
+ end
540
+
541
+ it "should forward the message when send_request failed" do
542
+ @mapper_with_target.stub!(:send_request).and_return(false)
543
+ @cluster_with_target.should_receive(:forward_response)
544
+ @cluster_with_target.__send__(:handle_request, @request_with_target)
545
+ end
546
+
547
+ describe "when getting push requests from an agent" do
548
+ it "should send the push message through the mapper" do
549
+ push = Nanite::Push.new(nil, nil)
550
+ @mapper_with_target.should_receive(:send_push).with(push)
551
+ @cluster_with_target.__send__(:handle_request, push)
552
+ end
553
+ end
554
+ end # Agent Request Handling
555
+
556
+ describe "Heartbeat" do
557
+ before(:each) do
558
+ @fanout = mock("fanout")
559
+ @binding = mock("binding", :subscribe => true)
560
+ @queue = mock("queue", :bind => @binding, :publish => true)
561
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
562
+ @serializer = mock("Serializer", :dump => "dumped_value")
563
+ Nanite::Log.stub!(:info)
564
+ @ping = stub("ping", :status => 0.3, :identity => "nanite_id")
565
+ end
566
+
567
+ it "should update the nanite status" do
568
+ run_in_em do
569
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
570
+ @cluster.nanites["nanite_id"] = {:status => "nanite_status"}
571
+ @cluster.send :handle_ping, @ping
572
+ @cluster.nanites["nanite_id"][:status].should == 0.3
573
+ end
574
+ end
575
+
576
+ it "should reset the agent time out" do
577
+ run_in_em do
578
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
579
+ @cluster.reaper.should_receive(:update).with("nanite_id", 33)
580
+ @cluster.nanites["nanite_id"] = {:status => "nanite_status"}
581
+ @cluster.send :handle_ping, @ping
582
+ end
583
+ end
584
+
585
+ it "should store the timestamp of the last update" do
586
+ run_in_em do
587
+ cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
588
+ cluster.reaper.should_receive(:update).with("nanite_id", 33)
589
+ cluster.nanites["nanite_id"] = {:status => "nanite_status"}
590
+ cluster.send :handle_ping, @ping
591
+ cluster.nanites["nanite_id"][:timestamp].should be_close(Time.now.utc.to_i, 1)
592
+ end
593
+ end
594
+
595
+ describe "when timing out after a heartbeat" do
596
+ it "should remove the nanite" do
597
+ run_in_em(false) do
598
+ @cluster = Nanite::Cluster.new(@amq, 0.1, "the_identity", @serializer, @mapper)
599
+ @cluster.nanites["nanite_id"] = {:status => "nanite_status"}
600
+ @cluster.send :handle_ping, @ping
601
+ EM.add_timer(1.5) do
602
+ @cluster.nanites["nanite_id"].should == nil
603
+ EM.stop_event_loop
604
+ end
605
+ end
606
+ end
607
+
608
+ it "should call the timeout callback when defined" do
609
+ run_in_em(false) do
610
+ @timeout_callback = lambda {|nanite, mapper| }
611
+ @timeout_callback.should_receive(:call).with("nanite_id", @mapper)
612
+ @cluster = Nanite::Cluster.new(@amq, 0.1, "the_identity", @serializer, @mapper, nil, :timeout => @timeout_callback)
613
+ @cluster.nanites["nanite_id"] = {:status => "nanite_status"}
614
+ @cluster.send :handle_ping, @ping
615
+ EM.add_timer(1.5) do
616
+ EM.stop_event_loop
617
+ end
618
+ end
619
+ end
620
+ end
621
+ end
622
+ end # Nanite::Cluster