nofxx-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 +258 -0
  13. data/lib/nanite/amqp.rb +54 -0
  14. data/lib/nanite/cluster.rb +236 -0
  15. data/lib/nanite/config.rb +111 -0
  16. data/lib/nanite/console.rb +39 -0
  17. data/lib/nanite/daemonize.rb +13 -0
  18. data/lib/nanite/dispatcher.rb +92 -0
  19. data/lib/nanite/identity.rb +16 -0
  20. data/lib/nanite/job.rb +104 -0
  21. data/lib/nanite/local_state.rb +34 -0
  22. data/lib/nanite/log.rb +66 -0
  23. data/lib/nanite/log/formatter.rb +39 -0
  24. data/lib/nanite/mapper.rb +310 -0
  25. data/lib/nanite/mapper_proxy.rb +67 -0
  26. data/lib/nanite/packets.rb +365 -0
  27. data/lib/nanite/pid_file.rb +52 -0
  28. data/lib/nanite/reaper.rb +38 -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 +164 -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 +485 -0
  50. data/spec/dispatcher_spec.rb +136 -0
  51. data/spec/distinguished_name_spec.rb +24 -0
  52. data/spec/encrypted_document_spec.rb +21 -0
  53. data/spec/job_spec.rb +251 -0
  54. data/spec/local_state_spec.rb +112 -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 +131 -0
@@ -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
@@ -0,0 +1,24 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::DistinguishedName do
4
+
5
+ before(:all) do
6
+ test_dn = { 'C' => 'US',
7
+ 'ST' => 'California',
8
+ 'L' => 'Santa Barbara',
9
+ 'O' => 'RightScale',
10
+ 'OU' => 'Certification Services',
11
+ 'CN' => 'rightscale.com/emailAddress=cert@rightscale.com' }
12
+ @dn = Nanite::DistinguishedName.new(test_dn)
13
+ end
14
+
15
+ it 'should convert to string and X509 DN' do
16
+ @dn.to_s.should_not be_nil
17
+ @dn.to_x509.should_not be_nil
18
+ end
19
+
20
+ it 'should correctly encode' do
21
+ @dn.to_s.should == @dn.to_x509.to_s
22
+ end
23
+
24
+ end
@@ -0,0 +1,21 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::EncryptedDocument do
4
+
5
+ include SpecHelpers
6
+
7
+ before(:all) do
8
+ @test_data = "Test Data to Sign"
9
+ @cert, @key = issue_cert
10
+ @doc = Nanite::EncryptedDocument.new(@test_data, @cert)
11
+ end
12
+
13
+ it 'should create encrypted data' do
14
+ @doc.encrypted_data.should_not be_nil
15
+ end
16
+
17
+ it 'should decrypt correctly' do
18
+ @doc.decrypted_data(@key, @cert).should == @test_data
19
+ end
20
+
21
+ end
@@ -0,0 +1,251 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+
3
+ describe Nanite::JobWarden do
4
+
5
+ describe "Creating a new Job" do
6
+
7
+ before(:each) do
8
+ @serializer = mock("Serializer")
9
+ @warden = Nanite::JobWarden.new(@serializer)
10
+
11
+ @request = mock("Request")
12
+ @targets = mock("Targets")
13
+ @job = mock("Job", :token => "3faba24fcc")
14
+ end
15
+
16
+ it "should instantiate a new Job" do
17
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
18
+ @warden.new_job(@request, @targets)
19
+ end
20
+
21
+ it "should add the job to the job list" do
22
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
23
+ @warden.jobs.size.should == 0
24
+ @warden.new_job(@request, @targets)
25
+ @warden.jobs.size.should == 1
26
+ @warden.jobs["3faba24fcc"].should == @job
27
+ end
28
+
29
+ it "return the newly crated job" do
30
+ Nanite::Job.should_receive(:new).with(@request, @targets, nil, nil).and_return(@job)
31
+ @warden.new_job(@request, @targets).should == @job
32
+ end
33
+
34
+ end # Creating a new Job
35
+
36
+ describe "Processing an intermediate message" do
37
+ before(:each) do
38
+ @intm_handler = lambda {|arg1, arg2, arg3| puts 'ehlo'}
39
+ @message = mock("Message", :token => "3faba24fcc", :from => 'nanite-agent')
40
+ @serializer = mock("Serializer", :load => @message)
41
+ @warden = Nanite::JobWarden.new(@serializer)
42
+ @job = Nanite::Job.new(stub("request", :token => "3faba24fcc"), [], @intm_handler)
43
+ @job.instance_variable_set(:@pending_keys, ["defaultkey"])
44
+ @job.instance_variable_set(:@intermediate_state, {"nanite-agent" => {"defaultkey" => [1]}})
45
+ @warden.jobs[@job.token] = @job
46
+ Nanite::Log.stub!(:debug)
47
+ Nanite::Log.stub!(:error)
48
+ end
49
+
50
+ it "should call the intermediate handler with three parameters" do
51
+ @intm_handler.should_receive(:call).with("defaultkey", "nanite-agent", 1)
52
+ @warden.process(@message)
53
+ end
54
+
55
+ it "should call the intermediate handler with four parameters" do
56
+ @intm_handler.stub!(:arity).and_return(4)
57
+ @intm_handler.should_receive(:call).with("defaultkey", "nanite-agent", 1, @job)
58
+ @warden.process(@message)
59
+ end
60
+
61
+ it "should not call the intermediate handler when it can't be found for the specified key" do
62
+ @intm_handler.should_not_receive(:call)
63
+ @job.instance_variable_set(:@intermediate_handler, nil)
64
+ @warden.process(@message)
65
+ end
66
+
67
+ it "should call the intermediate handler with one parameter which needs to be the result" do
68
+ @intm_handler.should_receive(:call).with(1, @job)
69
+ @intm_handler.stub!(:arity).and_return(2)
70
+ @warden.process(@message)
71
+ end
72
+ end
73
+
74
+ describe "Processing a Message" do
75
+
76
+ before(:each) do
77
+ @message = mock("Message", :token => "3faba24fcc")
78
+ @warden = Nanite::JobWarden.new(@serializer)
79
+ @job = mock("Job", :token => "3faba24fcc", :process => true, :completed? => false, :results => 42, :pending_keys => [], :intermediate_handler => true)
80
+
81
+ Nanite::Log.stub!(:debug)
82
+ end
83
+
84
+ it "should hand over processing to job" do
85
+ Nanite::Job.stub!(:new).and_return(@job)
86
+ @job.should_receive(:process).with(@message)
87
+
88
+ @warden.new_job("request", "targets")
89
+ @warden.process(@message)
90
+ end
91
+
92
+ it "should delete job from jobs after completion" do
93
+ Nanite::Job.stub!(:new).and_return(@job)
94
+ @job.should_receive(:process).with(@message)
95
+ @job.should_receive(:completed?).and_return(true)
96
+ @job.should_receive(:completed).and_return(nil)
97
+
98
+ @warden.jobs["3faba24fcc"].should be_nil
99
+ @warden.new_job("request", "targets")
100
+ @warden.jobs["3faba24fcc"].should == @job
101
+ @warden.process(@message)
102
+ @warden.jobs["3faba24fcc"].should be_nil
103
+ end
104
+
105
+ it "should call completed block after completion" do
106
+ completed_block = mock("Completed", :arity => 1, :call => true)
107
+
108
+ Nanite::Job.stub!(:new).and_return(@job)
109
+ @job.should_receive(:process).with(@message)
110
+ @job.should_receive(:completed?).and_return(true)
111
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
112
+
113
+ @warden.new_job("request", "targets")
114
+ @warden.process(@message)
115
+ end
116
+
117
+ it "should pass in job result if arity of completed block is one" do
118
+ completed_block = mock("Completed")
119
+
120
+ Nanite::Job.stub!(:new).and_return(@job)
121
+ @job.should_receive(:process).with(@message)
122
+ @job.should_receive(:completed?).and_return(true)
123
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
124
+ @job.should_receive(:results).and_return("the job result")
125
+ completed_block.should_receive(:arity).and_return(1)
126
+ completed_block.should_receive(:call).with("the job result")
127
+
128
+ @warden.new_job("request", "targets")
129
+ @warden.process(@message)
130
+ end
131
+
132
+ it "should pass in job result and job if arity of completed block is two" do
133
+ completed_block = mock("Completed")
134
+
135
+ Nanite::Job.stub!(:new).and_return(@job)
136
+ @job.should_receive(:process).with(@message)
137
+ @job.should_receive(:completed?).and_return(true)
138
+ @job.should_receive(:completed).exactly(3).times.and_return(completed_block)
139
+ @job.should_receive(:results).and_return("the job result")
140
+ completed_block.should_receive(:arity).and_return(2)
141
+ completed_block.should_receive(:call).with("the job result", @job)
142
+
143
+ @warden.new_job("request", "targets")
144
+ @warden.process(@message)
145
+ end
146
+
147
+ end # Processing a Message
148
+
149
+ end # Nanite::JobWarden
150
+
151
+
152
+ describe Nanite::Job do
153
+
154
+ describe "Creating a Job" do
155
+
156
+ before(:each) do
157
+ @request = mock("Request", :token => "af534ceaaacdcd")
158
+ end
159
+
160
+ it "should initialize the request" do
161
+ job = Nanite::Job.new(@request, nil, nil)
162
+ job.request.should == @request
163
+ end
164
+
165
+ it "should initialize the targets" do
166
+ job = Nanite::Job.new(@request, "targets", nil)
167
+ job.targets.should == "targets"
168
+ end
169
+
170
+ it "should initialize the job token to the request token" do
171
+ job = Nanite::Job.new(@request, nil, nil)
172
+ job.token.should == "af534ceaaacdcd"
173
+ end
174
+
175
+ it "should initialize the results to an empty hash" do
176
+ job = Nanite::Job.new(@request, nil, nil)
177
+ job.results.should == {}
178
+ end
179
+
180
+ it "should initialize the intermediate state to an empty hash" do
181
+ job = Nanite::Job.new(@request, nil, nil)
182
+ job.intermediate_state.should == {}
183
+ end
184
+
185
+ it "should initialize the job block" do
186
+ job = Nanite::Job.new(@request, nil, nil, "my block")
187
+ job.completed.should == "my block"
188
+ end
189
+
190
+ end # Creating a new Job
191
+
192
+
193
+ describe "Processing a Message" do
194
+
195
+ before(:each) do
196
+ @request = mock("Request", :token => "feeefe132")
197
+ end
198
+
199
+ it "should set the job result (for sender) to the message result for 'final' status messages" do
200
+ job = Nanite::Job.new(@request, [], nil)
201
+ message = Nanite::Result.new('token', 'to', 'results', 'from')
202
+ job.results.should == {}
203
+ job.process(message)
204
+ job.results.should == { 'from' => 'results' }
205
+ end
206
+
207
+ it "should delete the message sender from the targets for 'final' status messages" do
208
+ job = Nanite::Job.new(@request, ['from'], nil)
209
+ message = Nanite::Result.new('token', 'to', 'results', 'from')
210
+ job.targets.should == ['from']
211
+ job.process(message)
212
+ job.targets.should == []
213
+ end
214
+
215
+ it "should set the job result (for sender) to the message result for 'intermediate' status messages" do
216
+ job = Nanite::Job.new(@request, ['from'], nil)
217
+ message = Nanite::IntermediateMessage.new('token', 'to', 'from', 'messagekey', 'message')
218
+ job.process(message)
219
+ job.intermediate_state.should == { 'from' => { 'messagekey' => ['message'] } }
220
+ end
221
+
222
+ it "should not delete the message sender from the targets for 'intermediate' status messages" do
223
+ job = Nanite::Job.new(@request, ['from'], nil)
224
+ message = Nanite::IntermediateMessage.new('token', 'to', 'from', 'messagekey', 'message')
225
+ job.targets.should == ['from']
226
+ job.process(message)
227
+ job.targets.should == ['from']
228
+ end
229
+
230
+ end # Processing a Message
231
+
232
+
233
+ describe "Completion" do
234
+
235
+ before(:each) do
236
+ @request = mock("Request", :token => "af534ceaaacdcd")
237
+ end
238
+
239
+ it "should be true is targets are empty" do
240
+ job = Nanite::Job.new(@request, {}, nil)
241
+ job.completed?.should == true
242
+ end
243
+
244
+ it "should be false is targets are not empty" do
245
+ job = Nanite::Job.new(@request, { :a => 1 }, nil)
246
+ job.completed?.should == false
247
+ end
248
+
249
+ end # Completion
250
+
251
+ end # Nanite::Job
@@ -0,0 +1,112 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'nanite/local_state'
3
+
4
+ describe "Nanite::LocalState: " do
5
+
6
+ describe "Class" do
7
+
8
+ it "should a Hash" do
9
+ Nanite::LocalState.new({}).should be_kind_of(Hash)
10
+ end
11
+
12
+ it "should create empty hash if no hash passed in" do
13
+ Nanite::LocalState.new.should == {}
14
+ end
15
+
16
+ it "should initialize hash with value passed in" do
17
+ state = Nanite::LocalState.new({:a => 1, :b => 2, :c => 3})
18
+ state.should == {:a => 1, :b => 2, :c => 3}
19
+ end
20
+
21
+ end # Class
22
+
23
+
24
+ describe "All services" do
25
+
26
+ it "should return empty array if no services are defined" do
27
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :bar => 2 }})
28
+ state.all_services.should == []
29
+ end
30
+
31
+ it "should return all :services values" do
32
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :services => "b's services" }, :c => { :services => "c's services" }})
33
+ state.all_services.should include("b's services")
34
+ state.all_services.should include("c's services")
35
+ end
36
+
37
+ it "should only return one entry for each service" do
38
+ state = Nanite::LocalState.new({:f => { :services => "services" }, :b => { :services => "services" }})
39
+ state.all_services.size == 1
40
+ state.all_services.should == ["services"]
41
+ end
42
+
43
+ end # All services
44
+
45
+
46
+ describe "All tags" do
47
+
48
+ it "should return empty array if no tags are defined" do
49
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :bar => 2 }})
50
+ state.all_tags.should == []
51
+ end
52
+
53
+ it "should return all :tags values" do
54
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :tags => ["a", "b"] }, :c => { :tags => ["c", "d"] }})
55
+ state.all_tags.should include("a")
56
+ state.all_tags.should include("b")
57
+ state.all_tags.should include("c")
58
+ state.all_tags.should include("d")
59
+ end
60
+
61
+ it "should only return one entry for each tag" do
62
+ state = Nanite::LocalState.new({:f => { :foo => 1 }, :b => { :tags => ["a", "b"] }, :c => { :tags => ["a", "c"] }})
63
+ state.all_tags.size == 3
64
+ state.all_tags.should include("a")
65
+ state.all_tags.should include("b")
66
+ state.all_tags.should include("c")
67
+ end
68
+
69
+ end # All tags
70
+
71
+
72
+ describe "Nanites lookup" do
73
+
74
+ it "should find services matching the service criteria if no tags criteria is specified" do
75
+ state = Nanite::LocalState.new({:a => { :services => "a's services" }, :b => { :services => "b's services" }})
76
+ state.nanites_for("b's services").should == [[:b, {:services => "b's services"}]]
77
+ end
78
+
79
+ it "should find all services matching the service criteria if no tags criteria is specified" do
80
+ state = Nanite::LocalState.new({:a => { :services => "services" }, :b => { :services => "services" }, :c => { :services => "other services" }})
81
+ state.nanites_for("services").should include([:a, {:services => "services"}])
82
+ state.nanites_for("services").should include([:b, {:services => "services"}])
83
+ end
84
+
85
+ it "should only services matching the service criteria that also match the tags criteria" do
86
+ state = Nanite::LocalState.new({:a => { :services => "a's services", :tags => ["a_1", "a_2"] }, :b => { :services => "b's services", :tags => ["b_1", "b_2"] }})
87
+ state.nanites_for("b's services").should == [[:b, {:tags=>["b_1", "b_2"], :services=>"b's services"}]]
88
+ end
89
+
90
+ it "should also return all tags for services matching the service criteria that also match a single tags criterium" do
91
+ state = Nanite::LocalState.new({:a => { :services => "services", :tags => ["t_1", "t_2"] }})
92
+ state.nanites_for("services", ["t_1"]).should == [[:a, {:tags=>["t_1", "t_2"], :services=>"services"}]]
93
+ end
94
+
95
+ it "should return services matching the service criteria and also match the tags criterium" do
96
+ state = Nanite::LocalState.new({:a => { :services => "a's services", :tags => ["a_1", "a_2"] }, :b => { :services => "b's services", :tags => ["b_1", "b_2"] }})
97
+ state.nanites_for("b's services", ["b_1"]).should == [[:b, {:tags=>["b_1", "b_2"], :services=>"b's services"}]]
98
+ end
99
+
100
+ it "should ignore services matching the service criteria and but not the tags criteria" do
101
+ state = Nanite::LocalState.new({:a => { :services => "services", :tags => ["t_1", "t_2"] }, :b => { :services => "services", :tags => ["t_3", "t_4"] }})
102
+ state.nanites_for("services", ["t_1"]).should == [[:a, {:services => "services", :tags => ["t_1", "t_2"]}]]
103
+ end
104
+
105
+ it "should lookup services matching the service criteria and and any of the tags criteria" do
106
+ state = Nanite::LocalState.new({'a' => { :services => "services", :tags => ["t_1", "t_2"] }, 'b' => { :services => "services", :tags => ["t_2", "t_3"] }})
107
+ state.nanites_for("services", ["t_1", "t_3"]).sort.should == [['a', {:services => "services", :tags => ["t_1", "t_2"]}], ['b', {:services => "services", :tags => ["t_2", "t_3"]}]]
108
+ end
109
+
110
+ end # Nanites lookup
111
+
112
+ end # Nanite::LocalState