ezmobius-nanite 0.4.0 → 0.4.1.1

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 (60) hide show
  1. data/README.rdoc +70 -20
  2. data/Rakefile +1 -1
  3. data/bin/nanite-agent +34 -8
  4. data/bin/nanite-mapper +18 -8
  5. data/lib/nanite.rb +71 -0
  6. data/lib/nanite/actor.rb +60 -0
  7. data/lib/nanite/actor_registry.rb +24 -0
  8. data/lib/nanite/admin.rb +138 -0
  9. data/lib/nanite/agent.rb +250 -0
  10. data/lib/nanite/amqp.rb +47 -0
  11. data/lib/nanite/cluster.rb +203 -0
  12. data/lib/nanite/config.rb +102 -0
  13. data/lib/nanite/console.rb +39 -0
  14. data/lib/nanite/daemonize.rb +13 -0
  15. data/lib/nanite/dispatcher.rb +90 -0
  16. data/lib/nanite/identity.rb +16 -0
  17. data/lib/nanite/job.rb +104 -0
  18. data/lib/nanite/local_state.rb +34 -0
  19. data/lib/nanite/log.rb +64 -0
  20. data/lib/nanite/log/formatter.rb +39 -0
  21. data/lib/nanite/mapper.rb +277 -0
  22. data/lib/nanite/mapper_proxy.rb +56 -0
  23. data/lib/nanite/packets.rb +231 -0
  24. data/lib/nanite/pid_file.rb +52 -0
  25. data/lib/nanite/reaper.rb +38 -0
  26. data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
  27. data/lib/nanite/security/certificate.rb +55 -0
  28. data/lib/nanite/security/certificate_cache.rb +66 -0
  29. data/lib/nanite/security/distinguished_name.rb +34 -0
  30. data/lib/nanite/security/encrypted_document.rb +46 -0
  31. data/lib/nanite/security/rsa_key_pair.rb +53 -0
  32. data/lib/nanite/security/secure_serializer.rb +67 -0
  33. data/lib/nanite/security/signature.rb +40 -0
  34. data/lib/nanite/security/static_certificate_store.rb +35 -0
  35. data/lib/nanite/security_provider.rb +47 -0
  36. data/lib/nanite/serializer.rb +52 -0
  37. data/lib/nanite/state.rb +164 -0
  38. data/lib/nanite/streaming.rb +125 -0
  39. data/lib/nanite/util.rb +51 -0
  40. data/spec/actor_registry_spec.rb +62 -0
  41. data/spec/actor_spec.rb +59 -0
  42. data/spec/agent_spec.rb +235 -0
  43. data/spec/cached_certificate_store_proxy_spec.rb +34 -0
  44. data/spec/certificate_cache_spec.rb +49 -0
  45. data/spec/certificate_spec.rb +27 -0
  46. data/spec/cluster_spec.rb +300 -0
  47. data/spec/dispatcher_spec.rb +136 -0
  48. data/spec/distinguished_name_spec.rb +24 -0
  49. data/spec/encrypted_document_spec.rb +21 -0
  50. data/spec/job_spec.rb +219 -0
  51. data/spec/local_state_spec.rb +112 -0
  52. data/spec/packet_spec.rb +218 -0
  53. data/spec/rsa_key_pair_spec.rb +33 -0
  54. data/spec/secure_serializer_spec.rb +41 -0
  55. data/spec/serializer_spec.rb +107 -0
  56. data/spec/signature_spec.rb +30 -0
  57. data/spec/spec_helper.rb +23 -0
  58. data/spec/static_certificate_store_spec.rb +30 -0
  59. data/spec/util_spec.rb +63 -0
  60. metadata +63 -2
@@ -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,300 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::Cluster do
4
+
5
+ describe "Intialization" do
6
+
7
+ before(:each) do
8
+ @fanout = mock("fanout")
9
+ @binding = mock("binding", :subscribe => true)
10
+ @queue = mock("queue", :bind => @binding)
11
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
12
+ @serializer = mock("Serializer")
13
+ @reaper = mock("Reaper")
14
+ @mapper = mock("Mapper")
15
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
16
+ end
17
+
18
+ describe "of Heartbeat (Queue)" do
19
+
20
+ it "should setup the heartbeat (queue) for id" do
21
+ @amq.should_receive(:queue).with("heartbeat-the_identity", anything()).and_return(@queue)
22
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
23
+ end
24
+
25
+ it "should make the heartbeat (queue) exclusive" do
26
+ @amq.should_receive(:queue).with("heartbeat-the_identity", { :exclusive => true }).and_return(@queue)
27
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
28
+ end
29
+
30
+ it "should bind the heartbeat (queue) to 'heartbeat' fanout" do
31
+ @amq.should_receive(:fanout).with("heartbeat", { :durable => true }).and_return(@fanout)
32
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
33
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
34
+ end
35
+
36
+ end # of Heartbeat (Queue)
37
+
38
+
39
+ describe "of Registration (Queue)" do
40
+
41
+ it "should setup the registration (queue) for id" do
42
+ @amq.should_receive(:queue).with("registration-the_identity", anything()).and_return(@queue)
43
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
44
+ end
45
+
46
+ it "should make the registration (queue) exclusive" do
47
+ @amq.should_receive(:queue).with("registration-the_identity", { :exclusive => true }).and_return(@queue)
48
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
49
+ end
50
+
51
+ it "should bind the registration (queue) to 'registration' fanout" do
52
+ @amq.should_receive(:fanout).with("registration", { :durable => true }).and_return(@fanout)
53
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
54
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
55
+ end
56
+
57
+ end # of Registration (Queue)
58
+
59
+ describe "of Request (Queue)" do
60
+
61
+ it "should setup the request (queue) for id" do
62
+ @amq.should_receive(:queue).with("request-the_identity", anything()).and_return(@queue)
63
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
64
+ end
65
+
66
+ it "should make the request (queue) exclusive" do
67
+ @amq.should_receive(:queue).with("request-the_identity", { :exclusive => true }).and_return(@queue)
68
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
69
+ end
70
+
71
+ it "should bind the request (queue) to 'request' fanout" do
72
+ @amq.should_receive(:fanout).with("request", { :durable => true }).and_return(@fanout)
73
+ @queue.should_receive(:bind).with(@fanout).and_return(@binding)
74
+ cluster = Nanite::Cluster.new(@amq, 10, "the_identity", @serializer, @mapper)
75
+ end
76
+
77
+ end # of Request (Queue)
78
+
79
+
80
+ describe "Reaper" do
81
+
82
+ it "should be created" do
83
+ Nanite::Reaper.should_receive(:new).with(anything()).and_return(@reaper)
84
+ cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
85
+ end
86
+
87
+ it "should use the agent timeout" do
88
+ Nanite::Reaper.should_receive(:new).with(443).and_return(@reaper)
89
+ cluster = Nanite::Cluster.new(@amq, 443, "the_identity", @serializer, @mapper)
90
+ end
91
+
92
+ end # Reaper
93
+
94
+ end # Intialization
95
+
96
+
97
+ describe "Target Selection" do
98
+
99
+ before(:each) do
100
+ @fanout = mock("fanout")
101
+ @binding = mock("binding", :subscribe => true)
102
+ @queue = mock("queue", :bind => @binding)
103
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
104
+ @serializer = mock("Serializer")
105
+ @reaper = mock("Reaper")
106
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
107
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
108
+ end
109
+
110
+ it "should return array containing targets for request" do
111
+ target = mock("Supplied Target")
112
+ request = mock("Request", :target => target)
113
+ @cluster.targets_for(request).should be_instance_of(Array)
114
+ end
115
+
116
+ it "should use target from request" do
117
+ target = mock("Supplied Target")
118
+ request = mock("Request", :target => target)
119
+ @cluster.targets_for(request).should == [target]
120
+ end
121
+
122
+ it "should use targets choosen by least loaded selector (:least_loaded)" do
123
+ targets = { "target 3" => 3 }
124
+ request = mock("Request", :target => nil, :selector => :least_loaded, :type => "service", :tags => [])
125
+ @cluster.should_receive(:least_loaded).with("service", []).and_return(targets)
126
+ @cluster.targets_for(request).should == ["target 3"]
127
+ end
128
+
129
+ it "should use targets choosen by all selector (:all)" do
130
+ targets = { "target 1" => 1, "target 2" => 2, "target 3" => 3 }
131
+ request = mock("Request", :target => nil, :selector => :all, :type => "service", :tags => [])
132
+ @cluster.should_receive(:all).with("service", []).and_return(targets)
133
+ @cluster.targets_for(request).should == ["target 1", "target 2", "target 3"]
134
+ end
135
+
136
+ it "should use targets choosen by random selector (:random)" do
137
+ targets = { "target 3" => 3 }
138
+ request = mock("Request", :target => nil, :selector => :random, :type => "service", :tags => [])
139
+ @cluster.should_receive(:random).with("service", []).and_return(targets)
140
+ @cluster.targets_for(request).should == ["target 3"]
141
+ end
142
+
143
+ it "should use targets choosen by round-robin selector (:rr)" do
144
+ targets = { "target 2" => 2 }
145
+ request = mock("Request", :target => nil, :selector => :rr, :type => "service", :tags => [])
146
+ @cluster.should_receive(:rr).with("service", []).and_return(targets)
147
+ @cluster.targets_for(request).should == ["target 2"]
148
+ end
149
+
150
+ end # Target Selection
151
+
152
+
153
+ describe "Nanite Registration" do
154
+
155
+ before(:each) do
156
+ @fanout = mock("fanout")
157
+ @binding = mock("binding", :subscribe => true)
158
+ @queue = mock("queue", :bind => @binding)
159
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
160
+ @serializer = mock("Serializer")
161
+ @reaper = mock("Reaper", :timeout => true)
162
+ Nanite::Log.stub!(:info)
163
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
164
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
165
+ @register_packet = Nanite::Register.new("nanite_id", ["the_nanite_services"], "nanite_status",[])
166
+ end
167
+
168
+ it "should add the Nanite to the nanites map" do
169
+ @cluster.register(@register_packet)
170
+ @cluster.nanites['nanite_id'].should_not be_nil
171
+ end
172
+
173
+ it "should use hash of the Nanite's services and status as value" do
174
+ @cluster.register(@register_packet)
175
+ @cluster.nanites['nanite_id'].keys.size == 2
176
+ @cluster.nanites['nanite_id'].keys.should include(:services)
177
+ @cluster.nanites['nanite_id'].keys.should include(:status)
178
+ @cluster.nanites['nanite_id'][:services].should == ["the_nanite_services"]
179
+ @cluster.nanites['nanite_id'][:status].should == "nanite_status"
180
+ end
181
+
182
+ it "should add nanite to reaper" do
183
+ @reaper.should_receive(:timeout).with('nanite_id', 33)
184
+ @cluster.register(@register_packet)
185
+ end
186
+
187
+ it "should log info message that nanite was registered" do
188
+ Nanite::Log.should_receive(:info)
189
+ @cluster.register(@register_packet)
190
+ end
191
+
192
+ end # Nanite Registration
193
+
194
+
195
+ describe "Route" do
196
+
197
+ before(:each) do
198
+ @fanout = mock("fanout")
199
+ @binding = mock("binding", :subscribe => true)
200
+ @queue = mock("queue", :bind => @binding)
201
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
202
+ @serializer = mock("Serializer")
203
+ @reaper = mock("Reaper")
204
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
205
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
206
+ @request = mock("Request")
207
+ end
208
+
209
+ it "should publish request to all targets" do
210
+ target1 = mock("Target 1")
211
+ target2 = mock("Target 2")
212
+ @cluster.should_receive(:publish).with(@request, target1)
213
+ @cluster.should_receive(:publish).with(@request, target2)
214
+ EM.run {
215
+ @cluster.route(@request, [target1, target2])
216
+ EM.stop
217
+ }
218
+ end
219
+
220
+ end # Route
221
+
222
+
223
+ describe "Publish" do
224
+
225
+ before(:each) do
226
+ @fanout = mock("fanout")
227
+ @binding = mock("binding", :subscribe => true)
228
+ @queue = mock("queue", :bind => @binding, :publish => true)
229
+ @amq = mock("AMQueue", :queue => @queue, :fanout => @fanout)
230
+ @serializer = mock("Serializer", :dump => "dumped_value")
231
+ @reaper = mock("Reaper")
232
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
233
+ @cluster = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper)
234
+ @request = mock("Request", :persistent => true)
235
+ @target = mock("Target of Request")
236
+ end
237
+
238
+ it "should serialize request before publishing it" do
239
+ @request.should_receive(:target=).with(@target)
240
+ @request.should_receive(:target=)
241
+ @request.should_receive(:target)
242
+ @serializer.should_receive(:dump).with(@request).and_return("serialized_request")
243
+ @cluster.publish(@request, @target)
244
+ end
245
+
246
+ it "should publish request to target queue" do
247
+ @request.should_receive(:target=).with(@target)
248
+ @request.should_receive(:target=)
249
+ @request.should_receive(:target)
250
+ @queue.should_receive(:publish).with("dumped_value", anything())
251
+ @cluster.publish(@request, @target)
252
+ end
253
+
254
+ it "should persist request based on request setting" do
255
+ @request.should_receive(:target=).with(@target)
256
+ @request.should_receive(:target=)
257
+ @request.should_receive(:target)
258
+ @request.should_receive(:persistent).and_return(false)
259
+ @queue.should_receive(:publish).with(anything(), { :persistent => false })
260
+ @cluster.publish(@request, @target)
261
+ end
262
+
263
+ end # Publish
264
+
265
+ describe "Agent Request Handling" do
266
+
267
+ before(:each) do
268
+ @fanout = mock("fanout")
269
+ @binding = mock("binding", :subscribe => true)
270
+ @queue = mock("queue", :bind => @binding, :publish => true)
271
+ @amq = mock("AMPQueue", :queue => @queue, :fanout => @fanout)
272
+ @serializer = mock("Serializer", :dump => "dumped_value")
273
+ @target = mock("Target of Request")
274
+ @reaper = mock("Reaper")
275
+ Nanite::Reaper.stub!(:new).and_return(@reaper)
276
+ @request_without_target = mock("Request", :target => nil, :token => "Token",
277
+ :reply_to => "Reply To", :from => "From", :persistent => true, :identity => "Identity")
278
+ @request_with_target = mock("Request", :target => "Target", :token => "Token",
279
+ :reply_to => "Reply To", :from => "From", :persistent => true)
280
+ @mapper_with_target = mock("Mapper", :identity => "id")
281
+ @mapper_without_target = mock("Mapper", :request => false, :identity => @request_without_target.identity)
282
+ @cluster_with_target = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper_with_target)
283
+ @cluster_without_target = Nanite::Cluster.new(@amq, 32, "the_identity", @serializer, @mapper_without_target)
284
+ Nanite::Cluster.stub!(:mapper).and_return(@mapper)
285
+ end
286
+
287
+ it "should forward requests with targets" do
288
+ @mapper_with_target.should_receive(:send_request).with(@request_with_target, anything())
289
+ @cluster_with_target.__send__(:handle_request, @request_with_target)
290
+ end
291
+
292
+ it "should reply back with nil results for requests with no target when offline queue is disabled" do
293
+ @mapper_without_target.should_receive(:send_request).with(@request_without_target, anything())
294
+ Nanite::Result.should_receive(:new).with(@request_without_target.token, @request_without_target.from, nil, @request_without_target.identity)
295
+ @cluster_without_target.__send__(:handle_request, @request_without_target)
296
+ end
297
+
298
+ end # Agent Request Handling
299
+
300
+ end # Nanite::Cluster
@@ -0,0 +1,136 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ class Foo
4
+ include Nanite::Actor
5
+ expose :bar, :index, :i_kill_you
6
+ on_exception :handle_exception
7
+
8
+ def index(payload)
9
+ bar(payload)
10
+ end
11
+
12
+ def bar(payload)
13
+ ['hello', payload]
14
+ end
15
+
16
+ def bar2(payload, deliverable)
17
+ deliverable
18
+ end
19
+
20
+ def i_kill_you(payload)
21
+ raise RuntimeError.new('I kill you!')
22
+ end
23
+
24
+ def handle_exception(method, deliverable, error)
25
+ end
26
+ end
27
+
28
+ class Bar
29
+ include Nanite::Actor
30
+ expose :i_kill_you
31
+ on_exception do |method, deliverable, error|
32
+ @scope = self
33
+ @called_with = [method, deliverable, error]
34
+ end
35
+
36
+ def i_kill_you(payload)
37
+ raise RuntimeError.new('I kill you!')
38
+ end
39
+ end
40
+
41
+ # No specs, simply ensures multiple methods for assigning on_exception callback,
42
+ # on_exception raises exception when called with an invalid argument.
43
+ class Doomed
44
+ include Nanite::Actor
45
+ on_exception do
46
+ end
47
+ on_exception lambda {}
48
+ on_exception :doh
49
+ end
50
+
51
+ # Mock the EventMachine deferrer.
52
+ class EMMock
53
+ def self.defer(op = nil, callback = nil)
54
+ callback.call(op.call)
55
+ end
56
+ end
57
+
58
+ describe "Nanite::Dispatcher" do
59
+
60
+ before(:each) do
61
+ Nanite::Log.stub!(:info)
62
+ Nanite::Log.stub!(:error)
63
+ amq = mock('amq', :queue => mock('queue', :publish => nil))
64
+ @actor = Foo.new
65
+ @registry = Nanite::ActorRegistry.new
66
+ @registry.register(@actor, nil)
67
+ @dispatcher = Nanite::Dispatcher.new(amq, @registry, Nanite::Serializer.new(:marshal), '0xfunkymonkey', {})
68
+ @dispatcher.evmclass = EMMock
69
+ end
70
+
71
+ it "should dispatch a request" do
72
+ req = Nanite::Request.new('/foo/bar', 'you')
73
+ res = @dispatcher.dispatch(req)
74
+ res.should(be_kind_of(Nanite::Result))
75
+ res.token.should == req.token
76
+ res.results.should == ['hello', 'you']
77
+ end
78
+
79
+ it "should dispatch the deliverable to actions that accept it" do
80
+ req = Nanite::Request.new('/foo/bar2', 'you')
81
+ res = @dispatcher.dispatch(req)
82
+ res.should(be_kind_of(Nanite::Result))
83
+ res.token.should == req.token
84
+ res.results.should == req
85
+ end
86
+
87
+ it "should dispatch a request to the default action" do
88
+ req = Nanite::Request.new('/foo', 'you')
89
+ res = @dispatcher.dispatch(req)
90
+ res.should(be_kind_of(Nanite::Result))
91
+ res.token.should == req.token
92
+ res.results.should == ['hello', 'you']
93
+ end
94
+
95
+ it "should handle custom prefixes" do
96
+ @registry.register(Foo.new, 'umbongo')
97
+ req = Nanite::Request.new('/umbongo/bar', 'you')
98
+ res = @dispatcher.dispatch(req)
99
+ res.should(be_kind_of(Nanite::Result))
100
+ res.token.should == req.token
101
+ res.results.should == ['hello', 'you']
102
+ end
103
+
104
+ it "should call the on_exception callback if something goes wrong" do
105
+ req = Nanite::Request.new('/foo/i_kill_you', nil)
106
+ @actor.should_receive(:handle_exception).with(:i_kill_you, req, duck_type(:exception, :backtrace))
107
+ @dispatcher.dispatch(req)
108
+ end
109
+
110
+ it "should call on_exception Procs defined in a subclass with the correct arguments" do
111
+ actor = Bar.new
112
+ @registry.register(actor, nil)
113
+ req = Nanite::Request.new('/bar/i_kill_you', nil)
114
+ @dispatcher.dispatch(req)
115
+ called_with = actor.instance_variable_get("@called_with")
116
+ called_with[0].should == :i_kill_you
117
+ called_with[1].should == req
118
+ called_with[2].should be_kind_of(RuntimeError)
119
+ called_with[2].message.should == 'I kill you!'
120
+ end
121
+
122
+ it "should call on_exception Procs defined in a subclass in the scope of the actor" do
123
+ actor = Bar.new
124
+ @registry.register(actor, nil)
125
+ req = Nanite::Request.new('/bar/i_kill_you', nil)
126
+ @dispatcher.dispatch(req)
127
+ actor.instance_variable_get("@scope").should == actor
128
+ end
129
+
130
+ it "should log error if something goes wrong" do
131
+ Nanite::Log.should_receive(:error)
132
+ req = Nanite::Request.new('/foo/i_kill_you', nil)
133
+ @dispatcher.dispatch(req)
134
+ end
135
+
136
+ end # Nanite::Dispatcher