nofxx-nanite 0.4.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (63) hide show
  1. data/LICENSE +201 -0
  2. data/README.rdoc +430 -0
  3. data/Rakefile +76 -0
  4. data/TODO +24 -0
  5. data/bin/nanite-admin +65 -0
  6. data/bin/nanite-agent +79 -0
  7. data/bin/nanite-mapper +50 -0
  8. data/lib/nanite.rb +74 -0
  9. data/lib/nanite/actor.rb +71 -0
  10. data/lib/nanite/actor_registry.rb +26 -0
  11. data/lib/nanite/admin.rb +138 -0
  12. data/lib/nanite/agent.rb +258 -0
  13. data/lib/nanite/amqp.rb +54 -0
  14. data/lib/nanite/cluster.rb +236 -0
  15. data/lib/nanite/config.rb +111 -0
  16. data/lib/nanite/console.rb +39 -0
  17. data/lib/nanite/daemonize.rb +13 -0
  18. data/lib/nanite/dispatcher.rb +92 -0
  19. data/lib/nanite/identity.rb +16 -0
  20. data/lib/nanite/job.rb +104 -0
  21. data/lib/nanite/local_state.rb +34 -0
  22. data/lib/nanite/log.rb +66 -0
  23. data/lib/nanite/log/formatter.rb +39 -0
  24. data/lib/nanite/mapper.rb +310 -0
  25. data/lib/nanite/mapper_proxy.rb +67 -0
  26. data/lib/nanite/packets.rb +365 -0
  27. data/lib/nanite/pid_file.rb +52 -0
  28. data/lib/nanite/reaper.rb +38 -0
  29. data/lib/nanite/security/cached_certificate_store_proxy.rb +24 -0
  30. data/lib/nanite/security/certificate.rb +55 -0
  31. data/lib/nanite/security/certificate_cache.rb +66 -0
  32. data/lib/nanite/security/distinguished_name.rb +34 -0
  33. data/lib/nanite/security/encrypted_document.rb +46 -0
  34. data/lib/nanite/security/rsa_key_pair.rb +53 -0
  35. data/lib/nanite/security/secure_serializer.rb +68 -0
  36. data/lib/nanite/security/signature.rb +46 -0
  37. data/lib/nanite/security/static_certificate_store.rb +35 -0
  38. data/lib/nanite/security_provider.rb +47 -0
  39. data/lib/nanite/serializer.rb +52 -0
  40. data/lib/nanite/state.rb +164 -0
  41. data/lib/nanite/streaming.rb +125 -0
  42. data/lib/nanite/util.rb +58 -0
  43. data/spec/actor_registry_spec.rb +60 -0
  44. data/spec/actor_spec.rb +77 -0
  45. data/spec/agent_spec.rb +240 -0
  46. data/spec/cached_certificate_store_proxy_spec.rb +34 -0
  47. data/spec/certificate_cache_spec.rb +49 -0
  48. data/spec/certificate_spec.rb +27 -0
  49. data/spec/cluster_spec.rb +485 -0
  50. data/spec/dispatcher_spec.rb +136 -0
  51. data/spec/distinguished_name_spec.rb +24 -0
  52. data/spec/encrypted_document_spec.rb +21 -0
  53. data/spec/job_spec.rb +251 -0
  54. data/spec/local_state_spec.rb +112 -0
  55. data/spec/packet_spec.rb +220 -0
  56. data/spec/rsa_key_pair_spec.rb +33 -0
  57. data/spec/secure_serializer_spec.rb +41 -0
  58. data/spec/serializer_spec.rb +107 -0
  59. data/spec/signature_spec.rb +30 -0
  60. data/spec/spec_helper.rb +33 -0
  61. data/spec/static_certificate_store_spec.rb +30 -0
  62. data/spec/util_spec.rb +63 -0
  63. metadata +131 -0
@@ -0,0 +1,67 @@
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 if defined?(@@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
+ Nanite::Log.info("SEND #{request.to_s([:tags, :target])}")
41
+ amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(request))
42
+ end
43
+
44
+ def push(type, payload = '', opts = {})
45
+ raise "Mapper proxy not initialized" unless identity && options
46
+ push = Push.new(type, payload, opts)
47
+ push.from = identity
48
+ push.token = Identity.generate
49
+ push.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
50
+ Nanite::Log.info("SEND #{push.to_s([:tags, :target])}")
51
+ amqp.fanout('request', :no_declare => options[:secure]).publish(serializer.dump(push))
52
+ end
53
+
54
+ # Handle intermediary result
55
+ def handle_intermediate_result(res)
56
+ handlers = pending_requests[res.token]
57
+ handlers[:intermediate_handler].call(res) if handlers && handlers[:intermediate_handler]
58
+ end
59
+
60
+ # Handle final result
61
+ def handle_result(res)
62
+ handlers = pending_requests.delete(res.token)
63
+ handlers[:result_handler].call(res) if handlers && handlers[:result_handler]
64
+ end
65
+
66
+ end
67
+ end
@@ -0,0 +1,365 @@
1
+ module Nanite
2
+ # Base class for all Nanite packets,
3
+ # knows how to dump itself to JSON
4
+ class Packet
5
+
6
+ attr_accessor :size
7
+
8
+ def initialize
9
+ raise NotImplementedError.new("#{self.class.name} is an abstract class.")
10
+ end
11
+
12
+ def to_json(*a)
13
+ js = {
14
+ 'json_class' => self.class.name,
15
+ 'data' => instance_variables.inject({}) {|m,ivar| m[ivar.to_s.sub(/@/,'')] = instance_variable_get(ivar); m }
16
+ }.to_json(*a)
17
+ js = js.chop + ",\"size\":#{js.size}}"
18
+ js
19
+ end
20
+
21
+ # Log representation
22
+ def to_s(filter=nil)
23
+ res = "[#{ self.class.to_s.split('::').last.
24
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
25
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
26
+ downcase }]"
27
+ res += " (#{size.to_s.gsub(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1,")} bytes)" if size && !size.to_s.empty?
28
+ res
29
+ end
30
+
31
+ # Log friendly name for given agent id
32
+ def id_to_s(id)
33
+ case id
34
+ when /^mapper-/ then 'mapper'
35
+ when /^nanite-(.*)/ then Regexp.last_match(1)
36
+ else id
37
+ end
38
+ end
39
+
40
+ end
41
+
42
+ # packet that means start of a file transfer
43
+ # operation
44
+ class FileStart < Packet
45
+
46
+ attr_accessor :filename, :token, :dest
47
+
48
+ def initialize(filename, dest, token, size=nil)
49
+ @filename = filename
50
+ @dest = dest
51
+ @token = token
52
+ @size = size
53
+ end
54
+
55
+ def self.json_create(o)
56
+ i = o['data']
57
+ new(i['filename'], i['dest'], i['token'], o['size'])
58
+ end
59
+
60
+ def to_s
61
+ "#{super} <#{token}> #{filename} to #{dest}"
62
+ end
63
+ end
64
+
65
+ # packet that means end of a file transfer
66
+ # operation
67
+ class FileEnd < Packet
68
+
69
+ attr_accessor :token, :meta
70
+
71
+ def initialize(token, meta, size=nil)
72
+ @token = token
73
+ @meta = meta
74
+ @size = size
75
+ end
76
+
77
+ def self.json_create(o)
78
+ i = o['data']
79
+ new(i['token'], i['meta'], o['size'])
80
+ end
81
+
82
+ def to_s
83
+ "#{super} <#{token}> meta #{meta}"
84
+ end
85
+ end
86
+
87
+ # packet that carries data chunks during a file transfer
88
+ class FileChunk < Packet
89
+
90
+ attr_accessor :chunk, :token
91
+
92
+ def initialize(token, size=nil, chunk=nil)
93
+ @chunk = chunk
94
+ @token = token
95
+ @size = size
96
+ end
97
+
98
+ def self.json_create(o)
99
+ i = o['data']
100
+ new(i['token'], o['size'], i['chunk'])
101
+ end
102
+
103
+ def to_s
104
+ "#{super} <#{token}>"
105
+ end
106
+ end
107
+
108
+ # packet that means a work request from mapper
109
+ # to actor node
110
+ #
111
+ # type is a service name
112
+ # payload is arbitrary data that is transferred from mapper to actor
113
+ #
114
+ # Options:
115
+ # from is sender identity
116
+ # token is a generated request id that mapper uses to identify replies
117
+ # reply_to is identity of the node actor replies to, usually a mapper itself
118
+ # selector is the selector used to route the request
119
+ # target is the target nanite for the request
120
+ # persistent signifies if this request should be saved to persistent storage by the AMQP broker
121
+ class Request < Packet
122
+
123
+ attr_accessor :from, :payload, :type, :token, :reply_to, :selector, :target, :persistent, :tags
124
+
125
+ DEFAULT_OPTIONS = {:selector => :least_loaded}
126
+
127
+ def initialize(type, payload, size=nil, opts={})
128
+ opts = DEFAULT_OPTIONS.merge(opts)
129
+ @type = type
130
+ @payload = payload
131
+ @size = size
132
+ @from = opts[:from]
133
+ @token = opts[:token]
134
+ @reply_to = opts[:reply_to]
135
+ @selector = opts[:selector]
136
+ @target = opts[:target]
137
+ @persistent = opts[:persistent]
138
+ @tags = opts[:tags] || []
139
+ end
140
+
141
+ def self.json_create(o)
142
+ i = o['data']
143
+ new(i['type'], i['payload'], o['size'], { :from => i['from'], :token => i['token'],
144
+ :reply_to => i['reply_to'], :selector => i['selector'],
145
+ :target => i['target'], :persistent => i['persistent'],
146
+ :tags => i['tags'] })
147
+ end
148
+
149
+ def to_s(filter=nil)
150
+ log_msg = "#{super} <#{token}> #{type}"
151
+ log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
152
+ log_msg += " to #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
153
+ log_msg += ", reply_to #{id_to_s(reply_to)}" if reply_to && (filter.nil? || filter.include?(:reply_to))
154
+ log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
155
+ log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
156
+ log_msg
157
+ end
158
+
159
+ end
160
+
161
+ # packet that means a work push from mapper
162
+ # to actor node
163
+ #
164
+ # type is a service name
165
+ # payload is arbitrary data that is transferred from mapper to actor
166
+ #
167
+ # Options:
168
+ # from is sender identity
169
+ # token is a generated request id that mapper uses to identify replies
170
+ # selector is the selector used to route the request
171
+ # target is the target nanite for the request
172
+ # persistent signifies if this request should be saved to persistent storage by the AMQP broker
173
+ class Push < Packet
174
+
175
+ attr_accessor :from, :payload, :type, :token, :selector, :target, :persistent, :tags
176
+
177
+ DEFAULT_OPTIONS = {:selector => :least_loaded}
178
+
179
+ def initialize(type, payload, size=nil, opts={})
180
+ opts = DEFAULT_OPTIONS.merge(opts)
181
+ @type = type
182
+ @payload = payload
183
+ @size = size
184
+ @from = opts[:from]
185
+ @token = opts[:token]
186
+ @selector = opts[:selector]
187
+ @target = opts[:target]
188
+ @persistent = opts[:persistent]
189
+ @tags = opts[:tags] || []
190
+ end
191
+
192
+ def self.json_create(o)
193
+ i = o['data']
194
+ new(i['type'], i['payload'], o['size'], { :from => i['from'], :token => i['token'],
195
+ :selector => i['selector'], :target => i['target'],
196
+ :persistent => i['persistent'], :tags => i['tags'] })
197
+ end
198
+
199
+ def to_s(filter=nil)
200
+ log_msg = "#{super} <#{token}> #{type}"
201
+ log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
202
+ log_msg += ", target #{id_to_s(target)}" if target && (filter.nil? || filter.include?(:target))
203
+ log_msg += ", tags #{tags.inspect}" if tags && !tags.empty? && (filter.nil? || filter.include?(:tags))
204
+ log_msg += ", payload #{payload.inspect}" if filter.nil? || filter.include?(:payload)
205
+ log_msg
206
+ end
207
+ end
208
+
209
+ # packet that means a work result notification sent from actor to mapper
210
+ #
211
+ # from is sender identity
212
+ # results is arbitrary data that is transferred from actor, a result of actor's work
213
+ # token is a generated request id that mapper uses to identify replies
214
+ # to is identity of the node result should be delivered to
215
+ class Result < Packet
216
+
217
+ attr_accessor :token, :results, :to, :from
218
+
219
+ def initialize(token, to, results, from, size=nil)
220
+ @token = token
221
+ @to = to
222
+ @from = from
223
+ @results = results
224
+ @size = size
225
+ end
226
+
227
+ def self.json_create(o)
228
+ i = o['data']
229
+ new(i['token'], i['to'], i['results'], i['from'], o['size'])
230
+ end
231
+
232
+ def to_s(filter=nil)
233
+ log_msg = "#{super} <#{token}>"
234
+ log_msg += " from #{id_to_s(from)}" if filter.nil? || filter.include?(:from)
235
+ log_msg += " to #{id_to_s(to)}" if filter.nil? || filter.include?(:to)
236
+ log_msg += " results: #{results.inspect}" if filter.nil? || filter.include?(:results)
237
+ log_msg
238
+ end
239
+ end
240
+
241
+ # packet that means an intermediate status notification sent from actor to mapper. is appended to a list of messages matching messagekey.
242
+ #
243
+ # from is sender identity
244
+ # messagekey is a string that can become part of a redis key, which identifies the name under which the message is stored
245
+ # message is arbitrary data that is transferred from actor, an intermediate result of actor's work
246
+ # token is a generated request id that mapper uses to identify replies
247
+ # to is identity of the node result should be delivered to
248
+ class IntermediateMessage < Packet
249
+
250
+ attr_accessor :token, :messagekey, :message, :to, :from
251
+
252
+ def initialize(token, to, from, messagekey, message, size=nil)
253
+ @token = token
254
+ @to = to
255
+ @from = from
256
+ @messagekey = messagekey
257
+ @message = message
258
+ @size = size
259
+ end
260
+
261
+ def self.json_create(o)
262
+ i = o['data']
263
+ new(i['token'], i['to'], i['from'], i['messagekey'], i['message'], o['size'])
264
+ end
265
+
266
+ def to_s
267
+ "#{super} <#{token}> from #{id_to_s(from)}, key #{messagekey}"
268
+ end
269
+ end
270
+
271
+ # packet that means an availability notification sent from actor to mapper
272
+ #
273
+ # from is sender identity
274
+ # services is a list of services provided by the node
275
+ # status is a load of the node by default, but may be any criteria
276
+ # agent may use to report it's availability, load, etc
277
+ class Register < Packet
278
+
279
+ attr_accessor :identity, :services, :status, :tags
280
+
281
+ def initialize(identity, services, status, tags, size=nil)
282
+ @status = status
283
+ @tags = tags
284
+ @identity = identity
285
+ @services = services
286
+ @size = size
287
+ end
288
+
289
+ def self.json_create(o)
290
+ i = o['data']
291
+ new(i['identity'], i['services'], i['status'], i['tags'], o['size'])
292
+ end
293
+
294
+ def to_s
295
+ log_msg = "#{super} #{id_to_s(identity)}"
296
+ log_msg += ", services: #{services.join(', ')}" if services && !services.empty?
297
+ log_msg += ", tags: #{tags.join(', ')}" if tags && !tags.empty?
298
+ log_msg
299
+ end
300
+ end
301
+
302
+ # packet that means deregister an agent from the mappers
303
+ #
304
+ # from is sender identity
305
+ class UnRegister < Packet
306
+
307
+ attr_accessor :identity
308
+
309
+ def initialize(identity, size=nil)
310
+ @identity = identity
311
+ @size = size
312
+ end
313
+
314
+ def self.json_create(o)
315
+ i = o['data']
316
+ new(i['identity'], o['size'])
317
+ end
318
+
319
+ def to_s
320
+ "#{super} #{id_to_s(identity)}"
321
+ end
322
+ end
323
+
324
+ # heartbeat packet
325
+ #
326
+ # identity is sender's identity
327
+ # status is sender's status (see Register packet documentation)
328
+ class Ping < Packet
329
+
330
+ attr_accessor :identity, :status
331
+
332
+ def initialize(identity, status, size=nil)
333
+ @status = status
334
+ @identity = identity
335
+ @size = size
336
+ end
337
+
338
+ def self.json_create(o)
339
+ i = o['data']
340
+ new(i['identity'], i['status'], o['size'])
341
+ end
342
+
343
+ def to_s
344
+ "#{super} #{id_to_s(identity)} status #{status}"
345
+ end
346
+
347
+ end
348
+
349
+ # packet that is sent by workers to the mapper
350
+ # when worker initially comes online to advertise
351
+ # it's services
352
+ class Advertise < Packet
353
+
354
+ def initialize(size=nil)
355
+ @size = size
356
+ end
357
+
358
+ def self.json_create(o)
359
+ new(o['size'])
360
+ end
361
+
362
+ end
363
+
364
+ end
365
+
@@ -0,0 +1,52 @@
1
+ module Nanite
2
+ class PidFile
3
+ def initialize(identity, options)
4
+ @pid_dir = File.expand_path(options[:pid_dir] || options[:root] || Dir.pwd)
5
+ @pid_file = File.join(@pid_dir, "nanite.#{identity}.pid")
6
+ end
7
+
8
+ def check
9
+ if pid = read_pid
10
+ if process_running? pid
11
+ raise "#{@pid_file} already exists (pid: #{pid})"
12
+ else
13
+ Log.info "removing stale pid file: #{@pid_file}"
14
+ remove
15
+ end
16
+ end
17
+ end
18
+
19
+ def ensure_dir
20
+ FileUtils.mkdir_p @pid_dir
21
+ end
22
+
23
+ def write
24
+ ensure_dir
25
+ open(@pid_file,'w') {|f| f.write(Process.pid) }
26
+ File.chmod(0644, @pid_file)
27
+ end
28
+
29
+ def remove
30
+ File.delete(@pid_file) if exists?
31
+ end
32
+
33
+ def read_pid
34
+ open(@pid_file,'r') {|f| f.read.to_i } if exists?
35
+ end
36
+
37
+ def exists?
38
+ File.exists? @pid_file
39
+ end
40
+
41
+ def to_s
42
+ @pid_file
43
+ end
44
+
45
+ private
46
+ def process_running?(pid)
47
+ Process.getpgid(pid) != -1
48
+ rescue Errno::ESRCH
49
+ false
50
+ end
51
+ end
52
+ end