nanite 0.4.1.10 → 0.4.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Rakefile +1 -0
- data/bin/nanite-agent +3 -0
- data/lib/nanite.rb +6 -1
- data/lib/nanite/agent.rb +7 -11
- data/lib/nanite/cluster.rb +56 -60
- data/lib/nanite/config.rb +1 -1
- data/lib/nanite/local_state.rb +1 -1
- data/lib/nanite/mapper.rb +6 -13
- data/lib/nanite/mapper_proxy.rb +5 -13
- data/lib/nanite/packets.rb +23 -59
- data/lib/nanite/state.rb +117 -84
- data/lib/nanite/util.rb +1 -21
- metadata +16 -8
- data/lib/nanite/redis_tag_store.rb +0 -141
data/Rakefile
CHANGED
@@ -35,6 +35,7 @@ spec = Gem::Specification.new do |s|
|
|
35
35
|
s.executables = %w( nanite-agent nanite-mapper nanite-admin )
|
36
36
|
|
37
37
|
s.add_dependency('amqp', '>= 0.6.0')
|
38
|
+
s.add_dependency('json', '>= 1.1.7')
|
38
39
|
|
39
40
|
s.require_path = 'lib'
|
40
41
|
s.files = %w(LICENSE README.rdoc Rakefile TODO) + Dir.glob("{lib,bin,specs}/**/*")
|
data/bin/nanite-agent
CHANGED
@@ -42,6 +42,9 @@ opts = OptionParser.new do |opts|
|
|
42
42
|
options[:threadpool_size] = tps
|
43
43
|
end
|
44
44
|
|
45
|
+
opts.on("--prefetch COUNT", Integer, "The number of messages stuffed into the queue at anytime. Set this to a value of 1 or so for longer running jobs (1 or more seconds), so the agent does not get overwhelmed. Default is unlimited.") do |pref|
|
46
|
+
options[:prefetch] = pref
|
47
|
+
end
|
45
48
|
end
|
46
49
|
|
47
50
|
opts.parse!
|
data/lib/nanite.rb
CHANGED
@@ -39,7 +39,7 @@ require 'nanite/security/static_certificate_store'
|
|
39
39
|
require 'nanite/serializer'
|
40
40
|
|
41
41
|
module Nanite
|
42
|
-
VERSION = '0.4.1.
|
42
|
+
VERSION = '0.4.1.13' unless defined?(Nanite::VERSION)
|
43
43
|
|
44
44
|
class MapperNotRunning < StandardError; end
|
45
45
|
|
@@ -54,6 +54,11 @@ module Nanite
|
|
54
54
|
@mapper = Nanite::Mapper.start(options)
|
55
55
|
end
|
56
56
|
|
57
|
+
def start_mapper_proxy(options = {})
|
58
|
+
identity = options[:identity] || Nanite::Identity.generate
|
59
|
+
@mapper = Nanite::MapperProxy.new(identity, options)
|
60
|
+
end
|
61
|
+
|
57
62
|
def request(*args, &blk)
|
58
63
|
ensure_mapper
|
59
64
|
@mapper.request(*args, &blk)
|
data/lib/nanite/agent.rb
CHANGED
@@ -55,6 +55,9 @@ module Nanite
|
|
55
55
|
# services : list of services provided by this agent, by default
|
56
56
|
# all methods exposed by actors are listed
|
57
57
|
#
|
58
|
+
# prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6). Use value of 1 for long
|
59
|
+
# running jobs (greater than a second) to avoid slamming/stalling your agent.
|
60
|
+
#
|
58
61
|
# single_threaded: Run all operations in one thread
|
59
62
|
#
|
60
63
|
# threadpool_size: Number of threads to run operations in
|
@@ -86,7 +89,7 @@ module Nanite
|
|
86
89
|
def initialize(opts)
|
87
90
|
set_configuration(opts)
|
88
91
|
@tags = []
|
89
|
-
@tags << opts[:tag]
|
92
|
+
@tags << opts[:tag]
|
90
93
|
@tags.flatten!
|
91
94
|
@options.freeze
|
92
95
|
end
|
@@ -131,16 +134,6 @@ module Nanite
|
|
131
134
|
@deny_token = deny_token
|
132
135
|
end
|
133
136
|
|
134
|
-
# Update set of tags published by agent and notify mapper
|
135
|
-
# Add tags in 'new_tags' and remove tags in 'old_tags'
|
136
|
-
def update_tags(new_tags, old_tags)
|
137
|
-
@tags += (new_tags || [])
|
138
|
-
@tags -= (old_tags || [])
|
139
|
-
@tags.uniq!
|
140
|
-
tag_update = TagUpdate.new(identity, new_tags, old_tags)
|
141
|
-
amq.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(tag_update))
|
142
|
-
end
|
143
|
-
|
144
137
|
protected
|
145
138
|
|
146
139
|
def set_configuration(opts)
|
@@ -218,6 +211,9 @@ module Nanite
|
|
218
211
|
end
|
219
212
|
|
220
213
|
def setup_queue
|
214
|
+
if amq.respond_to?(:prefetch) && @options.has_key?(:prefetch)
|
215
|
+
amq.prefetch(@options[:prefetch])
|
216
|
+
end
|
221
217
|
amq.queue(identity, :durable => true).subscribe(:ack => true) do |info, msg|
|
222
218
|
begin
|
223
219
|
info.ack
|
data/lib/nanite/cluster.rb
CHANGED
@@ -2,14 +2,13 @@ module Nanite
|
|
2
2
|
class Cluster
|
3
3
|
attr_reader :agent_timeout, :nanites, :reaper, :serializer, :identity, :amq, :redis, :mapper, :callbacks
|
4
4
|
|
5
|
-
def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil,
|
5
|
+
def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, callbacks = {})
|
6
6
|
@amq = amq
|
7
7
|
@agent_timeout = agent_timeout
|
8
8
|
@identity = identity
|
9
9
|
@serializer = serializer
|
10
10
|
@mapper = mapper
|
11
11
|
@state = state_configuration
|
12
|
-
@tag_store = tag_store
|
13
12
|
@security = SecurityProvider.get
|
14
13
|
@callbacks = callbacks
|
15
14
|
setup_state
|
@@ -20,7 +19,7 @@ module Nanite
|
|
20
19
|
# determine which nanites should receive the given request
|
21
20
|
def targets_for(request)
|
22
21
|
return [request.target] if request.target
|
23
|
-
__send__(request.selector, request.
|
22
|
+
__send__(request.selector, request.type, request.tags).collect {|name, state| name }
|
24
23
|
end
|
25
24
|
|
26
25
|
# adds nanite to nanites map: key is nanite's identity
|
@@ -30,7 +29,7 @@ module Nanite
|
|
30
29
|
case reg
|
31
30
|
when Register
|
32
31
|
if @security.authorize_registration(reg)
|
33
|
-
Nanite::Log.
|
32
|
+
Nanite::Log.debug("RECV #{reg.to_s}")
|
34
33
|
nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
|
35
34
|
reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
|
36
35
|
callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
|
@@ -42,9 +41,6 @@ module Nanite
|
|
42
41
|
reaper.unregister(reg.identity)
|
43
42
|
nanites.delete(reg.identity)
|
44
43
|
callbacks[:unregister].call(reg.identity, mapper) if callbacks[:unregister]
|
45
|
-
when TagUpdate
|
46
|
-
Nanite::Log.info("RECV #{reg.to_s}")
|
47
|
-
nanites.update_tags(reg.identity, reg.new_tags, reg.obsolete_tags)
|
48
44
|
else
|
49
45
|
Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
|
50
46
|
end
|
@@ -59,23 +55,19 @@ module Nanite
|
|
59
55
|
true
|
60
56
|
end
|
61
57
|
end
|
62
|
-
|
58
|
+
|
63
59
|
def route(request, targets)
|
64
60
|
EM.next_tick { targets.map { |target| publish(request, target) } }
|
65
61
|
end
|
66
62
|
|
67
63
|
def publish(request, target)
|
68
|
-
# We need to initialize the 'target' field of the request object so that the serializer
|
69
|
-
#
|
64
|
+
# We need to initialize the 'target' field of the request object so that the serializer has
|
65
|
+
# access to it.
|
70
66
|
begin
|
71
67
|
old_target = request.target
|
72
68
|
request.target = target unless target == 'mapper-offline'
|
73
|
-
|
74
|
-
|
75
|
-
amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
|
76
|
-
else
|
77
|
-
Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
|
78
|
-
end
|
69
|
+
Nanite::Log.debug("SEND #{request.to_s([:from, :tags, :target])}")
|
70
|
+
amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
|
79
71
|
ensure
|
80
72
|
request.target = old_target
|
81
73
|
end
|
@@ -92,59 +84,62 @@ module Nanite
|
|
92
84
|
reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
|
93
85
|
else
|
94
86
|
packet = Advertise.new
|
95
|
-
Nanite::Log.
|
87
|
+
Nanite::Log.debug("SEND #{packet.to_s} to #{ping.identity}")
|
96
88
|
amq.queue(ping.identity).publish(serializer.dump(packet))
|
97
89
|
end
|
98
90
|
end
|
99
91
|
end
|
100
|
-
|
92
|
+
|
101
93
|
# forward request coming from agent
|
102
94
|
def handle_request(request)
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
95
|
+
if @security.authorize_request(request)
|
96
|
+
Nanite::Log.debug("RECV #{request.to_s}")
|
97
|
+
case request
|
98
|
+
when Push
|
99
|
+
mapper.send_push(request)
|
100
|
+
else
|
101
|
+
intm_handler = lambda do |result, job|
|
102
|
+
result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
|
103
|
+
forward_response(result, request.persistent)
|
104
|
+
end
|
105
|
+
|
106
|
+
result = Result.new(request.token, request.from, nil, mapper.identity)
|
107
|
+
ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
|
108
|
+
result.results = res
|
109
|
+
forward_response(result, request.persistent)
|
110
|
+
end
|
111
|
+
|
112
|
+
if ok == false
|
113
|
+
forward_response(result, request.persistent)
|
114
|
+
end
|
122
115
|
end
|
116
|
+
else
|
117
|
+
Nanite::Log.warn("RECV NOT AUTHORIZED #{request.to_s}")
|
123
118
|
end
|
124
119
|
end
|
125
|
-
|
120
|
+
|
126
121
|
# forward response back to agent that originally made the request
|
127
122
|
def forward_response(res, persistent)
|
128
|
-
Nanite::Log.
|
123
|
+
Nanite::Log.debug("SEND #{res.to_s([:to])}")
|
129
124
|
amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
|
130
125
|
end
|
131
|
-
|
126
|
+
|
132
127
|
# returns least loaded nanite that provides given service
|
133
|
-
def least_loaded(
|
134
|
-
candidates = nanites_providing(
|
128
|
+
def least_loaded(service, tags=[])
|
129
|
+
candidates = nanites_providing(service,tags)
|
135
130
|
return [] if candidates.empty?
|
136
131
|
|
137
132
|
[candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
|
138
133
|
end
|
139
134
|
|
140
135
|
# returns all nanites that provide given service
|
141
|
-
def all(
|
142
|
-
nanites_providing(
|
136
|
+
def all(service, tags=[])
|
137
|
+
nanites_providing(service,tags)
|
143
138
|
end
|
144
139
|
|
145
140
|
# returns a random nanite
|
146
|
-
def random(
|
147
|
-
candidates = nanites_providing(
|
141
|
+
def random(service, tags=[])
|
142
|
+
candidates = nanites_providing(service,tags)
|
148
143
|
return [] if candidates.empty?
|
149
144
|
|
150
145
|
[candidates[rand(candidates.size)]]
|
@@ -152,28 +147,30 @@ module Nanite
|
|
152
147
|
|
153
148
|
# selects next nanite that provides given service
|
154
149
|
# using round robin rotation
|
155
|
-
def rr(
|
150
|
+
def rr(service, tags=[])
|
156
151
|
@last ||= {}
|
157
152
|
@last[service] ||= 0
|
158
|
-
candidates = nanites_providing(
|
153
|
+
candidates = nanites_providing(service,tags)
|
159
154
|
return [] if candidates.empty?
|
160
155
|
@last[service] = 0 if @last[service] >= candidates.size
|
161
156
|
candidate = candidates[@last[service]]
|
162
157
|
@last[service] += 1
|
163
158
|
[candidate]
|
164
159
|
end
|
165
|
-
|
160
|
+
|
166
161
|
def timed_out?(nanite)
|
167
162
|
nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
|
168
163
|
end
|
169
164
|
|
170
165
|
# returns all nanites that provide the given service
|
171
|
-
def nanites_providing(
|
172
|
-
nanites.nanites_for(
|
173
|
-
|
174
|
-
|
166
|
+
def nanites_providing(service, *tags)
|
167
|
+
nanites.nanites_for(service, *tags).delete_if do |nanite|
|
168
|
+
nanite_id, nanite_attributes = nanite
|
169
|
+
if timed_out?(nanite_attributes)
|
170
|
+
reaper.unregister(nanite_id)
|
171
|
+
nanites.delete(nanite_id)
|
172
|
+
Nanite::Log.debug("Nanite #{nanite_id} timed out - ignoring in target selection and deleting from state - last seen at #{nanite_attributes[:timestamp]}")
|
175
173
|
end
|
176
|
-
res
|
177
174
|
end
|
178
175
|
end
|
179
176
|
|
@@ -191,7 +188,6 @@ module Nanite
|
|
191
188
|
handle_ping(ping)
|
192
189
|
rescue Exception => e
|
193
190
|
Nanite::Log.error("RECV [ping] #{e.message}")
|
194
|
-
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
195
191
|
end
|
196
192
|
end
|
197
193
|
hb_fanout = amq.fanout('heartbeat', :durable => true)
|
@@ -208,7 +204,6 @@ module Nanite
|
|
208
204
|
register(serializer.load(msg))
|
209
205
|
rescue Exception => e
|
210
206
|
Nanite::Log.error("RECV [register] #{e.message}")
|
211
|
-
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
212
207
|
end
|
213
208
|
end
|
214
209
|
reg_fanout = amq.fanout('registration', :durable => true)
|
@@ -218,14 +213,13 @@ module Nanite
|
|
218
213
|
amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
|
219
214
|
end
|
220
215
|
end
|
221
|
-
|
216
|
+
|
222
217
|
def setup_request_queue
|
223
218
|
handler = lambda do |msg|
|
224
219
|
begin
|
225
220
|
handle_request(serializer.load(msg))
|
226
221
|
rescue Exception => e
|
227
222
|
Nanite::Log.error("RECV [request] #{e.message}")
|
228
|
-
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
229
223
|
end
|
230
224
|
end
|
231
225
|
req_fanout = amq.fanout('request', :durable => true)
|
@@ -241,14 +235,16 @@ module Nanite
|
|
241
235
|
when String
|
242
236
|
# backwards compatibility, we assume redis if the configuration option
|
243
237
|
# was a string
|
238
|
+
Nanite::Log.info("[setup] using redis for state storage")
|
244
239
|
require 'nanite/state'
|
245
|
-
@nanites = Nanite::State.new(@state
|
240
|
+
@nanites = Nanite::State.new(@state)
|
241
|
+
when Hash
|
246
242
|
else
|
247
243
|
require 'nanite/local_state'
|
248
244
|
@nanites = Nanite::LocalState.new
|
249
245
|
end
|
250
246
|
end
|
251
|
-
|
247
|
+
|
252
248
|
def shared_state?
|
253
249
|
!@state.nil?
|
254
250
|
end
|
data/lib/nanite/config.rb
CHANGED
@@ -31,7 +31,7 @@ module Nanite
|
|
31
31
|
opts.on("--offline-failsafe", "Store messages in an offline queue when all the nanites are offline. Messages will be redelivered when nanites come online. Can be overriden on a per-message basis using the request methods.") do
|
32
32
|
options[:offline_failsafe] = true
|
33
33
|
end
|
34
|
-
|
34
|
+
|
35
35
|
opts.on("--redis HOST_PORT", "Use redis as the agent state storage in the mapper: --redis 127.0.0.1:6379; missing host and/or port will be filled with defaults if colon is present") do |redis|
|
36
36
|
redishost, redisport = redis.split(':')
|
37
37
|
redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
|
data/lib/nanite/local_state.rb
CHANGED
data/lib/nanite/mapper.rb
CHANGED
@@ -76,15 +76,10 @@ module Nanite
|
|
76
76
|
# secure : use Security features of rabbitmq to restrict nanites to themselves
|
77
77
|
#
|
78
78
|
# prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6)
|
79
|
-
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
#
|
83
|
-
# type of callback:
|
84
|
-
# exception -- the exception, the message being processed, a reference to the mapper
|
85
|
-
# all others -- the corresponding nanite's identity, a reference to the mapper
|
86
|
-
#
|
87
|
-
# tag_store : Name of class which implements tag store backend interface, RedisTagStore by default
|
79
|
+
# callbacks : A set of callbacks to have code executed on specific events, supported events are :register,
|
80
|
+
# :unregister and :timeout. Parameter must be a hash with the corresponding events as keys and
|
81
|
+
# a block as value. The block will get the corresponding nanite's identity and a copy of the
|
82
|
+
# mapper
|
88
83
|
#
|
89
84
|
# Connection options:
|
90
85
|
#
|
@@ -250,7 +245,7 @@ module Nanite
|
|
250
245
|
private
|
251
246
|
|
252
247
|
def build_deliverable(deliverable_type, type, payload, opts)
|
253
|
-
deliverable = deliverable_type.new(type, payload, opts)
|
248
|
+
deliverable = deliverable_type.new(type, payload, nil, opts)
|
254
249
|
deliverable.from = identity
|
255
250
|
deliverable.token = Identity.generate
|
256
251
|
deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
@@ -293,11 +288,9 @@ module Nanite
|
|
293
288
|
begin
|
294
289
|
msg = serializer.load(msg)
|
295
290
|
Nanite::Log.debug("RECV #{msg.to_s}")
|
296
|
-
Nanite::Log.info("RECV #{msg.to_s([:from])}") unless Nanite::Log.level == :debug
|
297
291
|
job_warden.process(msg)
|
298
292
|
rescue Exception => e
|
299
293
|
Nanite::Log.error("RECV [result] #{e.message}")
|
300
|
-
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
301
294
|
end
|
302
295
|
end
|
303
296
|
end
|
@@ -308,7 +301,7 @@ module Nanite
|
|
308
301
|
end
|
309
302
|
|
310
303
|
def setup_cluster
|
311
|
-
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:
|
304
|
+
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
|
312
305
|
end
|
313
306
|
end
|
314
307
|
end
|
data/lib/nanite/mapper_proxy.rb
CHANGED
@@ -31,33 +31,25 @@ module Nanite
|
|
31
31
|
# Send request to given agent through the mapper
|
32
32
|
def request(type, payload = '', opts = {}, &blk)
|
33
33
|
raise "Mapper proxy not initialized" unless identity && options
|
34
|
-
request = Request.new(type, payload, opts)
|
34
|
+
request = Request.new(type, payload, nil, opts)
|
35
35
|
request.from = identity
|
36
36
|
request.token = Identity.generate
|
37
37
|
request.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
38
38
|
pending_requests[request.token] =
|
39
39
|
{ :intermediate_handler => opts[:intermediate_handler], :result_handler => blk }
|
40
|
-
Nanite::Log.
|
40
|
+
Nanite::Log.debug("SEND #{request.to_s([:tags, :target])}")
|
41
41
|
amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(request))
|
42
42
|
end
|
43
43
|
|
44
44
|
def push(type, payload = '', opts = {})
|
45
45
|
raise "Mapper proxy not initialized" unless identity && options
|
46
|
-
push = Push.new(type, payload, opts)
|
46
|
+
push = Push.new(type, payload, nil, opts)
|
47
47
|
push.from = identity
|
48
48
|
push.token = Identity.generate
|
49
49
|
push.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
50
|
-
Nanite::Log.
|
50
|
+
Nanite::Log.debug("SEND #{push.to_s([:tags, :target])}")
|
51
51
|
amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(push))
|
52
|
-
end
|
53
|
-
|
54
|
-
# Update tags registered by mapper for agent
|
55
|
-
def update_tags(new_tags, obsolete_tags)
|
56
|
-
raise "Mapper proxy not initialized" unless identity && options
|
57
|
-
update = TagUpdate.new(identity, new_tags, obsolete_tags)
|
58
|
-
Nanite::Log.info("SEND #{update.to_s}")
|
59
|
-
amqp.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(update))
|
60
|
-
end
|
52
|
+
end
|
61
53
|
|
62
54
|
# Handle intermediary result
|
63
55
|
def handle_intermediate_result(res)
|
data/lib/nanite/packets.rb
CHANGED
@@ -89,7 +89,7 @@ module Nanite
|
|
89
89
|
|
90
90
|
attr_accessor :chunk, :token
|
91
91
|
|
92
|
-
def initialize(token,
|
92
|
+
def initialize(token, size=nil, chunk=nil)
|
93
93
|
@chunk = chunk
|
94
94
|
@token = token
|
95
95
|
@size = size
|
@@ -97,7 +97,7 @@ module Nanite
|
|
97
97
|
|
98
98
|
def self.json_create(o)
|
99
99
|
i = o['data']
|
100
|
-
new(i['token'],
|
100
|
+
new(i['token'], o['size'], i['chunk'])
|
101
101
|
end
|
102
102
|
|
103
103
|
def to_s
|
@@ -112,26 +112,24 @@ module Nanite
|
|
112
112
|
# payload is arbitrary data that is transferred from mapper to actor
|
113
113
|
#
|
114
114
|
# Options:
|
115
|
-
# from
|
116
|
-
#
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
# target is the target nanite for the request
|
115
|
+
# from is sender identity
|
116
|
+
# token is a generated request id that mapper uses to identify replies
|
117
|
+
# reply_to is identity of the node actor replies to, usually a mapper itself
|
118
|
+
# selector is the selector used to route the request
|
119
|
+
# target is the target nanite for the request
|
121
120
|
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
122
121
|
class Request < Packet
|
123
122
|
|
124
|
-
attr_accessor :from, :
|
123
|
+
attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
|
125
124
|
|
126
125
|
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
127
126
|
|
128
|
-
def initialize(type, payload, opts={}
|
127
|
+
def initialize(type, payload, size=nil, opts={})
|
129
128
|
opts = DEFAULT_OPTIONS.merge(opts)
|
130
129
|
@type = type
|
131
130
|
@payload = payload
|
132
131
|
@size = size
|
133
132
|
@from = opts[:from]
|
134
|
-
@on_behalf = opts[:on_behalf]
|
135
133
|
@token = opts[:token]
|
136
134
|
@reply_to = opts[:reply_to]
|
137
135
|
@selector = opts[:selector]
|
@@ -142,17 +140,15 @@ module Nanite
|
|
142
140
|
|
143
141
|
def self.json_create(o)
|
144
142
|
i = o['data']
|
145
|
-
new(i['type'], i['payload'], { :from => i['from'],
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
o['size'])
|
143
|
+
new(i['type'], i['payload'], o['size'], { :from => i['from'], :token => i['token'],
|
144
|
+
:reply_to => i['reply_to'], :selector => i['selector'],
|
145
|
+
:target => i['target'], :persistent => i['persistent'],
|
146
|
+
:tags => i['tags'] })
|
150
147
|
end
|
151
148
|
|
152
149
|
def to_s(filter=nil)
|
153
150
|
log_msg = "#{super} <#{token}> #{type}"
|
154
151
|
log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
|
155
|
-
log_msg += " on behalf of #{id_to_s(on_behalf)}" if on_behalf && (filter.nil? || filter.include?(:on_behalf))
|
156
152
|
log_msg += " to #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
|
157
153
|
log_msg += ", reply_to #{id_to_s(reply_to)}" if reply_to && (filter.nil? || filter.include?(:reply_to))
|
158
154
|
log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
|
@@ -169,25 +165,23 @@ module Nanite
|
|
169
165
|
# payload is arbitrary data that is transferred from mapper to actor
|
170
166
|
#
|
171
167
|
# Options:
|
172
|
-
# from
|
173
|
-
#
|
174
|
-
#
|
175
|
-
#
|
176
|
-
# target is the target nanite for the request
|
168
|
+
# from is sender identity
|
169
|
+
# token is a generated request id that mapper uses to identify replies
|
170
|
+
# selector is the selector used to route the request
|
171
|
+
# target is the target nanite for the request
|
177
172
|
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
178
173
|
class Push < Packet
|
179
174
|
|
180
|
-
attr_accessor :from, :
|
175
|
+
attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
|
181
176
|
|
182
177
|
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
183
178
|
|
184
|
-
def initialize(type, payload, opts={}
|
179
|
+
def initialize(type, payload, size=nil, opts={})
|
185
180
|
opts = DEFAULT_OPTIONS.merge(opts)
|
186
181
|
@type = type
|
187
182
|
@payload = payload
|
188
183
|
@size = size
|
189
184
|
@from = opts[:from]
|
190
|
-
@on_behalf = opts[:on_behalf]
|
191
185
|
@token = opts[:token]
|
192
186
|
@selector = opts[:selector]
|
193
187
|
@target = opts[:target]
|
@@ -197,17 +191,14 @@ module Nanite
|
|
197
191
|
|
198
192
|
def self.json_create(o)
|
199
193
|
i = o['data']
|
200
|
-
new(i['type'], i['payload'], { :from
|
201
|
-
|
202
|
-
|
203
|
-
:tags => i['tags'] },
|
204
|
-
o['size'])
|
194
|
+
new(i['type'], i['payload'], o['size'], { :from => i['from'], :token => i['token'],
|
195
|
+
:selector => i['selector'], :target => i['target'],
|
196
|
+
:persistent => i['persistent'], :tags => i['tags'] })
|
205
197
|
end
|
206
198
|
|
207
199
|
def to_s(filter=nil)
|
208
200
|
log_msg = "#{super} <#{token}> #{type}"
|
209
201
|
log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
|
210
|
-
log_msg += " on behalf of #{id_to_s(on_behalf)}" if on_behalf && (filter.nil? || filter.include?(:on_behalf))
|
211
202
|
log_msg += ", target #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
|
212
203
|
log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
|
213
204
|
log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
|
@@ -369,33 +360,6 @@ module Nanite
|
|
369
360
|
end
|
370
361
|
|
371
362
|
end
|
372
|
-
|
373
|
-
# packet that is sent by agents to the mapper
|
374
|
-
# to update their tags
|
375
|
-
class TagUpdate < Packet
|
376
|
-
|
377
|
-
attr_accessor :identity, :new_tags, :obsolete_tags
|
378
|
-
|
379
|
-
def initialize(identity, new_tags, obsolete_tags, size=nil)
|
380
|
-
@identity = identity
|
381
|
-
@new_tags = new_tags
|
382
|
-
@obsolete_tags = obsolete_tags
|
383
|
-
@size = size
|
384
|
-
end
|
385
|
-
|
386
|
-
def self.json_create(o)
|
387
|
-
i = o['data']
|
388
|
-
new(i['identity'], i['new_tags'], i['obsolete_tags'], o['size'])
|
389
|
-
end
|
390
|
-
|
391
|
-
def to_s
|
392
|
-
log_msg = "#{super} #{id_to_s(identity)}"
|
393
|
-
log_msg += ", new tags: #{new_tags.join(', ')}" if new_tags && !new_tags.empty?
|
394
|
-
log_msg += ", obsolete tags: #{obsolete_tags.join(', ')}" if obsolete_tags && !obsolete_tags.empty?
|
395
|
-
log_msg
|
396
|
-
end
|
397
|
-
|
398
|
-
end
|
399
|
-
|
363
|
+
|
400
364
|
end
|
401
365
|
|
data/lib/nanite/state.rb
CHANGED
@@ -1,135 +1,168 @@
|
|
1
1
|
require 'redis'
|
2
|
-
require 'redis_tag_store'
|
3
2
|
|
4
3
|
module Nanite
|
5
4
|
class State
|
6
5
|
include Enumerable
|
7
6
|
|
8
|
-
#
|
9
|
-
# data store
|
10
|
-
#
|
7
|
+
# this class encapsulates the state of a nanite system using redis as the
|
8
|
+
# data store. here is the schema, for each agent we store a number of items,
|
9
|
+
# for a nanite with the identity: nanite-foobar we store the following things:
|
11
10
|
#
|
12
|
-
# nanite-foobar: 0.72
|
11
|
+
# nanite-foobar: 0.72 # load average or 'status'
|
12
|
+
# s-nanite-foobar: { /foo/bar, /foo/nik } # a SET of the provided services
|
13
|
+
# tg-nanite-foobar: { foo-42, customer-12 } # a SET of the tags for this agent
|
13
14
|
# t-nanite-foobar: 123456789 # unix timestamp of the last state update
|
14
15
|
#
|
15
|
-
#
|
16
|
+
# also we do an inverted index for quick lookup of agents providing a certain
|
17
|
+
# service, so for each service the agent provides, we add the nanite to a SET
|
18
|
+
# of all the nanites that provide said service:
|
16
19
|
#
|
17
|
-
#
|
18
|
-
# - initialize(redis): Initialize tag store, may use provided redis handle
|
19
|
-
# - services(nanite): Retrieve services implemented by given agent
|
20
|
-
# - tags(nanite): Retrieve tags implemented by given agent
|
21
|
-
# - all_services: Retrieve all services implemented by all agents
|
22
|
-
# - all_tags: Retrieve all tags exposed by all agents
|
23
|
-
# - store(nanite, services, tags): Store agent's services and tags
|
24
|
-
# - update(name, new_tags,obsolete_tags): Update agent's tags
|
25
|
-
# - delete(nanite): Delete all entries associated with given agent
|
26
|
-
# - nanites_for(service, tags): Retrieve agents implementing given service
|
27
|
-
# and exposing given tags
|
20
|
+
# foo/bar: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
28
21
|
#
|
29
|
-
#
|
30
|
-
|
31
|
-
|
32
|
-
|
22
|
+
# we do that same thing for tags:
|
23
|
+
# some-tag: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
24
|
+
#
|
25
|
+
# This way we can do a lookup of what nanites provide a set of services and tags based
|
26
|
+
# on redis SET intersection:
|
27
|
+
#
|
28
|
+
# nanites_for('/gems/list', 'some-tag')
|
29
|
+
# => returns a nested array of nanites and their state that provide the intersection
|
30
|
+
# of these two service tags
|
31
|
+
|
32
|
+
def initialize(redis)
|
33
|
+
Nanite::Log.info("[setup] initializing redis state: #{redis}")
|
34
|
+
host, port = redis.split(':')
|
33
35
|
host ||= '127.0.0.1'
|
34
36
|
port ||= '6379'
|
35
|
-
|
36
|
-
@redis = Redis.new(:host => host, :port => port)
|
37
|
-
@tag_store = tag_store.to_const.new(@redis)
|
38
|
-
Nanite::Log.info("[setup] Initializing redis state using host '#{host}', port '#{port}' and tag store #{tag_store}")
|
37
|
+
@redis = Redis.new :host => host, :port => port
|
39
38
|
end
|
40
|
-
|
41
|
-
|
39
|
+
|
40
|
+
def log_redis_error(meth,&blk)
|
41
|
+
blk.call
|
42
|
+
rescue Exception => e
|
43
|
+
Nanite::Log.info("redis error in method: #{meth}")
|
44
|
+
raise e
|
45
|
+
end
|
46
|
+
|
42
47
|
def [](nanite)
|
43
|
-
log_redis_error do
|
44
|
-
status
|
48
|
+
log_redis_error("[]") do
|
49
|
+
status = @redis[nanite]
|
45
50
|
timestamp = @redis["t-#{nanite}"]
|
46
|
-
services
|
47
|
-
tags
|
51
|
+
services = @redis.set_members("s-#{nanite}")
|
52
|
+
tags = @redis.set_members("tg-#{nanite}")
|
48
53
|
return nil unless status && timestamp && services
|
49
54
|
{:services => services, :status => status, :timestamp => timestamp.to_i, :tags => tags}
|
50
55
|
end
|
51
56
|
end
|
52
|
-
|
53
|
-
# Set given attributes for given agent
|
54
|
-
# Attributes may include services, tags and status
|
57
|
+
|
55
58
|
def []=(nanite, attributes)
|
56
|
-
|
57
|
-
|
59
|
+
log_redis_error("[]=") do
|
60
|
+
update_state(nanite, attributes[:status], attributes[:services], attributes[:tags])
|
61
|
+
end
|
58
62
|
end
|
59
|
-
|
60
|
-
# Delete all information related to given agent
|
63
|
+
|
61
64
|
def delete(nanite)
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
65
|
+
log_redis_error("delete") do
|
66
|
+
(@redis.set_members("s-#{nanite}")||[]).each do |srv|
|
67
|
+
@redis.set_delete(srv, nanite)
|
68
|
+
if @redis.set_count(srv) == 0
|
69
|
+
@redis.delete(srv)
|
70
|
+
@redis.set_delete("naniteservices", srv)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
(@redis.set_members("tg-#{nanite}")||[]).each do |tag|
|
74
|
+
@redis.set_delete(tag, nanite)
|
75
|
+
if @redis.set_count(tag) == 0
|
76
|
+
@redis.delete(tag)
|
77
|
+
@redis.set_delete("nanitetags", tag)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
@redis.delete nanite
|
81
|
+
@redis.delete "s-#{nanite}"
|
82
|
+
@redis.delete "t-#{nanite}"
|
83
|
+
@redis.delete "tg-#{nanite}"
|
66
84
|
end
|
67
85
|
end
|
68
|
-
|
69
|
-
# Return all services exposed by all agents
|
86
|
+
|
70
87
|
def all_services
|
71
|
-
|
88
|
+
log_redis_error("all_services") do
|
89
|
+
@redis.set_members("naniteservices")
|
90
|
+
end
|
72
91
|
end
|
73
92
|
|
74
|
-
# Return all tags exposed by all agents
|
75
93
|
def all_tags
|
76
|
-
|
94
|
+
log_redis_error("all_tags") do
|
95
|
+
@redis.set_members("nanitetags")
|
96
|
+
end
|
77
97
|
end
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
98
|
+
|
99
|
+
def update_state(name, status, services, tags)
|
100
|
+
old_services = @redis.set_members("s-#{name}")
|
101
|
+
if old_services
|
102
|
+
(old_services - services).each do |s|
|
103
|
+
@redis.set_delete(s, name)
|
104
|
+
@redis.set_delete("naniteservices", s)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
old_tags = @redis.set_members("tg-#{name}")
|
108
|
+
if old_tags
|
109
|
+
(old_tags - tags).each do |t|
|
110
|
+
@redis.set_delete(t, name)
|
111
|
+
@redis.set_delete("nanitetags", t)
|
112
|
+
end
|
84
113
|
end
|
114
|
+
@redis.delete("s-#{name}")
|
115
|
+
services.each do |srv|
|
116
|
+
@redis.set_add(srv, name)
|
117
|
+
@redis.set_add("s-#{name}", srv)
|
118
|
+
@redis.set_add("naniteservices", srv)
|
119
|
+
end
|
120
|
+
@redis.delete("tg-#{name}")
|
121
|
+
tags.each do |tag|
|
122
|
+
next if tag.nil?
|
123
|
+
@redis.set_add(tag, name)
|
124
|
+
@redis.set_add("tg-#{name}", tag)
|
125
|
+
@redis.set_add("nanitetags", tag)
|
126
|
+
end
|
127
|
+
update_status(name, status)
|
85
128
|
end
|
86
129
|
|
87
|
-
|
88
|
-
|
89
|
-
@
|
130
|
+
def update_status(name, status)
|
131
|
+
@redis[name] = status
|
132
|
+
@redis["t-#{name}"] = Time.now.utc.to_i
|
90
133
|
end
|
91
|
-
|
92
|
-
# Return all registered agents
|
134
|
+
|
93
135
|
def list_nanites
|
94
|
-
log_redis_error do
|
136
|
+
log_redis_error("list_nanites") do
|
95
137
|
@redis.keys("nanite-*")
|
96
138
|
end
|
97
139
|
end
|
98
|
-
|
99
|
-
# Number of registered agents
|
140
|
+
|
100
141
|
def size
|
101
142
|
list_nanites.size
|
102
143
|
end
|
103
|
-
|
104
|
-
|
105
|
-
|
144
|
+
|
145
|
+
def clear_state
|
146
|
+
log_redis_error("clear_state") do
|
147
|
+
@redis.keys("*").each {|k| @redis.delete k}
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
106
151
|
def each
|
107
152
|
list_nanites.each do |nan|
|
108
153
|
yield nan, self[nan]
|
109
154
|
end
|
110
155
|
end
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
res << [nanite_id, nanite]
|
156
|
+
|
157
|
+
def nanites_for(service, *tags)
|
158
|
+
keys = tags.dup << service
|
159
|
+
log_redis_error("nanites_for") do
|
160
|
+
res = []
|
161
|
+
(@redis.set_intersect(keys)||[]).each do |nan|
|
162
|
+
res << [nan, self[nan]]
|
119
163
|
end
|
164
|
+
res
|
120
165
|
end
|
121
|
-
res
|
122
|
-
end
|
123
|
-
|
124
|
-
private
|
125
|
-
|
126
|
-
# Helper method, catch and log errors
|
127
|
-
def log_redis_error(&blk)
|
128
|
-
blk.call
|
129
|
-
rescue Exception => e
|
130
|
-
Nanite::Log.warn("redis error in method: #{caller[0]}")
|
131
|
-
raise e
|
132
166
|
end
|
133
|
-
|
134
167
|
end
|
135
168
|
end
|
data/lib/nanite/util.rb
CHANGED
@@ -27,26 +27,6 @@ class String
|
|
27
27
|
def to_const_path
|
28
28
|
snake_case.gsub(/::/, "/")
|
29
29
|
end
|
30
|
-
|
31
|
-
# Convert constant name to constant
|
32
|
-
#
|
33
|
-
# "FooBar::Baz".to_const => FooBar::Baz
|
34
|
-
#
|
35
|
-
# @return [Constant] Constant corresponding to given name or nil if no
|
36
|
-
# constant with that name exists
|
37
|
-
#
|
38
|
-
# @api public
|
39
|
-
def to_const
|
40
|
-
names = split('::')
|
41
|
-
names.shift if names.empty? || names.first.empty?
|
42
|
-
|
43
|
-
constant = Object
|
44
|
-
names.each do |name|
|
45
|
-
# modified to return nil instead of raising an const_missing error
|
46
|
-
constant = constant && constant.const_defined?(name) ? constant.const_get(name) : nil
|
47
|
-
end
|
48
|
-
constant
|
49
|
-
end
|
50
30
|
end
|
51
31
|
|
52
32
|
class Object
|
@@ -75,4 +55,4 @@ class Object
|
|
75
55
|
end
|
76
56
|
end
|
77
57
|
end
|
78
|
-
end
|
58
|
+
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nanite
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.4.1.
|
4
|
+
version: 0.4.1.13
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-11-
|
12
|
+
date: 2009-11-18 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -22,6 +22,16 @@ dependencies:
|
|
22
22
|
- !ruby/object:Gem::Version
|
23
23
|
version: 0.6.0
|
24
24
|
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: json
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.7
|
34
|
+
version:
|
25
35
|
description: self assembling fabric of ruby daemons
|
26
36
|
email: ezra@engineyard.com
|
27
37
|
executables:
|
@@ -39,7 +49,6 @@ files:
|
|
39
49
|
- README.rdoc
|
40
50
|
- Rakefile
|
41
51
|
- TODO
|
42
|
-
- lib/nanite
|
43
52
|
- lib/nanite/actor.rb
|
44
53
|
- lib/nanite/actor_registry.rb
|
45
54
|
- lib/nanite/admin.rb
|
@@ -52,7 +61,6 @@ files:
|
|
52
61
|
- lib/nanite/identity.rb
|
53
62
|
- lib/nanite/job.rb
|
54
63
|
- lib/nanite/local_state.rb
|
55
|
-
- lib/nanite/log
|
56
64
|
- lib/nanite/log/formatter.rb
|
57
65
|
- lib/nanite/log.rb
|
58
66
|
- lib/nanite/mapper.rb
|
@@ -61,8 +69,6 @@ files:
|
|
61
69
|
- lib/nanite/packets.rb
|
62
70
|
- lib/nanite/pid_file.rb
|
63
71
|
- lib/nanite/reaper.rb
|
64
|
-
- lib/nanite/redis_tag_store.rb
|
65
|
-
- lib/nanite/security
|
66
72
|
- lib/nanite/security/cached_certificate_store_proxy.rb
|
67
73
|
- lib/nanite/security/certificate.rb
|
68
74
|
- lib/nanite/security/certificate_cache.rb
|
@@ -83,6 +89,8 @@ files:
|
|
83
89
|
- bin/nanite-mapper
|
84
90
|
has_rdoc: true
|
85
91
|
homepage: http://github.com/ezmobius/nanite
|
92
|
+
licenses: []
|
93
|
+
|
86
94
|
post_install_message:
|
87
95
|
rdoc_options: []
|
88
96
|
|
@@ -103,9 +111,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
103
111
|
requirements: []
|
104
112
|
|
105
113
|
rubyforge_project:
|
106
|
-
rubygems_version: 1.3.
|
114
|
+
rubygems_version: 1.3.5
|
107
115
|
signing_key:
|
108
|
-
specification_version:
|
116
|
+
specification_version: 3
|
109
117
|
summary: self assembling fabric of ruby daemons
|
110
118
|
test_files: []
|
111
119
|
|
@@ -1,141 +0,0 @@
|
|
1
|
-
require 'redis'
|
2
|
-
|
3
|
-
module Nanite
|
4
|
-
|
5
|
-
# Implementation of a tag store on top of Redis
|
6
|
-
# For a nanite with the identity 'nanite-foobar', we store the following:
|
7
|
-
#
|
8
|
-
# s-nanite-foobar: { /foo/bar, /foo/nik } # a SET of the provided services
|
9
|
-
# tg-nanite-foobar: { foo-42, customer-12 } # a SET of the tags for this agent
|
10
|
-
#
|
11
|
-
# Also we do an inverted index for quick lookup of agents providing a certain
|
12
|
-
# service, so for each service the agent provides, we add the nanite to a SET
|
13
|
-
# of all the nanites that provide said service:
|
14
|
-
#
|
15
|
-
# foo/bar: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
16
|
-
#
|
17
|
-
# We do that same thing for tags:
|
18
|
-
#
|
19
|
-
# some-tag: { nanite-foobar, nanite-nickelbag, nanite-another } # redis SET
|
20
|
-
#
|
21
|
-
# This way we can do a lookup of what nanites provide a set of services and tags based
|
22
|
-
# on redis SET intersection:
|
23
|
-
#
|
24
|
-
# nanites_for('/gems/list', 'some-tag')
|
25
|
-
# => returns an array of nanites that provide the intersection of these two service tags
|
26
|
-
|
27
|
-
class RedisTagStore
|
28
|
-
|
29
|
-
# Initialize tag store with given redis handle
|
30
|
-
def initialize(redis)
|
31
|
-
@redis = redis
|
32
|
-
end
|
33
|
-
|
34
|
-
# Store services and tags for given agent
|
35
|
-
def store(nanite, services, tags)
|
36
|
-
services = nil if services.compact.empty?
|
37
|
-
tags = nil if tags.compact.empty?
|
38
|
-
log_redis_error do
|
39
|
-
if services
|
40
|
-
obsolete_services = @redis.set_members("s-#{nanite}") - services
|
41
|
-
update_elems(nanite, services, obsolete_services, "s-#{nanite}", 'naniteservices')
|
42
|
-
end
|
43
|
-
if tags
|
44
|
-
obsolete_tags = @redis.set_members("tg-#{nanite}") - tags
|
45
|
-
update_elems(nanite, tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
|
46
|
-
end
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
# Update tags for given agent
|
51
|
-
def update(nanite, new_tags, obsolete_tags)
|
52
|
-
update_elems(nanite, new_tags, obsolete_tags, "tg-#{nanite}", 'nanitestags')
|
53
|
-
end
|
54
|
-
|
55
|
-
# Delete services and tags for given agent
|
56
|
-
def delete(nanite)
|
57
|
-
delete_elems(nanite, "s-#{nanite}", 'naniteservices')
|
58
|
-
delete_elems(nanite, "tg-#{nanite}", 'nanitestags')
|
59
|
-
end
|
60
|
-
|
61
|
-
# Services implemented by given agent
|
62
|
-
def services(nanite)
|
63
|
-
@redis.set_members("s-#{nanite}")
|
64
|
-
end
|
65
|
-
|
66
|
-
# Tags exposed by given agent
|
67
|
-
def tags(nanite)
|
68
|
-
@redis.set_members("tg-#{nanite}")
|
69
|
-
end
|
70
|
-
|
71
|
-
# Retrieve all agents services
|
72
|
-
def all_services
|
73
|
-
log_redis_error do
|
74
|
-
@redis.set_members('naniteservices')
|
75
|
-
end
|
76
|
-
end
|
77
|
-
|
78
|
-
# Retrieve all agents tags
|
79
|
-
def all_tags
|
80
|
-
log_redis_error do
|
81
|
-
@redis.set_members('nanitetags')
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# Retrieve nanites implementing given service and exposing given tags
|
86
|
-
def nanites_for(from, service, tags)
|
87
|
-
keys = tags && tags.dup || []
|
88
|
-
keys << service
|
89
|
-
log_redis_error do
|
90
|
-
@redis.set_intersect(keys.compact)
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
private
|
95
|
-
|
96
|
-
# Update values stored for given agent
|
97
|
-
# Also store reverse lookup information using both a unique and
|
98
|
-
# a global key (so it's possible to retrieve that agent value or
|
99
|
-
# all related values)
|
100
|
-
def update_elems(nanite, new_tags, obsolete_tags, elem_key, global_key)
|
101
|
-
new_tags = nil if new_tags.compact.empty?
|
102
|
-
obsolete_tags = nil if obsolete_tags.compact.empty?
|
103
|
-
log_redis_error do
|
104
|
-
obsolete_tags.each do |val|
|
105
|
-
@redis.set_delete(val, nanite)
|
106
|
-
@redis.set_delete(elem_key, val)
|
107
|
-
@redis.set_delete(global_key, val)
|
108
|
-
end if obsolete_tags
|
109
|
-
new_tags.each do |val|
|
110
|
-
@redis.set_add(val, nanite)
|
111
|
-
@redis.set_add(elem_key, val)
|
112
|
-
@redis.set_add(global_key, val)
|
113
|
-
end if new_tags
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
# Delete all values for given nanite agent
|
118
|
-
# Also delete reverse lookup information
|
119
|
-
def delete_elems(nanite, elem_key, global_key)
|
120
|
-
log_redis_error do
|
121
|
-
(@redis.set_members(elem_key)||[]).each do |val|
|
122
|
-
@redis.set_delete(val, nanite)
|
123
|
-
if @redis.set_count(val) == 0
|
124
|
-
@redis.delete(val)
|
125
|
-
@redis.set_delete(global_key, val)
|
126
|
-
end
|
127
|
-
end
|
128
|
-
@redis.delete(elem_key)
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
# Helper method, catch and log errors
|
133
|
-
def log_redis_error(&blk)
|
134
|
-
blk.call
|
135
|
-
rescue Exception => e
|
136
|
-
Nanite::Log.warn("redis error in method: #{caller[0]}")
|
137
|
-
raise e
|
138
|
-
end
|
139
|
-
|
140
|
-
end
|
141
|
-
end
|