nanite 0.4.1.2 → 0.4.1.10
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/lib/nanite.rb +1 -1
- data/lib/nanite/agent.rb +11 -1
- data/lib/nanite/cluster.rb +56 -50
- data/lib/nanite/local_state.rb +1 -1
- data/lib/nanite/mapper.rb +12 -6
- data/lib/nanite/mapper_proxy.rb +11 -3
- data/lib/nanite/packets.rb +59 -23
- data/lib/nanite/redis_tag_store.rb +141 -0
- data/lib/nanite/state.rb +84 -117
- data/lib/nanite/util.rb +21 -1
- metadata +8 -6
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.10' unless defined?(Nanite::VERSION)
|
43
43
|
|
44
44
|
class MapperNotRunning < StandardError; end
|
45
45
|
|
data/lib/nanite/agent.rb
CHANGED
@@ -86,7 +86,7 @@ module Nanite
|
|
86
86
|
def initialize(opts)
|
87
87
|
set_configuration(opts)
|
88
88
|
@tags = []
|
89
|
-
@tags << opts[:tag]
|
89
|
+
@tags << opts[:tag] if opts[:tag]
|
90
90
|
@tags.flatten!
|
91
91
|
@options.freeze
|
92
92
|
end
|
@@ -131,6 +131,16 @@ module Nanite
|
|
131
131
|
@deny_token = deny_token
|
132
132
|
end
|
133
133
|
|
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
|
+
|
134
144
|
protected
|
135
145
|
|
136
146
|
def set_configuration(opts)
|
data/lib/nanite/cluster.rb
CHANGED
@@ -2,13 +2,14 @@ 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, callbacks = {})
|
5
|
+
def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, tag_store=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
|
12
13
|
@security = SecurityProvider.get
|
13
14
|
@callbacks = callbacks
|
14
15
|
setup_state
|
@@ -19,7 +20,7 @@ module Nanite
|
|
19
20
|
# determine which nanites should receive the given request
|
20
21
|
def targets_for(request)
|
21
22
|
return [request.target] if request.target
|
22
|
-
__send__(request.selector, request.type, request.tags).collect {|name, state| name }
|
23
|
+
__send__(request.selector, request.from, request.type, request.tags).collect {|name, state| name }
|
23
24
|
end
|
24
25
|
|
25
26
|
# adds nanite to nanites map: key is nanite's identity
|
@@ -41,6 +42,9 @@ module Nanite
|
|
41
42
|
reaper.unregister(reg.identity)
|
42
43
|
nanites.delete(reg.identity)
|
43
44
|
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)
|
44
48
|
else
|
45
49
|
Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
|
46
50
|
end
|
@@ -55,19 +59,23 @@ module Nanite
|
|
55
59
|
true
|
56
60
|
end
|
57
61
|
end
|
58
|
-
|
62
|
+
|
59
63
|
def route(request, targets)
|
60
64
|
EM.next_tick { targets.map { |target| publish(request, target) } }
|
61
65
|
end
|
62
66
|
|
63
67
|
def publish(request, target)
|
64
|
-
# We need to initialize the 'target' field of the request object so that the serializer
|
65
|
-
# access to it.
|
68
|
+
# We need to initialize the 'target' field of the request object so that the serializer and
|
69
|
+
# the security provider have access to it.
|
66
70
|
begin
|
67
71
|
old_target = request.target
|
68
72
|
request.target = target unless target == 'mapper-offline'
|
69
|
-
|
70
|
-
|
73
|
+
if @security.authorize_request(request)
|
74
|
+
Nanite::Log.info("SEND #{request.to_s([:from, :on_behalf, :tags, :target])}")
|
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
|
71
79
|
ensure
|
72
80
|
request.target = old_target
|
73
81
|
end
|
@@ -89,58 +97,54 @@ module Nanite
|
|
89
97
|
end
|
90
98
|
end
|
91
99
|
end
|
92
|
-
|
100
|
+
|
93
101
|
# forward request coming from agent
|
94
102
|
def handle_request(request)
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
mapper.send_push(request)
|
101
|
-
else
|
102
|
-
intm_handler = lambda do |result, job|
|
103
|
-
result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
|
104
|
-
forward_response(result, request.persistent)
|
105
|
-
end
|
106
|
-
|
107
|
-
result = Result.new(request.token, request.from, nil, mapper.identity)
|
108
|
-
ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
|
109
|
-
result.results = res
|
110
|
-
forward_response(result, request.persistent)
|
111
|
-
end
|
112
|
-
|
113
|
-
if ok == false
|
114
|
-
forward_response(result, request.persistent)
|
115
|
-
end
|
116
|
-
end
|
103
|
+
Nanite::Log.info("RECV #{request.to_s([:from, :on_behalf, :target, :tags])}") unless Nanite::Log.level == :debug
|
104
|
+
Nanite::Log.debug("RECV #{request.to_s}")
|
105
|
+
case request
|
106
|
+
when Push
|
107
|
+
mapper.send_push(request)
|
117
108
|
else
|
118
|
-
|
109
|
+
intm_handler = lambda do |result, job|
|
110
|
+
result = IntermediateMessage.new(request.token, job.request.from, mapper.identity, nil, result)
|
111
|
+
forward_response(result, request.persistent)
|
112
|
+
end
|
113
|
+
|
114
|
+
result = Result.new(request.token, request.from, nil, mapper.identity)
|
115
|
+
ok = mapper.send_request(request, :intermediate_handler => intm_handler) do |res|
|
116
|
+
result.results = res
|
117
|
+
forward_response(result, request.persistent)
|
118
|
+
end
|
119
|
+
|
120
|
+
if ok == false
|
121
|
+
forward_response(result, request.persistent)
|
122
|
+
end
|
119
123
|
end
|
120
124
|
end
|
121
|
-
|
125
|
+
|
122
126
|
# forward response back to agent that originally made the request
|
123
127
|
def forward_response(res, persistent)
|
124
128
|
Nanite::Log.info("SEND #{res.to_s([:to])}")
|
125
129
|
amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
|
126
130
|
end
|
127
|
-
|
131
|
+
|
128
132
|
# returns least loaded nanite that provides given service
|
129
|
-
def least_loaded(service, tags=[])
|
130
|
-
candidates = nanites_providing(service,tags)
|
133
|
+
def least_loaded(from, service, tags=[])
|
134
|
+
candidates = nanites_providing(from, service, tags)
|
131
135
|
return [] if candidates.empty?
|
132
136
|
|
133
137
|
[candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
|
134
138
|
end
|
135
139
|
|
136
140
|
# returns all nanites that provide given service
|
137
|
-
def all(service, tags=[])
|
138
|
-
nanites_providing(service,tags)
|
141
|
+
def all(from, service, tags=[])
|
142
|
+
nanites_providing(from, service,tags)
|
139
143
|
end
|
140
144
|
|
141
145
|
# returns a random nanite
|
142
|
-
def random(service, tags=[])
|
143
|
-
candidates = nanites_providing(service,tags)
|
146
|
+
def random(from, service, tags=[])
|
147
|
+
candidates = nanites_providing(from, service,tags)
|
144
148
|
return [] if candidates.empty?
|
145
149
|
|
146
150
|
[candidates[rand(candidates.size)]]
|
@@ -148,27 +152,28 @@ module Nanite
|
|
148
152
|
|
149
153
|
# selects next nanite that provides given service
|
150
154
|
# using round robin rotation
|
151
|
-
def rr(service, tags=[])
|
155
|
+
def rr(from, service, tags=[])
|
152
156
|
@last ||= {}
|
153
157
|
@last[service] ||= 0
|
154
|
-
candidates = nanites_providing(service,tags)
|
158
|
+
candidates = nanites_providing(from, service,tags)
|
155
159
|
return [] if candidates.empty?
|
156
160
|
@last[service] = 0 if @last[service] >= candidates.size
|
157
161
|
candidate = candidates[@last[service]]
|
158
162
|
@last[service] += 1
|
159
163
|
[candidate]
|
160
164
|
end
|
161
|
-
|
165
|
+
|
162
166
|
def timed_out?(nanite)
|
163
167
|
nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
|
164
168
|
end
|
165
169
|
|
166
170
|
# returns all nanites that provide the given service
|
167
|
-
def nanites_providing(service,
|
168
|
-
nanites.nanites_for(service,
|
169
|
-
if timed_out?(nanite[1])
|
171
|
+
def nanites_providing(from, service, tags)
|
172
|
+
nanites.nanites_for(from, service, tags).delete_if do |nanite|
|
173
|
+
if res = timed_out?(nanite[1])
|
170
174
|
Nanite::Log.debug("Ignoring timed out nanite #{nanite[0]} in target selection - last seen at #{nanite[1][:timestamp]}")
|
171
175
|
end
|
176
|
+
res
|
172
177
|
end
|
173
178
|
end
|
174
179
|
|
@@ -186,6 +191,7 @@ module Nanite
|
|
186
191
|
handle_ping(ping)
|
187
192
|
rescue Exception => e
|
188
193
|
Nanite::Log.error("RECV [ping] #{e.message}")
|
194
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
189
195
|
end
|
190
196
|
end
|
191
197
|
hb_fanout = amq.fanout('heartbeat', :durable => true)
|
@@ -202,6 +208,7 @@ module Nanite
|
|
202
208
|
register(serializer.load(msg))
|
203
209
|
rescue Exception => e
|
204
210
|
Nanite::Log.error("RECV [register] #{e.message}")
|
211
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
205
212
|
end
|
206
213
|
end
|
207
214
|
reg_fanout = amq.fanout('registration', :durable => true)
|
@@ -211,13 +218,14 @@ module Nanite
|
|
211
218
|
amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
|
212
219
|
end
|
213
220
|
end
|
214
|
-
|
221
|
+
|
215
222
|
def setup_request_queue
|
216
223
|
handler = lambda do |msg|
|
217
224
|
begin
|
218
225
|
handle_request(serializer.load(msg))
|
219
226
|
rescue Exception => e
|
220
227
|
Nanite::Log.error("RECV [request] #{e.message}")
|
228
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
221
229
|
end
|
222
230
|
end
|
223
231
|
req_fanout = amq.fanout('request', :durable => true)
|
@@ -233,16 +241,14 @@ module Nanite
|
|
233
241
|
when String
|
234
242
|
# backwards compatibility, we assume redis if the configuration option
|
235
243
|
# was a string
|
236
|
-
Nanite::Log.info("[setup] using redis for state storage")
|
237
244
|
require 'nanite/state'
|
238
|
-
@nanites = Nanite::State.new(@state)
|
239
|
-
when Hash
|
245
|
+
@nanites = Nanite::State.new(@state, @tag_store)
|
240
246
|
else
|
241
247
|
require 'nanite/local_state'
|
242
248
|
@nanites = Nanite::LocalState.new
|
243
249
|
end
|
244
250
|
end
|
245
|
-
|
251
|
+
|
246
252
|
def shared_state?
|
247
253
|
!@state.nil?
|
248
254
|
end
|
data/lib/nanite/local_state.rb
CHANGED
data/lib/nanite/mapper.rb
CHANGED
@@ -76,10 +76,15 @@ 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
|
-
#
|
79
|
+
#
|
80
|
+
# callbacks : A set of callbacks to be executed on specific events. Supported events are :register,
|
81
|
+
# :unregister, :timeout and :exception. This option must be a hash with event names as
|
82
|
+
# as keys and Procs as values. The Proc's arity (number of parameters) depends on the
|
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
|
83
88
|
#
|
84
89
|
# Connection options:
|
85
90
|
#
|
@@ -245,7 +250,7 @@ module Nanite
|
|
245
250
|
private
|
246
251
|
|
247
252
|
def build_deliverable(deliverable_type, type, payload, opts)
|
248
|
-
deliverable = deliverable_type.new(type, payload,
|
253
|
+
deliverable = deliverable_type.new(type, payload, opts)
|
249
254
|
deliverable.from = identity
|
250
255
|
deliverable.token = Identity.generate
|
251
256
|
deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
@@ -292,6 +297,7 @@ module Nanite
|
|
292
297
|
job_warden.process(msg)
|
293
298
|
rescue Exception => e
|
294
299
|
Nanite::Log.error("RECV [result] #{e.message}")
|
300
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
295
301
|
end
|
296
302
|
end
|
297
303
|
end
|
@@ -302,7 +308,7 @@ module Nanite
|
|
302
308
|
end
|
303
309
|
|
304
310
|
def setup_cluster
|
305
|
-
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
|
311
|
+
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:tag_store], @options[:callbacks])
|
306
312
|
end
|
307
313
|
end
|
308
314
|
end
|
data/lib/nanite/mapper_proxy.rb
CHANGED
@@ -31,7 +31,7 @@ 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,
|
34
|
+
request = Request.new(type, payload, opts)
|
35
35
|
request.from = identity
|
36
36
|
request.token = Identity.generate
|
37
37
|
request.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
@@ -43,13 +43,21 @@ module Nanite
|
|
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,
|
46
|
+
push = Push.new(type, payload, opts)
|
47
47
|
push.from = identity
|
48
48
|
push.token = Identity.generate
|
49
49
|
push.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
50
50
|
Nanite::Log.info("SEND #{push.to_s([:tags, :target])}")
|
51
51
|
amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(push))
|
52
|
-
end
|
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
|
53
61
|
|
54
62
|
# Handle intermediary result
|
55
63
|
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, chunk=nil, size=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'], i['chunk'], o['size'])
|
101
101
|
end
|
102
102
|
|
103
103
|
def to_s
|
@@ -112,24 +112,26 @@ 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
|
-
#
|
115
|
+
# from is sender identity
|
116
|
+
# on_behalf is agent identity that should be used to authorize request
|
117
|
+
# token is a generated request id that mapper uses to identify replies
|
118
|
+
# reply_to is identity of the node actor replies to, usually a mapper itself
|
119
|
+
# selector is the selector used to route the request
|
120
|
+
# target is the target nanite for the request
|
120
121
|
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
121
122
|
class Request < Packet
|
122
123
|
|
123
|
-
attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
|
124
|
+
attr_accessor :from, :on_behalf, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
|
124
125
|
|
125
126
|
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
126
127
|
|
127
|
-
def initialize(type, payload,
|
128
|
+
def initialize(type, payload, opts={}, size=nil)
|
128
129
|
opts = DEFAULT_OPTIONS.merge(opts)
|
129
130
|
@type = type
|
130
131
|
@payload = payload
|
131
132
|
@size = size
|
132
133
|
@from = opts[:from]
|
134
|
+
@on_behalf = opts[:on_behalf]
|
133
135
|
@token = opts[:token]
|
134
136
|
@reply_to = opts[:reply_to]
|
135
137
|
@selector = opts[:selector]
|
@@ -140,15 +142,17 @@ module Nanite
|
|
140
142
|
|
141
143
|
def self.json_create(o)
|
142
144
|
i = o['data']
|
143
|
-
new(i['type'], i['payload'],
|
144
|
-
|
145
|
-
|
146
|
-
|
145
|
+
new(i['type'], i['payload'], { :from => i['from'], :on_behalf => i['on_behalf'],
|
146
|
+
:token => i['token'], :reply_to => i['reply_to'],
|
147
|
+
:selector => i['selector'], :target => i['target'],
|
148
|
+
:persistent => i['persistent'], :tags => i['tags'] },
|
149
|
+
o['size'])
|
147
150
|
end
|
148
151
|
|
149
152
|
def to_s(filter=nil)
|
150
153
|
log_msg = "#{super} <#{token}> #{type}"
|
151
154
|
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))
|
152
156
|
log_msg += " to #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
|
153
157
|
log_msg += ", reply_to #{id_to_s(reply_to)}" if reply_to && (filter.nil? || filter.include?(:reply_to))
|
154
158
|
log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
|
@@ -165,23 +169,25 @@ module Nanite
|
|
165
169
|
# payload is arbitrary data that is transferred from mapper to actor
|
166
170
|
#
|
167
171
|
# Options:
|
168
|
-
# from
|
169
|
-
#
|
170
|
-
#
|
171
|
-
#
|
172
|
+
# from is sender identity
|
173
|
+
# on_behalf is agent identity that should be used to authorize request
|
174
|
+
# token is a generated request id that mapper uses to identify replies
|
175
|
+
# selector is the selector used to route the request
|
176
|
+
# target is the target nanite for the request
|
172
177
|
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
173
178
|
class Push < Packet
|
174
179
|
|
175
|
-
attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
|
180
|
+
attr_accessor :from, :on_behalf, :payload, :type, :token, :selector, :target, :persistent, :tags
|
176
181
|
|
177
182
|
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
178
183
|
|
179
|
-
def initialize(type, payload,
|
184
|
+
def initialize(type, payload, opts={}, size=nil)
|
180
185
|
opts = DEFAULT_OPTIONS.merge(opts)
|
181
186
|
@type = type
|
182
187
|
@payload = payload
|
183
188
|
@size = size
|
184
189
|
@from = opts[:from]
|
190
|
+
@on_behalf = opts[:on_behalf]
|
185
191
|
@token = opts[:token]
|
186
192
|
@selector = opts[:selector]
|
187
193
|
@target = opts[:target]
|
@@ -191,14 +197,17 @@ module Nanite
|
|
191
197
|
|
192
198
|
def self.json_create(o)
|
193
199
|
i = o['data']
|
194
|
-
new(i['type'], i['payload'],
|
195
|
-
|
196
|
-
|
200
|
+
new(i['type'], i['payload'], { :from => i['from'], :on_behalf => i['on_behalf'],
|
201
|
+
:token => i['token'], :selector => i['selector'],
|
202
|
+
:target => i['target'], :persistent => i['persistent'],
|
203
|
+
:tags => i['tags'] },
|
204
|
+
o['size'])
|
197
205
|
end
|
198
206
|
|
199
207
|
def to_s(filter=nil)
|
200
208
|
log_msg = "#{super} <#{token}> #{type}"
|
201
209
|
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))
|
202
211
|
log_msg += ", target #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
|
203
212
|
log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
|
204
213
|
log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
|
@@ -360,6 +369,33 @@ module Nanite
|
|
360
369
|
end
|
361
370
|
|
362
371
|
end
|
363
|
-
|
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
|
+
|
364
400
|
end
|
365
401
|
|
@@ -0,0 +1,141 @@
|
|
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
|
data/lib/nanite/state.rb
CHANGED
@@ -1,168 +1,135 @@
|
|
1
1
|
require 'redis'
|
2
|
+
require 'redis_tag_store'
|
2
3
|
|
3
4
|
module Nanite
|
4
5
|
class State
|
5
6
|
include Enumerable
|
6
7
|
|
7
|
-
#
|
8
|
-
# data store
|
9
|
-
#
|
8
|
+
# This class encapsulates the state of a nanite system using redis as the
|
9
|
+
# data store and a provided tag store. For a nanite with the identity
|
10
|
+
# 'nanite-foobar' we store the following:
|
10
11
|
#
|
11
|
-
# nanite-foobar: 0.72
|
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
|
12
|
+
# nanite-foobar: 0.72 # load average or 'status'
|
14
13
|
# t-nanite-foobar: 123456789 # unix timestamp of the last state update
|
15
14
|
#
|
16
|
-
#
|
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:
|
15
|
+
# The tag store is used to store the associated services and tags.
|
19
16
|
#
|
20
|
-
#
|
17
|
+
# A tag store should provide the following methods:
|
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
|
21
28
|
#
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
|
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(':')
|
29
|
+
# The default implementation for the tag store reuses Redis.
|
30
|
+
|
31
|
+
def initialize(redis, tag_store=nil)
|
32
|
+
host, port, tag_store_type = redis.split(':')
|
35
33
|
host ||= '127.0.0.1'
|
36
34
|
port ||= '6379'
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
blk.call
|
42
|
-
rescue Exception => e
|
43
|
-
Nanite::Log.info("redis error in method: #{meth}")
|
44
|
-
raise e
|
35
|
+
tag_store||= 'Nanite::RedisTagStore'
|
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}")
|
45
39
|
end
|
46
|
-
|
40
|
+
|
41
|
+
# Retrieve given agent services, tags, status and timestamp
|
47
42
|
def [](nanite)
|
48
|
-
log_redis_error
|
49
|
-
status
|
43
|
+
log_redis_error do
|
44
|
+
status = @redis[nanite]
|
50
45
|
timestamp = @redis["t-#{nanite}"]
|
51
|
-
services
|
52
|
-
tags
|
46
|
+
services = @tag_store.services(nanite)
|
47
|
+
tags = @tag_store.tags(nanite)
|
53
48
|
return nil unless status && timestamp && services
|
54
49
|
{:services => services, :status => status, :timestamp => timestamp.to_i, :tags => tags}
|
55
50
|
end
|
56
51
|
end
|
57
|
-
|
52
|
+
|
53
|
+
# Set given attributes for given agent
|
54
|
+
# Attributes may include services, tags and status
|
58
55
|
def []=(nanite, attributes)
|
59
|
-
|
60
|
-
|
61
|
-
end
|
56
|
+
@tag_store.store(nanite, attributes[:services], attributes[:tags])
|
57
|
+
update_status(nanite, attributes[:status])
|
62
58
|
end
|
63
|
-
|
59
|
+
|
60
|
+
# Delete all information related to given agent
|
64
61
|
def delete(nanite)
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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}"
|
62
|
+
@tag_store.delete(nanite)
|
63
|
+
log_redis_error do
|
64
|
+
@redis.delete(nanite)
|
65
|
+
@redis.delete("t-#{nanite}")
|
84
66
|
end
|
85
67
|
end
|
86
|
-
|
68
|
+
|
69
|
+
# Return all services exposed by all agents
|
87
70
|
def all_services
|
88
|
-
|
89
|
-
@redis.set_members("naniteservices")
|
90
|
-
end
|
71
|
+
@tag_store.all_services
|
91
72
|
end
|
92
73
|
|
74
|
+
# Return all tags exposed by all agents
|
93
75
|
def all_tags
|
94
|
-
|
95
|
-
@redis.set_members("nanitetags")
|
96
|
-
end
|
76
|
+
@tag_store.all_tags
|
97
77
|
end
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
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
|
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)
|
78
|
+
|
79
|
+
# Update status and timestamp for given agent
|
80
|
+
def update_status(name, status)
|
81
|
+
log_redis_error do
|
82
|
+
@redis[name] = status
|
83
|
+
@redis["t-#{name}"] = Time.now.utc.to_i
|
126
84
|
end
|
127
|
-
update_status(name, status)
|
128
85
|
end
|
129
86
|
|
130
|
-
|
131
|
-
|
132
|
-
@
|
87
|
+
# Update tags for given agent
|
88
|
+
def update_tags(name, new_tags, obsolete_tags)
|
89
|
+
@tag_store.update(name, new_tags, obsolete_tags)
|
133
90
|
end
|
134
|
-
|
91
|
+
|
92
|
+
# Return all registered agents
|
135
93
|
def list_nanites
|
136
|
-
log_redis_error
|
94
|
+
log_redis_error do
|
137
95
|
@redis.keys("nanite-*")
|
138
96
|
end
|
139
97
|
end
|
140
|
-
|
98
|
+
|
99
|
+
# Number of registered agents
|
141
100
|
def size
|
142
101
|
list_nanites.size
|
143
102
|
end
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
@redis.keys("*").each {|k| @redis.delete k}
|
148
|
-
end
|
149
|
-
end
|
150
|
-
|
103
|
+
|
104
|
+
# Iterate through all agents, yielding services, tags
|
105
|
+
# status and timestamp keyed by agent name
|
151
106
|
def each
|
152
107
|
list_nanites.each do |nan|
|
153
108
|
yield nan, self[nan]
|
154
109
|
end
|
155
110
|
end
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
111
|
+
|
112
|
+
# Return agents that implement given service and expose
|
113
|
+
# all given tags
|
114
|
+
def nanites_for(from, service, tags)
|
115
|
+
res = []
|
116
|
+
@tag_store.nanites_for(from, service, tags).each do |nanite_id|
|
117
|
+
if nanite = self[nanite_id]
|
118
|
+
res << [nanite_id, nanite]
|
163
119
|
end
|
164
|
-
res
|
165
120
|
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
|
166
132
|
end
|
133
|
+
|
167
134
|
end
|
168
135
|
end
|
data/lib/nanite/util.rb
CHANGED
@@ -27,6 +27,26 @@ 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
|
30
50
|
end
|
31
51
|
|
32
52
|
class Object
|
@@ -55,4 +75,4 @@ class Object
|
|
55
75
|
end
|
56
76
|
end
|
57
77
|
end
|
58
|
-
end
|
78
|
+
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.10
|
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-
|
12
|
+
date: 2009-11-06 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -39,6 +39,7 @@ files:
|
|
39
39
|
- README.rdoc
|
40
40
|
- Rakefile
|
41
41
|
- TODO
|
42
|
+
- lib/nanite
|
42
43
|
- lib/nanite/actor.rb
|
43
44
|
- lib/nanite/actor_registry.rb
|
44
45
|
- lib/nanite/admin.rb
|
@@ -51,6 +52,7 @@ files:
|
|
51
52
|
- lib/nanite/identity.rb
|
52
53
|
- lib/nanite/job.rb
|
53
54
|
- lib/nanite/local_state.rb
|
55
|
+
- lib/nanite/log
|
54
56
|
- lib/nanite/log/formatter.rb
|
55
57
|
- lib/nanite/log.rb
|
56
58
|
- lib/nanite/mapper.rb
|
@@ -59,6 +61,8 @@ files:
|
|
59
61
|
- lib/nanite/packets.rb
|
60
62
|
- lib/nanite/pid_file.rb
|
61
63
|
- lib/nanite/reaper.rb
|
64
|
+
- lib/nanite/redis_tag_store.rb
|
65
|
+
- lib/nanite/security
|
62
66
|
- lib/nanite/security/cached_certificate_store_proxy.rb
|
63
67
|
- lib/nanite/security/certificate.rb
|
64
68
|
- lib/nanite/security/certificate_cache.rb
|
@@ -79,8 +83,6 @@ files:
|
|
79
83
|
- bin/nanite-mapper
|
80
84
|
has_rdoc: true
|
81
85
|
homepage: http://github.com/ezmobius/nanite
|
82
|
-
licenses: []
|
83
|
-
|
84
86
|
post_install_message:
|
85
87
|
rdoc_options: []
|
86
88
|
|
@@ -101,9 +103,9 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
101
103
|
requirements: []
|
102
104
|
|
103
105
|
rubyforge_project:
|
104
|
-
rubygems_version: 1.3.
|
106
|
+
rubygems_version: 1.3.1
|
105
107
|
signing_key:
|
106
|
-
specification_version:
|
108
|
+
specification_version: 2
|
107
109
|
summary: self assembling fabric of ruby daemons
|
108
110
|
test_files: []
|
109
111
|
|