shift-nanite 0.4.1.2

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