br-nanite 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README.rdoc +356 -0
- data/Rakefile +74 -0
- data/TODO +24 -0
- data/bin/nanite-admin +59 -0
- data/bin/nanite-agent +30 -0
- data/bin/nanite-mapper +22 -0
- data/lib/nanite.rb +58 -0
- data/lib/nanite/actor.rb +64 -0
- data/lib/nanite/actor_registry.rb +25 -0
- data/lib/nanite/admin.rb +147 -0
- data/lib/nanite/agent.rb +163 -0
- data/lib/nanite/amqp.rb +47 -0
- data/lib/nanite/cluster.rb +110 -0
- data/lib/nanite/config.rb +80 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +12 -0
- data/lib/nanite/dispatcher.rb +59 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +50 -0
- data/lib/nanite/log.rb +23 -0
- data/lib/nanite/mapper.rb +214 -0
- data/lib/nanite/packets.rb +192 -0
- data/lib/nanite/reaper.rb +30 -0
- data/lib/nanite/serializer.rb +40 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +51 -0
- metadata +104 -0
@@ -0,0 +1,59 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Dispatcher
|
3
|
+
attr_reader :registry, :serializer, :identity, :log, :amq, :options
|
4
|
+
|
5
|
+
def initialize(amq, registry, serializer, identity, log, options)
|
6
|
+
@amq = amq
|
7
|
+
@registry = registry
|
8
|
+
@serializer = serializer
|
9
|
+
@identity = identity
|
10
|
+
@log = log
|
11
|
+
@options = options
|
12
|
+
end
|
13
|
+
|
14
|
+
def dispatch(deliverable)
|
15
|
+
result = begin
|
16
|
+
prefix, meth = deliverable.type.split('/')[1..-1]
|
17
|
+
actor = registry.actor_for(prefix)
|
18
|
+
actor.send(meth, deliverable.payload)
|
19
|
+
rescue Exception => e
|
20
|
+
handle_exception(actor, meth, deliverable, e)
|
21
|
+
end
|
22
|
+
|
23
|
+
if deliverable.kind_of?(Request)
|
24
|
+
result = Result.new(deliverable.token, deliverable.reply_to, result, identity)
|
25
|
+
amq.queue(deliverable.reply_to, :no_declare => options[:secure]).publish(serializer.dump(result))
|
26
|
+
end
|
27
|
+
|
28
|
+
result
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def describe_error(e)
|
34
|
+
"#{e.class.name}: #{e.message}\n #{e.backtrace.join("\n ")}"
|
35
|
+
end
|
36
|
+
|
37
|
+
def handle_exception(actor, meth, deliverable, e)
|
38
|
+
error = describe_error(e)
|
39
|
+
log.error(error)
|
40
|
+
begin
|
41
|
+
if actor.class.instance_exception_callback
|
42
|
+
case actor.class.instance_exception_callback
|
43
|
+
when Symbol, String
|
44
|
+
actor.send(actor.class.instance_exception_callback, meth.to_sym, deliverable, e)
|
45
|
+
when Proc
|
46
|
+
actor.instance_exec(meth.to_sym, deliverable, e, &actor.class.instance_exception_callback)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
if Nanite::Actor.superclass_exception_callback
|
50
|
+
Nanite::Actor.superclass_exception_callback.call(actor, meth.to_sym, deliverable, e)
|
51
|
+
end
|
52
|
+
rescue Exception => e1
|
53
|
+
error = describe_error(e1)
|
54
|
+
log.error(error)
|
55
|
+
end
|
56
|
+
error
|
57
|
+
end
|
58
|
+
end
|
59
|
+
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,50 @@
|
|
1
|
+
module Nanite
|
2
|
+
class JobWarden
|
3
|
+
attr_reader :serializer, :jobs, :log
|
4
|
+
|
5
|
+
def initialize(serializer, log)
|
6
|
+
@serializer = serializer
|
7
|
+
@log = log
|
8
|
+
@jobs = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def new_job(request, targets, blk = nil)
|
12
|
+
job = Job.new(request, targets, blk)
|
13
|
+
jobs[job.token] = job
|
14
|
+
job
|
15
|
+
end
|
16
|
+
|
17
|
+
def process(msg)
|
18
|
+
msg = serializer.load(msg)
|
19
|
+
log.debug("processing message: #{msg.inspect}")
|
20
|
+
if job = jobs[msg.token]
|
21
|
+
job.process(msg)
|
22
|
+
if job.completed?
|
23
|
+
jobs.delete(job.token)
|
24
|
+
job.completed.call(job.results) if job.completed
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Job
|
31
|
+
attr_reader :results, :request, :token, :targets, :completed
|
32
|
+
|
33
|
+
def initialize(request, targets, blk)
|
34
|
+
@request = request
|
35
|
+
@targets = targets
|
36
|
+
@token = @request.token
|
37
|
+
@results = {}
|
38
|
+
@completed = blk
|
39
|
+
end
|
40
|
+
|
41
|
+
def process(msg)
|
42
|
+
results[msg.from] = msg.results
|
43
|
+
targets.delete(msg.from)
|
44
|
+
end
|
45
|
+
|
46
|
+
def completed?
|
47
|
+
targets.empty?
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
data/lib/nanite/log.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Log
|
3
|
+
def initialize(options, identity)
|
4
|
+
@file = File.join((options[:log_dir] || options[:root] || Dir.pwd), "nanite.#{identity}.log")
|
5
|
+
@logger = Logger.new(file)
|
6
|
+
@logger.level = log_level(options[:log_level])
|
7
|
+
end
|
8
|
+
|
9
|
+
def file
|
10
|
+
@file
|
11
|
+
end
|
12
|
+
|
13
|
+
def method_missing(method, *args)
|
14
|
+
@logger.send(method, *args)
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def log_level(level)
|
20
|
+
{'fatal' => Logger::FATAL, 'error' => Logger::ERROR, 'warn' => Logger::WARN, 'info' => Logger::INFO, 'debug' => Logger::DEBUG}[level] || Logger::INFO
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,214 @@
|
|
1
|
+
module Nanite
|
2
|
+
# Mappers are control nodes in nanite clusters. Nanite clusters
|
3
|
+
# can follow peer-to-peer model of communication as well as client-server,
|
4
|
+
# and mappers are nodes that know who to send work requests to agents.
|
5
|
+
#
|
6
|
+
# Mappers can reside inside a front end web application written in Merb/Rails
|
7
|
+
# and distribute heavy lifting to actors that register with the mapper as soon
|
8
|
+
# as they go online.
|
9
|
+
#
|
10
|
+
# Each mapper tracks nanites registered with it. It periodically checks
|
11
|
+
# when the last time a certain nanite sent a heartbeat notification,
|
12
|
+
# and removes those that have timed out from the list of available workers.
|
13
|
+
# As soon as a worker goes back online again it re-registers itself
|
14
|
+
# and the mapper adds it to the list and makes it available to
|
15
|
+
# be called again.
|
16
|
+
#
|
17
|
+
# This makes Nanite clusters self-healing and immune to individual node
|
18
|
+
# failures.
|
19
|
+
class Mapper
|
20
|
+
include AMQPHelper
|
21
|
+
include ConsoleHelper
|
22
|
+
include DaemonizeHelper
|
23
|
+
|
24
|
+
attr_reader :cluster, :identity, :job_warden, :options, :serializer, :log, :amq
|
25
|
+
|
26
|
+
DEFAULT_OPTIONS = COMMON_DEFAULT_OPTIONS.merge({:user => 'mapper', :identity => Identity.generate, :agent_timeout => 15,
|
27
|
+
:offline_redelivery_frequency => 10, :persistent => false, :offline_failsafe => false}) unless defined?(DEFAULT_OPTIONS)
|
28
|
+
|
29
|
+
# Initializes a new mapper and establishes
|
30
|
+
# AMQP connection. This must be used inside EM.run block or if EventMachine reactor
|
31
|
+
# is already started, for instance, by a Thin server that your Merb/Rails
|
32
|
+
# application runs on.
|
33
|
+
#
|
34
|
+
# Mapper options:
|
35
|
+
#
|
36
|
+
# identity : identity of this mapper, may be any string
|
37
|
+
#
|
38
|
+
# format : format to use for packets serialization. Can be :marshal, :json or :yaml.
|
39
|
+
# Defaults to Ruby's Marshall format. For interoperability with
|
40
|
+
# AMQP clients implemented in other languages, use JSON.
|
41
|
+
#
|
42
|
+
# Note that Nanite uses JSON gem,
|
43
|
+
# and ActiveSupport's JSON encoder may cause clashes
|
44
|
+
# if ActiveSupport is loaded after JSON gem.
|
45
|
+
#
|
46
|
+
# log_level : the verbosity of logging, can be debug, info, warn, error or fatal.
|
47
|
+
#
|
48
|
+
# agent_timeout : how long to wait before an agent is considered to be offline
|
49
|
+
# and thus removed from the list of available agents.
|
50
|
+
#
|
51
|
+
# log_dir : log file path, defaults to the current working directory.
|
52
|
+
#
|
53
|
+
# console : true tells mapper to start interactive console
|
54
|
+
#
|
55
|
+
# daemonize : true tells mapper to daemonize
|
56
|
+
#
|
57
|
+
# offline_redelivery_frequency : The frequency in seconds that messages stored in the offline queue will be retrieved
|
58
|
+
# for attempted redelivery to the nanites. Default is 10 seconds.
|
59
|
+
#
|
60
|
+
# persistent : true instructs the AMQP broker to save messages to persistent storage so that they aren't lost when the
|
61
|
+
# broker is restarted. Default is false. Can be overriden on a per-message basis using the request and push methods.
|
62
|
+
#
|
63
|
+
# secure : use Security features of rabbitmq to restrict nanites to themselves
|
64
|
+
#
|
65
|
+
# Connection options:
|
66
|
+
#
|
67
|
+
# vhost : AMQP broker vhost that should be used
|
68
|
+
#
|
69
|
+
# user : AMQP broker user
|
70
|
+
#
|
71
|
+
# pass : AMQP broker password
|
72
|
+
#
|
73
|
+
# host : host AMQP broker (or node of interest) runs on,
|
74
|
+
# defaults to 0.0.0.0
|
75
|
+
#
|
76
|
+
# port : port AMQP broker (or node of interest) runs on,
|
77
|
+
# this defaults to 5672, port used by some widely
|
78
|
+
# used AMQP brokers (RabbitMQ and ZeroMQ)
|
79
|
+
#
|
80
|
+
# @api :public:
|
81
|
+
def self.start(options = {})
|
82
|
+
new(options)
|
83
|
+
end
|
84
|
+
|
85
|
+
def initialize(options)
|
86
|
+
@options = DEFAULT_OPTIONS.merge(options)
|
87
|
+
@identity = "mapper-#{@options[:identity]}"
|
88
|
+
@log = Log.new(@options, @identity)
|
89
|
+
@serializer = Serializer.new(@options[:format])
|
90
|
+
daemonize if @options[:daemonize]
|
91
|
+
@amq =start_amqp(@options)
|
92
|
+
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @log, @serializer)
|
93
|
+
@job_warden = JobWarden.new(@serializer, @log)
|
94
|
+
@log.info('starting mapper')
|
95
|
+
setup_queues
|
96
|
+
start_console if @options[:console] && !@options[:daemonize]
|
97
|
+
end
|
98
|
+
|
99
|
+
# Make a nanite request which expects a response.
|
100
|
+
#
|
101
|
+
# ==== Parameters
|
102
|
+
# type<String>:: The dispatch route for the request
|
103
|
+
# payload<Object>:: Payload to send. This will get marshalled en route
|
104
|
+
#
|
105
|
+
# ==== Options
|
106
|
+
# :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
|
107
|
+
# :least_loaded:: Pick the nanite which has the lowest load.
|
108
|
+
# :all:: Send the request to all nanites which respond to the service.
|
109
|
+
# :random:: Randomly pick a nanite.
|
110
|
+
# :rr: Select a nanite according to round robin ordering.
|
111
|
+
# :target<String>:: Select a specific nanite via identity, rather than using
|
112
|
+
# a selector.
|
113
|
+
# :offline_failsafe<Boolean>:: Store messages in an offline queue when all
|
114
|
+
# the nanites are offline. Messages will be redelivered when nanites come online.
|
115
|
+
# Default is false unless the mapper was started with the --offline-failsafe flag.
|
116
|
+
# :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
|
117
|
+
# storage so that it isnt lost when the broker is restarted.
|
118
|
+
# Default is false unless the mapper was started with the --persistent flag.
|
119
|
+
#
|
120
|
+
# ==== Block Parameters
|
121
|
+
# :results<Object>:: The returned value from the nanite actor.
|
122
|
+
#
|
123
|
+
# @api :public:
|
124
|
+
def request(type, payload = '', opts = {}, &blk)
|
125
|
+
request = build_deliverable(Request, type, payload, opts)
|
126
|
+
request.reply_to = identity
|
127
|
+
targets = cluster.targets_for(request)
|
128
|
+
if !targets.empty?
|
129
|
+
job = job_warden.new_job(request, targets, blk)
|
130
|
+
cluster.route(request, job.targets)
|
131
|
+
job
|
132
|
+
elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
|
133
|
+
cluster.publish(request, 'mapper-offline')
|
134
|
+
:offline
|
135
|
+
else
|
136
|
+
false
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Make a nanite request which does not expect a response.
|
141
|
+
#
|
142
|
+
# ==== Parameters
|
143
|
+
# type<String>:: The dispatch route for the request
|
144
|
+
# payload<Object>:: Payload to send. This will get marshalled en route
|
145
|
+
#
|
146
|
+
# ==== Options
|
147
|
+
# :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
|
148
|
+
# :least_loaded:: Pick the nanite which has the lowest load.
|
149
|
+
# :all:: Send the request to all nanites which respond to the service.
|
150
|
+
# :random:: Randomly pick a nanite.
|
151
|
+
# :rr: Select a nanite according to round robin ordering.
|
152
|
+
# :offline_failsafe<Boolean>:: Store messages in an offline queue when all
|
153
|
+
# the nanites are offline. Messages will be redelivered when nanites come online.
|
154
|
+
# Default is false unless the mapper was started with the --offline-failsafe flag.
|
155
|
+
# :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
|
156
|
+
# storage so that it isnt lost when the broker is restarted.
|
157
|
+
# Default is false unless the mapper was started with the --persistent flag.
|
158
|
+
#
|
159
|
+
# @api :public:
|
160
|
+
def push(type, payload = '', opts = {})
|
161
|
+
push = build_deliverable(Push, type, payload, opts)
|
162
|
+
targets = cluster.targets_for(push)
|
163
|
+
if !targets.empty?
|
164
|
+
cluster.route(push, targets)
|
165
|
+
true
|
166
|
+
elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
|
167
|
+
cluster.publish(push, 'mapper-offline')
|
168
|
+
:offline
|
169
|
+
else
|
170
|
+
false
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
private
|
175
|
+
|
176
|
+
def build_deliverable(deliverable_type, type, payload, opts)
|
177
|
+
deliverable = deliverable_type.new(type, payload, opts)
|
178
|
+
deliverable.from = identity
|
179
|
+
deliverable.token = Identity.generate
|
180
|
+
deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
181
|
+
deliverable
|
182
|
+
end
|
183
|
+
|
184
|
+
def setup_queues
|
185
|
+
setup_offline_queue
|
186
|
+
setup_message_queue
|
187
|
+
end
|
188
|
+
|
189
|
+
def setup_offline_queue
|
190
|
+
offline_queue = amq.queue('mapper-offline', :durable => true)
|
191
|
+
offline_queue.subscribe(:ack => true) do |info, deliverable|
|
192
|
+
deliverable = serializer.load(deliverable)
|
193
|
+
targets = cluster.targets_for(deliverable)
|
194
|
+
unless targets.empty?
|
195
|
+
info.ack
|
196
|
+
if deliverable.kind_of?(Request)
|
197
|
+
deliverable.reply_to = identity
|
198
|
+
job_warden.new_job(deliverable, targets)
|
199
|
+
end
|
200
|
+
cluster.route(deliverable, targets)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
EM.add_periodic_timer(options[:offline_redelivery_frequency]) { offline_queue.recover }
|
205
|
+
end
|
206
|
+
|
207
|
+
def setup_message_queue
|
208
|
+
amq.queue(identity, :exclusive => true).bind(amq.fanout(identity)).subscribe do |msg|
|
209
|
+
job_warden.process(msg)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
@@ -0,0 +1,192 @@
|
|
1
|
+
module Nanite
|
2
|
+
# Base class for all Nanite packets,
|
3
|
+
# knows how to dump itself to JSON
|
4
|
+
class Packet
|
5
|
+
def initialize
|
6
|
+
raise NotImplementedError.new("#{self.class.name} is an abstract class.")
|
7
|
+
end
|
8
|
+
def to_json(*a)
|
9
|
+
{
|
10
|
+
'json_class' => self.class.name,
|
11
|
+
'data' => instance_variables.inject({}) {|m,ivar| m[ivar.sub(/@/,'')] = instance_variable_get(ivar); m }
|
12
|
+
}.to_json(*a)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# packet that means start of a file transfer
|
17
|
+
# operation
|
18
|
+
class FileStart < Packet
|
19
|
+
attr_accessor :filename, :token, :dest
|
20
|
+
def initialize(filename, dest, token)
|
21
|
+
@filename = filename
|
22
|
+
@dest = dest
|
23
|
+
@token = token
|
24
|
+
end
|
25
|
+
|
26
|
+
def self.json_create(o)
|
27
|
+
i = o['data']
|
28
|
+
new(i['filename'], i['dest'], i['token'])
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# packet that means end of a file transfer
|
33
|
+
# operation
|
34
|
+
class FileEnd < Packet
|
35
|
+
attr_accessor :token, :meta
|
36
|
+
def initialize(token, meta)
|
37
|
+
@token = token
|
38
|
+
@meta = meta
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.json_create(o)
|
42
|
+
i = o['data']
|
43
|
+
new(i['token'], i['meta'])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# packet that carries data chunks during a file transfer
|
48
|
+
class FileChunk < Packet
|
49
|
+
attr_accessor :chunk, :token
|
50
|
+
def initialize(token, chunk=nil)
|
51
|
+
@chunk = chunk
|
52
|
+
@token = token
|
53
|
+
end
|
54
|
+
def self.json_create(o)
|
55
|
+
i = o['data']
|
56
|
+
new(i['token'], i['chunk'])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# packet that means a work request from mapper
|
61
|
+
# to actor node
|
62
|
+
#
|
63
|
+
# type is a service name
|
64
|
+
# payload is arbitrary data that is transferred from mapper to actor
|
65
|
+
#
|
66
|
+
# Options:
|
67
|
+
# from is sender identity
|
68
|
+
# token is a generated request id that mapper uses to identify replies
|
69
|
+
# reply_to is identity of the node actor replies to, usually a mapper itself
|
70
|
+
# selector is the selector used to route the request
|
71
|
+
# target is the target nanite for the request
|
72
|
+
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
73
|
+
class Request < Packet
|
74
|
+
attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent
|
75
|
+
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
76
|
+
def initialize(type, payload, opts={})
|
77
|
+
opts = DEFAULT_OPTIONS.merge(opts)
|
78
|
+
@type = type
|
79
|
+
@payload = payload
|
80
|
+
@from = opts[:from]
|
81
|
+
@token = opts[:token]
|
82
|
+
@reply_to = opts[:reply_to]
|
83
|
+
@selector = opts[:selector]
|
84
|
+
@target = opts[:target]
|
85
|
+
@persistent = opts[:persistent]
|
86
|
+
end
|
87
|
+
def self.json_create(o)
|
88
|
+
i = o['data']
|
89
|
+
new(i['type'], i['payload'], {:from => i['from'], :token => i['token'], :reply_to => i['reply_to'], :selector => i['selector'],
|
90
|
+
:target => i['target'], :persistent => i['persistent']})
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# packet that means a work push from mapper
|
95
|
+
# to actor node
|
96
|
+
#
|
97
|
+
# type is a service name
|
98
|
+
# payload is arbitrary data that is transferred from mapper to actor
|
99
|
+
#
|
100
|
+
# Options:
|
101
|
+
# from is sender identity
|
102
|
+
# token is a generated request id that mapper uses to identify replies
|
103
|
+
# selector is the selector used to route the request
|
104
|
+
# target is the target nanite for the request
|
105
|
+
# persistent signifies if this request should be saved to persistent storage by the AMQP broker
|
106
|
+
class Push < Packet
|
107
|
+
attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent
|
108
|
+
DEFAULT_OPTIONS = {:selector => :least_loaded}
|
109
|
+
def initialize(type, payload, opts={})
|
110
|
+
opts = DEFAULT_OPTIONS.merge(opts)
|
111
|
+
@type = type
|
112
|
+
@payload = payload
|
113
|
+
@from = opts[:from]
|
114
|
+
@token = opts[:token]
|
115
|
+
@selector = opts[:selector]
|
116
|
+
@target = opts[:target]
|
117
|
+
@persistent = opts[:persistent]
|
118
|
+
end
|
119
|
+
def self.json_create(o)
|
120
|
+
i = o['data']
|
121
|
+
new(i['type'], i['payload'], {:from => i['from'], :token => i['token'], :selector => i['selector'],
|
122
|
+
:target => i['target'], :persistent => i['persistent']})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# packet that means a work result notification sent from actor to mapper
|
127
|
+
#
|
128
|
+
# from is sender identity
|
129
|
+
# results is arbitrary data that is transferred from actor, a result of actor's work
|
130
|
+
# token is a generated request id that mapper uses to identify replies
|
131
|
+
# to is identity of the node result should be delivered to
|
132
|
+
class Result < Packet
|
133
|
+
attr_accessor :token, :results, :to, :from
|
134
|
+
def initialize(token, to, results, from)
|
135
|
+
@token = token
|
136
|
+
@to = to
|
137
|
+
@from = from
|
138
|
+
@results = results
|
139
|
+
end
|
140
|
+
def self.json_create(o)
|
141
|
+
i = o['data']
|
142
|
+
new(i['token'], i['to'], i['results'], i['from'])
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# packet that means an availability notification sent from actor to mapper
|
147
|
+
#
|
148
|
+
# from is sender identity
|
149
|
+
# services is a list of services provided by the node
|
150
|
+
# status is a load of the node by default, but may be any criteria
|
151
|
+
# agent may use to report it's availability, load, etc
|
152
|
+
class Register < Packet
|
153
|
+
attr_accessor :identity, :services, :status
|
154
|
+
def initialize(identity, services, status)
|
155
|
+
@status = status
|
156
|
+
@identity = identity
|
157
|
+
@services = services
|
158
|
+
end
|
159
|
+
def self.json_create(o)
|
160
|
+
i = o['data']
|
161
|
+
new(i['identity'], i['services'], i['status'])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
# heartbeat packet
|
166
|
+
#
|
167
|
+
# identity is sender's identity
|
168
|
+
# status is sender's status (see Register packet documentation)
|
169
|
+
class Ping < Packet
|
170
|
+
attr_accessor :identity, :status
|
171
|
+
def initialize(identity, status)
|
172
|
+
@status = status
|
173
|
+
@identity = identity
|
174
|
+
end
|
175
|
+
def self.json_create(o)
|
176
|
+
i = o['data']
|
177
|
+
new(i['identity'], i['status'])
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# packet that is sent by workers to the mapper
|
182
|
+
# when worker initially comes online to advertise
|
183
|
+
# it's services
|
184
|
+
class Advertise < Packet
|
185
|
+
def initialize
|
186
|
+
end
|
187
|
+
def self.json_create(o)
|
188
|
+
new
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|