rightscale-nanite-dev 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/LICENSE +201 -0
- data/README.rdoc +430 -0
- data/Rakefile +78 -0
- data/TODO +24 -0
- data/bin/nanite-admin +65 -0
- data/bin/nanite-agent +79 -0
- data/bin/nanite-mapper +50 -0
- data/lib/nanite.rb +74 -0
- data/lib/nanite/actor.rb +71 -0
- data/lib/nanite/actor_registry.rb +26 -0
- data/lib/nanite/admin.rb +138 -0
- data/lib/nanite/agent.rb +274 -0
- data/lib/nanite/amqp.rb +58 -0
- data/lib/nanite/cluster.rb +256 -0
- data/lib/nanite/config.rb +111 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +13 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +104 -0
- data/lib/nanite/local_state.rb +38 -0
- data/lib/nanite/log.rb +66 -0
- data/lib/nanite/log/formatter.rb +39 -0
- data/lib/nanite/mapper.rb +315 -0
- data/lib/nanite/mapper_proxy.rb +75 -0
- data/lib/nanite/nanite_dispatcher.rb +92 -0
- data/lib/nanite/packets.rb +401 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +39 -0
- data/lib/nanite/redis_tag_store.rb +141 -0
- data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
- data/lib/nanite/security/certificate.rb +55 -0
- data/lib/nanite/security/certificate_cache.rb +66 -0
- data/lib/nanite/security/distinguished_name.rb +34 -0
- data/lib/nanite/security/encrypted_document.rb +46 -0
- data/lib/nanite/security/rsa_key_pair.rb +53 -0
- data/lib/nanite/security/secure_serializer.rb +68 -0
- data/lib/nanite/security/signature.rb +46 -0
- data/lib/nanite/security/static_certificate_store.rb +35 -0
- data/lib/nanite/security_provider.rb +47 -0
- data/lib/nanite/serializer.rb +52 -0
- data/lib/nanite/state.rb +135 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +78 -0
- metadata +111 -0
@@ -0,0 +1,256 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Cluster
|
3
|
+
attr_reader :agent_timeout, :nanites, :reaper, :serializer, :identity, :amq, :redis, :mapper, :callbacks
|
4
|
+
|
5
|
+
def initialize(amq, agent_timeout, identity, serializer, mapper, state_configuration=nil, tag_store=nil, callbacks = {})
|
6
|
+
@amq = amq
|
7
|
+
@agent_timeout = agent_timeout
|
8
|
+
@identity = identity
|
9
|
+
@serializer = serializer
|
10
|
+
@mapper = mapper
|
11
|
+
@state = state_configuration
|
12
|
+
@tag_store = tag_store
|
13
|
+
@security = SecurityProvider.get
|
14
|
+
@callbacks = callbacks
|
15
|
+
setup_state
|
16
|
+
@reaper = Reaper.new(agent_timeout)
|
17
|
+
setup_queues
|
18
|
+
end
|
19
|
+
|
20
|
+
# determine which nanites should receive the given request
|
21
|
+
def targets_for(request)
|
22
|
+
return [request.target] if request.target
|
23
|
+
__send__(request.selector, request.from, request.type, request.tags).collect {|name, state| name }
|
24
|
+
end
|
25
|
+
|
26
|
+
# adds nanite to nanites map: key is nanite's identity
|
27
|
+
# and value is a services/status pair implemented
|
28
|
+
# as a hash
|
29
|
+
def register(reg)
|
30
|
+
case reg
|
31
|
+
when Register
|
32
|
+
if @security.authorize_registration(reg)
|
33
|
+
Nanite::Log.info("RECV #{reg.to_s}")
|
34
|
+
nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
|
35
|
+
reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
|
36
|
+
callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
|
37
|
+
else
|
38
|
+
Nanite::Log.warn("RECV NOT AUTHORIZED #{reg.to_s}")
|
39
|
+
end
|
40
|
+
when UnRegister
|
41
|
+
Nanite::Log.info("RECV #{reg.to_s}")
|
42
|
+
reaper.unregister(reg.identity)
|
43
|
+
nanites.delete(reg.identity)
|
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)
|
48
|
+
else
|
49
|
+
Nanite::Log.warn("RECV [register] Invalid packet type: #{reg.class}")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def nanite_timed_out(token)
|
54
|
+
nanite = nanites[token]
|
55
|
+
if nanite && timed_out?(nanite)
|
56
|
+
Nanite::Log.info("Nanite #{token} timed out")
|
57
|
+
nanite = nanites.delete(token)
|
58
|
+
callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
|
59
|
+
true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def route(request, targets)
|
64
|
+
EM.next_tick { targets.map { |target| publish(request, target) } }
|
65
|
+
end
|
66
|
+
|
67
|
+
def publish(request, target)
|
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.
|
70
|
+
begin
|
71
|
+
old_target = request.target
|
72
|
+
request.target = target unless target == 'mapper-offline'
|
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
|
79
|
+
ensure
|
80
|
+
request.target = old_target
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
protected
|
85
|
+
|
86
|
+
# updates nanite information (last ping timestamps, status)
|
87
|
+
# when heartbeat message is received
|
88
|
+
def handle_ping(ping)
|
89
|
+
begin
|
90
|
+
if nanite = nanites[ping.identity]
|
91
|
+
nanites.update_status(ping.identity, ping.status)
|
92
|
+
reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
|
93
|
+
else
|
94
|
+
packet = Advertise.new
|
95
|
+
Nanite::Log.info("SEND #{packet.to_s} to #{ping.identity}")
|
96
|
+
amq.queue(ping.identity).publish(serializer.dump(packet))
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
# forward request coming from agent
|
102
|
+
def handle_request(request)
|
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)
|
108
|
+
else
|
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
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# forward response back to agent that originally made the request
|
127
|
+
def forward_response(res, persistent)
|
128
|
+
Nanite::Log.info("SEND #{res.to_s([:to])}")
|
129
|
+
amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
|
130
|
+
end
|
131
|
+
|
132
|
+
# returns least loaded nanite that provides given service
|
133
|
+
def least_loaded(from, service, tags=[])
|
134
|
+
candidates = nanites_providing(from, service, tags)
|
135
|
+
return [] if candidates.empty?
|
136
|
+
|
137
|
+
[candidates.min { |a,b| a[1][:status] <=> b[1][:status] }]
|
138
|
+
end
|
139
|
+
|
140
|
+
# returns all nanites that provide given service
|
141
|
+
def all(from, service, tags=[])
|
142
|
+
nanites_providing(from, service,tags)
|
143
|
+
end
|
144
|
+
|
145
|
+
# returns a random nanite
|
146
|
+
def random(from, service, tags=[])
|
147
|
+
candidates = nanites_providing(from, service,tags)
|
148
|
+
return [] if candidates.empty?
|
149
|
+
|
150
|
+
[candidates[rand(candidates.size)]]
|
151
|
+
end
|
152
|
+
|
153
|
+
# selects next nanite that provides given service
|
154
|
+
# using round robin rotation
|
155
|
+
def rr(from, service, tags=[])
|
156
|
+
@last ||= {}
|
157
|
+
@last[service] ||= 0
|
158
|
+
candidates = nanites_providing(from, service,tags)
|
159
|
+
return [] if candidates.empty?
|
160
|
+
@last[service] = 0 if @last[service] >= candidates.size
|
161
|
+
candidate = candidates[@last[service]]
|
162
|
+
@last[service] += 1
|
163
|
+
[candidate]
|
164
|
+
end
|
165
|
+
|
166
|
+
def timed_out?(nanite)
|
167
|
+
nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
|
168
|
+
end
|
169
|
+
|
170
|
+
# returns all nanites that provide the given service
|
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])
|
174
|
+
Nanite::Log.debug("Ignoring timed out nanite #{nanite[0]} in target selection - last seen at #{nanite[1][:timestamp]}")
|
175
|
+
end
|
176
|
+
res
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def setup_queues
|
181
|
+
setup_heartbeat_queue
|
182
|
+
setup_registration_queue
|
183
|
+
setup_request_queue
|
184
|
+
end
|
185
|
+
|
186
|
+
def setup_heartbeat_queue
|
187
|
+
handler = lambda do |ping|
|
188
|
+
begin
|
189
|
+
ping = serializer.load(ping)
|
190
|
+
Nanite::Log.debug("RECV #{ping.to_s}") if ping.respond_to?(:to_s)
|
191
|
+
handle_ping(ping)
|
192
|
+
rescue Exception => e
|
193
|
+
Nanite::Log.error("RECV [ping] #{e.message}")
|
194
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
195
|
+
end
|
196
|
+
end
|
197
|
+
hb_fanout = amq.fanout('heartbeat', :durable => true)
|
198
|
+
if shared_state?
|
199
|
+
amq.queue("heartbeat").bind(hb_fanout).subscribe &handler
|
200
|
+
else
|
201
|
+
amq.queue("heartbeat-#{identity}", :exclusive => true).bind(hb_fanout).subscribe &handler
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
def setup_registration_queue
|
206
|
+
handler = lambda do |msg|
|
207
|
+
begin
|
208
|
+
register(serializer.load(msg))
|
209
|
+
rescue Exception => e
|
210
|
+
Nanite::Log.error("RECV [register] #{e.message}")
|
211
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
212
|
+
end
|
213
|
+
end
|
214
|
+
reg_fanout = amq.fanout('registration', :durable => true)
|
215
|
+
if shared_state?
|
216
|
+
amq.queue("registration").bind(reg_fanout).subscribe &handler
|
217
|
+
else
|
218
|
+
amq.queue("registration-#{identity}", :exclusive => true).bind(reg_fanout).subscribe &handler
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
def setup_request_queue
|
223
|
+
handler = lambda do |msg|
|
224
|
+
begin
|
225
|
+
handle_request(serializer.load(msg))
|
226
|
+
rescue Exception => e
|
227
|
+
Nanite::Log.error("RECV [request] #{e.message}")
|
228
|
+
callbacks[:exception].call(e, msg, mapper) rescue nil if callbacks[:exception]
|
229
|
+
end
|
230
|
+
end
|
231
|
+
req_fanout = amq.fanout('request', :durable => true)
|
232
|
+
if shared_state?
|
233
|
+
amq.queue("request").bind(req_fanout).subscribe &handler
|
234
|
+
else
|
235
|
+
amq.queue("request-#{identity}", :exclusive => true).bind(req_fanout).subscribe &handler
|
236
|
+
end
|
237
|
+
end
|
238
|
+
|
239
|
+
def setup_state
|
240
|
+
case @state
|
241
|
+
when String
|
242
|
+
# backwards compatibility, we assume redis if the configuration option
|
243
|
+
# was a string
|
244
|
+
require 'nanite/state'
|
245
|
+
@nanites = Nanite::State.new(@state, @tag_store)
|
246
|
+
else
|
247
|
+
require 'nanite/local_state'
|
248
|
+
@nanites = Nanite::LocalState.new
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def shared_state?
|
253
|
+
!@state.nil?
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
COMMON_DEFAULT_OPTIONS = {
|
4
|
+
:pass => 'testing',
|
5
|
+
:vhost => '/nanite',
|
6
|
+
:secure => false,
|
7
|
+
:host => '0.0.0.0',
|
8
|
+
:log_level => :info,
|
9
|
+
:format => :marshal,
|
10
|
+
:daemonize => false,
|
11
|
+
:console => false,
|
12
|
+
:root => Dir.pwd
|
13
|
+
}
|
14
|
+
|
15
|
+
module CommonConfig
|
16
|
+
def setup_mapper_options(opts, options)
|
17
|
+
setup_common_options(opts, options, 'mapper')
|
18
|
+
|
19
|
+
opts.on("-a", "--agent-timeout TIMEOUT", "How long to wait before an agent is considered to be offline and thus removed from the list of available agents.") do |timeout|
|
20
|
+
options[:agent_timeout] = timeout
|
21
|
+
end
|
22
|
+
|
23
|
+
opts.on("-r", "--offline-redelivery-frequency", "The frequency in seconds that messages stored in the offline queue will be retrieved for attempted redelivery to the nanites. Default is 10 seconds.") do |frequency|
|
24
|
+
options[:offline_redelivery_frequency] = frequency
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on("--persistent", "Instructs the AMQP broker to save messages to persistent storage so that they aren't lost when the broker is restarted. Can be overriden on a per-message basis using the request and push methods.") do
|
28
|
+
options[:persistent] = true
|
29
|
+
end
|
30
|
+
|
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
|
+
options[:offline_failsafe] = true
|
33
|
+
end
|
34
|
+
|
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
|
+
redishost, redisport = redis.split(':')
|
37
|
+
redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
|
38
|
+
redisport = '6379' if (redishost.nil? || redishost.empty?)
|
39
|
+
redis = "#{redishost}:#{redisport}"
|
40
|
+
options[:redis] = redis
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
def setup_common_options(opts, options, type)
|
46
|
+
opts.version = Nanite::VERSION
|
47
|
+
|
48
|
+
opts.on("-i", "--irb-console", "Start #{type} in irb console mode.") do |console|
|
49
|
+
options[:console] = 'irb'
|
50
|
+
end
|
51
|
+
|
52
|
+
opts.on("-u", "--user USER", "Specify the rabbitmq username.") do |user|
|
53
|
+
options[:user] = user
|
54
|
+
end
|
55
|
+
|
56
|
+
opts.on("-h", "--host HOST", "Specify the rabbitmq hostname.") do |host|
|
57
|
+
options[:host] = host
|
58
|
+
end
|
59
|
+
|
60
|
+
opts.on("-P", "--port PORT", "Specify the rabbitmq PORT, default 5672.") do |port|
|
61
|
+
options[:port] = port
|
62
|
+
end
|
63
|
+
|
64
|
+
opts.on("-p", "--pass PASSWORD", "Specify the rabbitmq password") do |pass|
|
65
|
+
options[:pass] = pass
|
66
|
+
end
|
67
|
+
|
68
|
+
opts.on("-t", "--token IDENITY", "Specify the #{type} identity.") do |ident|
|
69
|
+
options[:identity] = ident
|
70
|
+
end
|
71
|
+
|
72
|
+
opts.on("-v", "--vhost VHOST", "Specify the rabbitmq vhost") do |vhost|
|
73
|
+
options[:vhost] = vhost
|
74
|
+
end
|
75
|
+
|
76
|
+
opts.on("-s", "--secure", "Use Security features of rabbitmq to restrict nanites to themselves") do
|
77
|
+
options[:secure] = true
|
78
|
+
end
|
79
|
+
|
80
|
+
opts.on("-f", "--format FORMAT", "The serialization type to use for transfering data. Can be marshal, json or yaml. Default is marshal") do |fmt|
|
81
|
+
options[:format] = fmt.to_sym
|
82
|
+
end
|
83
|
+
|
84
|
+
opts.on("-d", "--daemonize", "Run #{type} as a daemon") do |d|
|
85
|
+
options[:daemonize] = true
|
86
|
+
end
|
87
|
+
|
88
|
+
opts.on("--pid-dir PATH", "Specify the pid path, only used with daemonize") do |dir|
|
89
|
+
options[:pid_dir] = dir
|
90
|
+
end
|
91
|
+
|
92
|
+
opts.on("-l", "--log-level LEVEL", "Specify the log level (fatal, error, warn, info, debug). Default is info") do |level|
|
93
|
+
options[:log_level] = level
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on("--log-dir PATH", "Specify the log path") do |dir|
|
97
|
+
options[:log_dir] = dir
|
98
|
+
end
|
99
|
+
|
100
|
+
opts.on("--tag TAG", "Specify a tag. Can issue multiple times.") do |tag|
|
101
|
+
options[:tag] ||= []
|
102
|
+
options[:tag] << tag
|
103
|
+
end
|
104
|
+
|
105
|
+
opts.on("--version", "Show the nanite version number") do |res|
|
106
|
+
puts "Nanite Version #{opts.version}"
|
107
|
+
exit
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module Nanite
|
2
|
+
module ConsoleHelper
|
3
|
+
def self.included(base)
|
4
|
+
@@base = base
|
5
|
+
end
|
6
|
+
|
7
|
+
def start_console
|
8
|
+
puts "Starting #{@@base.name.split(":").last.downcase} console (#{self.identity}) (Nanite #{Nanite::VERSION})"
|
9
|
+
Thread.new do
|
10
|
+
Console.start(self)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
module Console
|
16
|
+
class << self; attr_accessor :instance; end
|
17
|
+
|
18
|
+
def self.start(binding)
|
19
|
+
require 'irb'
|
20
|
+
old_args = ARGV.dup
|
21
|
+
ARGV.replace ["--simple-prompt"]
|
22
|
+
|
23
|
+
IRB.setup(nil)
|
24
|
+
self.instance = IRB::Irb.new(IRB::WorkSpace.new(binding))
|
25
|
+
|
26
|
+
@CONF = IRB.instance_variable_get(:@CONF)
|
27
|
+
@CONF[:IRB_RC].call self.instance.context if @CONF[:IRB_RC]
|
28
|
+
@CONF[:MAIN_CONTEXT] = self.instance.context
|
29
|
+
|
30
|
+
catch(:IRB_EXIT) { self.instance.eval_input }
|
31
|
+
ensure
|
32
|
+
ARGV.replace old_args
|
33
|
+
# Clean up tty settings in some evil, evil cases
|
34
|
+
begin; catch(:IRB_EXIT) { irb_exit }; rescue Exception; end
|
35
|
+
# Make nanite exit when irb does
|
36
|
+
EM.stop if EM.reactor_running?
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Nanite
|
2
|
+
module DaemonizeHelper
|
3
|
+
def daemonize(identity, options = {})
|
4
|
+
exit if fork
|
5
|
+
Process.setsid
|
6
|
+
exit if fork
|
7
|
+
STDIN.reopen "/dev/null"
|
8
|
+
STDOUT.reopen "#{options[:log_path]}/nanite.#{identity}.out", "a"
|
9
|
+
STDERR.reopen "#{options[:log_path]}/nanite.#{identity}.err", "a"
|
10
|
+
File.umask 0000
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module Nanite
|
2
|
+
module Identity
|
3
|
+
def self.generate
|
4
|
+
values = [
|
5
|
+
rand(0x0010000),
|
6
|
+
rand(0x0010000),
|
7
|
+
rand(0x0010000),
|
8
|
+
rand(0x0010000),
|
9
|
+
rand(0x0010000),
|
10
|
+
rand(0x1000000),
|
11
|
+
rand(0x1000000),
|
12
|
+
]
|
13
|
+
"%04x%04x%04x%04x%04x%06x%06x" % values
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/nanite/job.rb
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Nanite
|
2
|
+
class JobWarden
|
3
|
+
attr_reader :serializer, :jobs
|
4
|
+
|
5
|
+
def initialize(serializer)
|
6
|
+
@serializer = serializer
|
7
|
+
@jobs = {}
|
8
|
+
end
|
9
|
+
|
10
|
+
def new_job(request, targets, inthandler = nil, blk = nil)
|
11
|
+
job = Job.new(request, targets, inthandler, blk)
|
12
|
+
jobs[job.token] = job
|
13
|
+
job
|
14
|
+
end
|
15
|
+
|
16
|
+
def process(msg)
|
17
|
+
if job = jobs[msg.token]
|
18
|
+
job.process(msg)
|
19
|
+
|
20
|
+
if job.intermediate_handler && (job.pending_keys.size > 0)
|
21
|
+
|
22
|
+
unless job.pending_keys.size == 1
|
23
|
+
raise "IntermediateMessages are currently dispatched as they arrive, shouldn't have more than one key in pending_keys: #{job.pending_keys.inspect}"
|
24
|
+
end
|
25
|
+
|
26
|
+
key = job.pending_keys.first
|
27
|
+
handler = job.intermediate_handler_for_key(key)
|
28
|
+
if handler
|
29
|
+
case handler.arity
|
30
|
+
when 2
|
31
|
+
handler.call(job.intermediate_state[msg.from][key].last, job)
|
32
|
+
when 3
|
33
|
+
handler.call(key, msg.from, job.intermediate_state[msg.from][key].last)
|
34
|
+
when 4
|
35
|
+
handler.call(key, msg.from, job.intermediate_state[msg.from][key].last, job)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
job.reset_pending_intermediate_state_keys
|
40
|
+
end
|
41
|
+
|
42
|
+
if job.completed?
|
43
|
+
jobs.delete(job.token)
|
44
|
+
if job.completed
|
45
|
+
case job.completed.arity
|
46
|
+
when 1
|
47
|
+
job.completed.call(job.results)
|
48
|
+
when 2
|
49
|
+
job.completed.call(job.results, job)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end # JobWarden
|
56
|
+
|
57
|
+
class Job
|
58
|
+
attr_reader :results, :request, :token, :completed, :intermediate_state, :pending_keys, :intermediate_handler
|
59
|
+
attr_accessor :targets # This can be updated when a request gets picked up from the offline queue
|
60
|
+
|
61
|
+
def initialize(request, targets, inthandler = nil, blk = nil)
|
62
|
+
@request = request
|
63
|
+
@targets = targets
|
64
|
+
@token = @request.token
|
65
|
+
@results = {}
|
66
|
+
@intermediate_handler = inthandler
|
67
|
+
@pending_keys = []
|
68
|
+
@completed = blk
|
69
|
+
@intermediate_state = {}
|
70
|
+
end
|
71
|
+
|
72
|
+
def process(msg)
|
73
|
+
case msg
|
74
|
+
when Result
|
75
|
+
results[msg.from] = msg.results
|
76
|
+
targets.delete(msg.from)
|
77
|
+
when IntermediateMessage
|
78
|
+
intermediate_state[msg.from] ||= {}
|
79
|
+
intermediate_state[msg.from][msg.messagekey] ||= []
|
80
|
+
intermediate_state[msg.from][msg.messagekey] << msg.message
|
81
|
+
@pending_keys << msg.messagekey
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def intermediate_handler_for_key(key)
|
86
|
+
return nil unless @intermediate_handler
|
87
|
+
case @intermediate_handler
|
88
|
+
when Proc
|
89
|
+
@intermediate_handler
|
90
|
+
when Hash
|
91
|
+
@intermediate_handler[key] || @intermediate_handler['*']
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def reset_pending_intermediate_state_keys
|
96
|
+
@pending_keys = []
|
97
|
+
end
|
98
|
+
|
99
|
+
def completed?
|
100
|
+
targets.empty?
|
101
|
+
end
|
102
|
+
end # Job
|
103
|
+
|
104
|
+
end # Nanite
|