ezmobius-nanite 0.4.0 → 0.4.1.1

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.
Files changed (60) hide show
  1. data/README.rdoc +70 -20
  2. data/Rakefile +1 -1
  3. data/bin/nanite-agent +34 -8
  4. data/bin/nanite-mapper +18 -8
  5. data/lib/nanite.rb +71 -0
  6. data/lib/nanite/actor.rb +60 -0
  7. data/lib/nanite/actor_registry.rb +24 -0
  8. data/lib/nanite/admin.rb +138 -0
  9. data/lib/nanite/agent.rb +250 -0
  10. data/lib/nanite/amqp.rb +47 -0
  11. data/lib/nanite/cluster.rb +203 -0
  12. data/lib/nanite/config.rb +102 -0
  13. data/lib/nanite/console.rb +39 -0
  14. data/lib/nanite/daemonize.rb +13 -0
  15. data/lib/nanite/dispatcher.rb +90 -0
  16. data/lib/nanite/identity.rb +16 -0
  17. data/lib/nanite/job.rb +104 -0
  18. data/lib/nanite/local_state.rb +34 -0
  19. data/lib/nanite/log.rb +64 -0
  20. data/lib/nanite/log/formatter.rb +39 -0
  21. data/lib/nanite/mapper.rb +277 -0
  22. data/lib/nanite/mapper_proxy.rb +56 -0
  23. data/lib/nanite/packets.rb +231 -0
  24. data/lib/nanite/pid_file.rb +52 -0
  25. data/lib/nanite/reaper.rb +38 -0
  26. data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
  27. data/lib/nanite/security/certificate.rb +55 -0
  28. data/lib/nanite/security/certificate_cache.rb +66 -0
  29. data/lib/nanite/security/distinguished_name.rb +34 -0
  30. data/lib/nanite/security/encrypted_document.rb +46 -0
  31. data/lib/nanite/security/rsa_key_pair.rb +53 -0
  32. data/lib/nanite/security/secure_serializer.rb +67 -0
  33. data/lib/nanite/security/signature.rb +40 -0
  34. data/lib/nanite/security/static_certificate_store.rb +35 -0
  35. data/lib/nanite/security_provider.rb +47 -0
  36. data/lib/nanite/serializer.rb +52 -0
  37. data/lib/nanite/state.rb +164 -0
  38. data/lib/nanite/streaming.rb +125 -0
  39. data/lib/nanite/util.rb +51 -0
  40. data/spec/actor_registry_spec.rb +62 -0
  41. data/spec/actor_spec.rb +59 -0
  42. data/spec/agent_spec.rb +235 -0
  43. data/spec/cached_certificate_store_proxy_spec.rb +34 -0
  44. data/spec/certificate_cache_spec.rb +49 -0
  45. data/spec/certificate_spec.rb +27 -0
  46. data/spec/cluster_spec.rb +300 -0
  47. data/spec/dispatcher_spec.rb +136 -0
  48. data/spec/distinguished_name_spec.rb +24 -0
  49. data/spec/encrypted_document_spec.rb +21 -0
  50. data/spec/job_spec.rb +219 -0
  51. data/spec/local_state_spec.rb +112 -0
  52. data/spec/packet_spec.rb +218 -0
  53. data/spec/rsa_key_pair_spec.rb +33 -0
  54. data/spec/secure_serializer_spec.rb +41 -0
  55. data/spec/serializer_spec.rb +107 -0
  56. data/spec/signature_spec.rb +30 -0
  57. data/spec/spec_helper.rb +23 -0
  58. data/spec/static_certificate_store_spec.rb +30 -0
  59. data/spec/util_spec.rb +63 -0
  60. metadata +63 -2
@@ -0,0 +1,39 @@
1
+ require 'logger'
2
+ require 'time'
3
+
4
+ module Nanite
5
+ class Log
6
+ class Formatter < Logger::Formatter
7
+ @@show_time = true
8
+
9
+ def self.show_time=(show=false)
10
+ @@show_time = show
11
+ end
12
+
13
+ # Prints a log message as '[time] severity: message' if Nanite::Log::Formatter.show_time == true.
14
+ # Otherwise, doesn't print the time.
15
+ def call(severity, time, progname, msg)
16
+ if @@show_time
17
+ sprintf("[%s] %s: %s\n", time.rfc2822(), severity, msg2str(msg))
18
+ else
19
+ sprintf("%s: %s\n", severity, msg2str(msg))
20
+ end
21
+ end
22
+
23
+ # Converts some argument to a Logger.severity() call to a string. Regular strings pass through like
24
+ # normal, Exceptions get formatted as "message (class)\nbacktrace", and other random stuff gets
25
+ # put through "object.inspect"
26
+ def msg2str(msg)
27
+ case msg
28
+ when ::String
29
+ msg
30
+ when ::Exception
31
+ "#{ msg.message } (#{ msg.class })\n" <<
32
+ (msg.backtrace || []).join("\n")
33
+ else
34
+ msg.inspect
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,277 @@
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, :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 or :secure.
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
+ # Also using the secure format requires prior initialization of the serializer, see
47
+ # SecureSerializer.init
48
+ #
49
+ # log_level : the verbosity of logging, can be debug, info, warn, error or fatal.
50
+ #
51
+ # agent_timeout : how long to wait before an agent is considered to be offline
52
+ # and thus removed from the list of available agents.
53
+ #
54
+ # log_dir : log file path, defaults to the current working directory.
55
+ #
56
+ # console : true tells mapper to start interactive console
57
+ #
58
+ # daemonize : true tells mapper to daemonize
59
+ #
60
+ # pid_dir : path to the directory where the agent stores its pid file (only if daemonized)
61
+ # defaults to the root or the current working directory.
62
+ #
63
+ # offline_redelivery_frequency : The frequency in seconds that messages stored in the offline queue will be retrieved
64
+ # for attempted redelivery to the nanites. Default is 10 seconds.
65
+ #
66
+ # persistent : true instructs the AMQP broker to save messages to persistent storage so that they aren't lost when the
67
+ # broker is restarted. Default is false. Can be overriden on a per-message basis using the request and push methods.
68
+ #
69
+ # secure : use Security features of rabbitmq to restrict nanites to themselves
70
+ #
71
+ # Connection options:
72
+ #
73
+ # vhost : AMQP broker vhost that should be used
74
+ #
75
+ # user : AMQP broker user
76
+ #
77
+ # pass : AMQP broker password
78
+ #
79
+ # host : host AMQP broker (or node of interest) runs on,
80
+ # defaults to 0.0.0.0
81
+ #
82
+ # port : port AMQP broker (or node of interest) runs on,
83
+ # this defaults to 5672, port used by some widely
84
+ # used AMQP brokers (RabbitMQ and ZeroMQ)
85
+ #
86
+ # @api :public:
87
+ def self.start(options = {})
88
+ mapper = new(options)
89
+ mapper.run
90
+ mapper
91
+ end
92
+
93
+ def initialize(options)
94
+ @options = DEFAULT_OPTIONS.clone.merge(options)
95
+ root = options[:root] || @options[:root]
96
+ custom_config = if root
97
+ file = File.expand_path(File.join(root, 'config.yml'))
98
+ File.exists?(file) ? (YAML.load(IO.read(file)) || {}) : {}
99
+ else
100
+ {}
101
+ end
102
+ options.delete(:identity) unless options[:identity]
103
+ @options.update(custom_config.merge(options))
104
+ @identity = "mapper-#{@options[:identity]}"
105
+ @options[:file_root] ||= File.join(@options[:root], 'files')
106
+ @options.freeze
107
+ end
108
+
109
+ def run
110
+ log_path = false
111
+ if @options[:daemonize]
112
+ log_path = (@options[:log_dir] || @options[:root] || Dir.pwd)
113
+ end
114
+ Nanite::Log.init(@identity, log_path)
115
+ Nanite::Log.level = @options[:log_level] if @options[:log_level]
116
+ @serializer = Serializer.new(@options[:format])
117
+ pid_file = PidFile.new(@identity, @options)
118
+ pid_file.check
119
+ if @options[:daemonize]
120
+ daemonize
121
+ pid_file.write
122
+ at_exit { pid_file.remove }
123
+ end
124
+ @amq = start_amqp(@options)
125
+ @job_warden = JobWarden.new(@serializer)
126
+ @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis])
127
+ Nanite::Log.info('starting mapper')
128
+ setup_queues
129
+ start_console if @options[:console] && !@options[:daemonize]
130
+ end
131
+
132
+ # Make a nanite request which expects a response.
133
+ #
134
+ # ==== Parameters
135
+ # type<String>:: The dispatch route for the request
136
+ # payload<Object>:: Payload to send. This will get marshalled en route
137
+ #
138
+ # ==== Options
139
+ # :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
140
+ # :least_loaded:: Pick the nanite which has the lowest load.
141
+ # :all:: Send the request to all nanites which respond to the service.
142
+ # :random:: Randomly pick a nanite.
143
+ # :rr: Select a nanite according to round robin ordering.
144
+ # :target<String>:: Select a specific nanite via identity, rather than using
145
+ # a selector.
146
+ # :offline_failsafe<Boolean>:: Store messages in an offline queue when all
147
+ # the nanites are offline. Messages will be redelivered when nanites come online.
148
+ # Default is false unless the mapper was started with the --offline-failsafe flag.
149
+ # :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
150
+ # storage so that it isnt lost when the broker is restarted.
151
+ # Default is false unless the mapper was started with the --persistent flag.
152
+ # :intermediate_handler:: Takes a lambda to call when an IntermediateMessage
153
+ # event arrives from a nanite. If passed a Hash, hash keys should correspond to
154
+ # the IntermediateMessage keys provided by the nanite, and each should have a value
155
+ # that is a lambda/proc taking the parameters specified here. Can supply a key '*'
156
+ # as a catch-all for unmatched keys.
157
+ #
158
+ # ==== Block Parameters for intermediate_handler
159
+ # key<String>:: array of unique keys for which intermediate state has been received
160
+ # since the last call to this block.
161
+ # nanite<String>:: nanite which sent the message.
162
+ # state:: most recently delivered intermediate state for the key provided.
163
+ # job:: (optional) -- if provided, this parameter gets the whole job object, if there's
164
+ # a reason to do more complex work with the job.
165
+ #
166
+ # ==== Block Parameters
167
+ # :results<Object>:: The returned value from the nanite actor.
168
+ #
169
+ # @api :public:
170
+ def request(type, payload = '', opts = {}, &blk)
171
+ request = build_deliverable(Request, type, payload, opts)
172
+ send_request(request, opts, &blk)
173
+ end
174
+
175
+ # Send request with pre-built request instance
176
+ def send_request(request, opts = {}, &blk)
177
+ request.reply_to = identity
178
+ intm_handler = opts.delete(:intermediate_handler)
179
+ targets = cluster.targets_for(request)
180
+ if !targets.empty?
181
+ job = job_warden.new_job(request, targets, intm_handler, blk)
182
+ cluster.route(request, job.targets)
183
+ job
184
+ elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
185
+ job_warden.new_job(request, [], intm_handler, blk)
186
+ cluster.publish(request, 'mapper-offline')
187
+ :offline
188
+ else
189
+ false
190
+ end
191
+ end
192
+
193
+ # Make a nanite request which does not expect a response.
194
+ #
195
+ # ==== Parameters
196
+ # type<String>:: The dispatch route for the request
197
+ # payload<Object>:: Payload to send. This will get marshalled en route
198
+ #
199
+ # ==== Options
200
+ # :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
201
+ # :least_loaded:: Pick the nanite which has the lowest load.
202
+ # :all:: Send the request to all nanites which respond to the service.
203
+ # :random:: Randomly pick a nanite.
204
+ # :rr: Select a nanite according to round robin ordering.
205
+ # :offline_failsafe<Boolean>:: Store messages in an offline queue when all
206
+ # the nanites are offline. Messages will be redelivered when nanites come online.
207
+ # Default is false unless the mapper was started with the --offline-failsafe flag.
208
+ # :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
209
+ # storage so that it isnt lost when the broker is restarted.
210
+ # Default is false unless the mapper was started with the --persistent flag.
211
+ #
212
+ # @api :public:
213
+ def push(type, payload = '', opts = {})
214
+ push = build_deliverable(Push, type, payload, opts)
215
+ targets = cluster.targets_for(push)
216
+ if !targets.empty?
217
+ cluster.route(push, targets)
218
+ true
219
+ elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
220
+ cluster.publish(push, 'mapper-offline')
221
+ :offline
222
+ else
223
+ false
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ def build_deliverable(deliverable_type, type, payload, opts)
230
+ deliverable = deliverable_type.new(type, payload, opts)
231
+ deliverable.from = identity
232
+ deliverable.token = Identity.generate
233
+ deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
234
+ deliverable
235
+ end
236
+
237
+ def setup_queues
238
+ setup_offline_queue
239
+ setup_message_queue
240
+ end
241
+
242
+ def setup_offline_queue
243
+ offline_queue = amq.queue('mapper-offline', :durable => true)
244
+ offline_queue.subscribe(:ack => true) do |info, deliverable|
245
+ deliverable = serializer.load(deliverable)
246
+ targets = cluster.targets_for(deliverable)
247
+ unless targets.empty?
248
+ info.ack
249
+ if deliverable.kind_of?(Request)
250
+ if job = job_warden.jobs[deliverable.token]
251
+ job.targets = targets
252
+ else
253
+ deliverable.reply_to = identity
254
+ job_warden.new_job(deliverable, targets)
255
+ end
256
+ end
257
+ cluster.route(deliverable, targets)
258
+ end
259
+ end
260
+
261
+ EM.add_periodic_timer(options[:offline_redelivery_frequency]) { offline_queue.recover }
262
+ end
263
+
264
+ def setup_message_queue
265
+ amq.queue(identity, :exclusive => true).bind(amq.fanout(identity)).subscribe do |msg|
266
+ begin
267
+ msg = serializer.load(msg)
268
+ Nanite::Log.debug("got result from #{msg.from}: #{msg.results.inspect}")
269
+ job_warden.process(msg)
270
+ rescue Exception => e
271
+ Nanite::Log.error("Error handling result: #{e.message}")
272
+ end
273
+ end
274
+ end
275
+ end
276
+ end
277
+
@@ -0,0 +1,56 @@
1
+ module Nanite
2
+
3
+ # This class allows sending requests to nanite agents without having
4
+ # to run a local mapper.
5
+ # It is used by Actor.request which can be used by actors than need
6
+ # to send requests to remote agents.
7
+ # All requests go through the mapper for security purposes.
8
+ class MapperProxy
9
+
10
+ $:.push File.dirname(__FILE__)
11
+ require 'amqp'
12
+
13
+ include AMQPHelper
14
+
15
+ attr_accessor :pending_requests, :identity, :options, :amqp, :serializer
16
+
17
+ # Accessor for actor
18
+ def self.instance
19
+ @@instance
20
+ end
21
+
22
+ def initialize(id, opts)
23
+ @options = opts || {}
24
+ @identity = id
25
+ @pending_requests = {}
26
+ @amqp = start_amqp(options)
27
+ @serializer = Serializer.new(options[:format])
28
+ @@instance = self
29
+ end
30
+
31
+ # Send request to given agent through the mapper
32
+ def request(type, payload = '', opts = {}, &blk)
33
+ raise "Mapper proxy not initialized" unless identity && options
34
+ request = Request.new(type, payload, opts)
35
+ request.from = identity
36
+ request.token = Identity.generate
37
+ request.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
38
+ pending_requests[request.token] =
39
+ { :intermediate_handler => opts[:intermediate_handler], :result_handler => blk }
40
+ amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(request))
41
+ end
42
+
43
+ # Handle intermediary result
44
+ def handle_intermediate_result(res)
45
+ handlers = pending_requests[res.token]
46
+ handlers[:intermediate_handler].call(res) if handlers && handlers[:intermediate_handler]
47
+ end
48
+
49
+ # Handle final result
50
+ def handle_result(res)
51
+ handlers = pending_requests.delete(res.token)
52
+ handlers[:result_handler].call(res) if handlers && handlers[:result_handler]
53
+ end
54
+
55
+ end
56
+ end
@@ -0,0 +1,231 @@
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, :tags
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
+ @tags = opts[:tags] || []
87
+ end
88
+ def self.json_create(o)
89
+ i = o['data']
90
+ new(i['type'], i['payload'], {:from => i['from'], :token => i['token'], :reply_to => i['reply_to'], :selector => i['selector'],
91
+ :target => i['target'], :persistent => i['persistent'], :tags => i['tags']})
92
+ end
93
+ end
94
+
95
+ # packet that means a work push from mapper
96
+ # to actor node
97
+ #
98
+ # type is a service name
99
+ # payload is arbitrary data that is transferred from mapper to actor
100
+ #
101
+ # Options:
102
+ # from is sender identity
103
+ # token is a generated request id that mapper uses to identify replies
104
+ # selector is the selector used to route the request
105
+ # target is the target nanite for the request
106
+ # persistent signifies if this request should be saved to persistent storage by the AMQP broker
107
+ class Push < Packet
108
+ attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
109
+ DEFAULT_OPTIONS = {:selector => :least_loaded}
110
+ def initialize(type, payload, opts={})
111
+ opts = DEFAULT_OPTIONS.merge(opts)
112
+ @type = type
113
+ @payload = payload
114
+ @from = opts[:from]
115
+ @token = opts[:token]
116
+ @selector = opts[:selector]
117
+ @target = opts[:target]
118
+ @persistent = opts[:persistent]
119
+ @tags = opts[:tags] || []
120
+ end
121
+ def self.json_create(o)
122
+ i = o['data']
123
+ new(i['type'], i['payload'], {:from => i['from'], :token => i['token'], :selector => i['selector'],
124
+ :target => i['target'], :persistent => i['persistent'], :tags => i['tags']})
125
+ end
126
+ end
127
+
128
+ # packet that means a work result notification sent from actor to mapper
129
+ #
130
+ # from is sender identity
131
+ # results is arbitrary data that is transferred from actor, a result of actor's work
132
+ # token is a generated request id that mapper uses to identify replies
133
+ # to is identity of the node result should be delivered to
134
+ class Result < Packet
135
+ attr_accessor :token, :results, :to, :from
136
+ def initialize(token, to, results, from)
137
+ @token = token
138
+ @to = to
139
+ @from = from
140
+ @results = results
141
+ end
142
+ def self.json_create(o)
143
+ i = o['data']
144
+ new(i['token'], i['to'], i['results'], i['from'])
145
+ end
146
+ end
147
+
148
+ # packet that means an intermediate status notification sent from actor to mapper. is appended to a list of messages matching messagekey.
149
+ #
150
+ # from is sender identity
151
+ # messagekey is a string that can become part of a redis key, which identifies the name under which the message is stored
152
+ # message is arbitrary data that is transferred from actor, an intermediate result of actor's work
153
+ # token is a generated request id that mapper uses to identify replies
154
+ # to is identity of the node result should be delivered to
155
+ class IntermediateMessage < Packet
156
+ attr_accessor :token, :messagekey, :message, :to, :from
157
+ def initialize(token, to, from, messagekey, message)
158
+ @token = token
159
+ @to = to
160
+ @from = from
161
+ @messagekey = messagekey
162
+ @message = message
163
+ end
164
+ def self.json_create(o)
165
+ i = o['data']
166
+ new(i['token'], i['to'], i['from'], i['messagekey'], i['message'])
167
+ end
168
+ end
169
+
170
+ # packet that means an availability notification sent from actor to mapper
171
+ #
172
+ # from is sender identity
173
+ # services is a list of services provided by the node
174
+ # status is a load of the node by default, but may be any criteria
175
+ # agent may use to report it's availability, load, etc
176
+ class Register < Packet
177
+ attr_accessor :identity, :services, :status, :tags
178
+ def initialize(identity, services, status, tags)
179
+ @status = status
180
+ @tags = tags
181
+ @identity = identity
182
+ @services = services
183
+ end
184
+ def self.json_create(o)
185
+ i = o['data']
186
+ new(i['identity'], i['services'], i['status'], i['tags'])
187
+ end
188
+ end
189
+
190
+ # packet that means deregister an agent from the mappers
191
+ #
192
+ # from is sender identity
193
+ class UnRegister < Packet
194
+ attr_accessor :identity
195
+ def initialize(identity)
196
+ @identity = identity
197
+ end
198
+ def self.json_create(o)
199
+ i = o['data']
200
+ new(i['identity'])
201
+ end
202
+ end
203
+
204
+ # heartbeat packet
205
+ #
206
+ # identity is sender's identity
207
+ # status is sender's status (see Register packet documentation)
208
+ class Ping < Packet
209
+ attr_accessor :identity, :status
210
+ def initialize(identity, status)
211
+ @status = status
212
+ @identity = identity
213
+ end
214
+ def self.json_create(o)
215
+ i = o['data']
216
+ new(i['identity'], i['status'])
217
+ end
218
+ end
219
+
220
+ # packet that is sent by workers to the mapper
221
+ # when worker initially comes online to advertise
222
+ # it's services
223
+ class Advertise < Packet
224
+ def initialize
225
+ end
226
+ def self.json_create(o)
227
+ new
228
+ end
229
+ end
230
+ end
231
+