nofxx-nanite 0.4.1.2 → 0.4.1.15

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ end
10
10
  require 'rake/clean'
11
11
  require 'lib/nanite'
12
12
 
13
- GEM = "nanite"
13
+ GEM = "nofxx-nanite"
14
14
  AUTHOR = "Ezra Zygmuntowicz"
15
15
  EMAIL = "ezra@engineyard.com"
16
16
  HOMEPAGE = "http://github.com/ezmobius/nanite"
@@ -35,6 +35,7 @@ spec = Gem::Specification.new do |s|
35
35
  s.executables = %w( nanite-agent nanite-mapper nanite-admin )
36
36
 
37
37
  s.add_dependency('amqp', '>= 0.6.0')
38
+ s.add_dependency('json', '>= 1.1.7')
38
39
 
39
40
  s.require_path = 'lib'
40
41
  s.files = %w(LICENSE README.rdoc Rakefile TODO) + Dir.glob("{lib,bin,specs}/**/*")
@@ -70,7 +71,7 @@ task :docs => :rdoc do
70
71
  case RUBY_PLATFORM
71
72
  when /darwin/ ; sh 'open rdoc/index.html'
72
73
  when /mswin|mingw/ ; sh 'start rdoc\index.html'
73
- else
74
+ else
74
75
  sh 'firefox rdoc/index.html'
75
76
  end
76
77
  end
data/bin/nanite-agent CHANGED
@@ -42,6 +42,9 @@ opts = OptionParser.new do |opts|
42
42
  options[:threadpool_size] = tps
43
43
  end
44
44
 
45
+ opts.on("--prefetch COUNT", Integer, "The number of messages stuffed into the queue at anytime. Set this to a value of 1 or so for longer running jobs (1 or more seconds), so the agent does not get overwhelmed. Default is unlimited.") do |pref|
46
+ options[:prefetch] = pref
47
+ end
45
48
  end
46
49
 
47
50
  opts.parse!
data/lib/nanite.rb CHANGED
@@ -5,6 +5,7 @@ require 'json'
5
5
  require 'logger'
6
6
  require 'yaml'
7
7
  require 'openssl'
8
+ require 'fileutils'
8
9
 
9
10
  $:.unshift File.dirname(__FILE__)
10
11
  require 'nanite/amqp'
@@ -20,7 +21,7 @@ require 'nanite/mapper'
20
21
  require 'nanite/actor'
21
22
  require 'nanite/actor_registry'
22
23
  require 'nanite/streaming'
23
- require 'nanite/dispatcher'
24
+ require 'nanite/nanite_dispatcher'
24
25
  require 'nanite/agent'
25
26
  require 'nanite/cluster'
26
27
  require 'nanite/reaper'
@@ -39,7 +40,7 @@ require 'nanite/security/static_certificate_store'
39
40
  require 'nanite/serializer'
40
41
 
41
42
  module Nanite
42
- VERSION = '0.4.1.2' unless defined?(Nanite::VERSION)
43
+ VERSION = '0.4.1.15' unless defined?(Nanite::VERSION)
43
44
 
44
45
  class MapperNotRunning < StandardError; end
45
46
 
@@ -54,6 +55,11 @@ module Nanite
54
55
  @mapper = Nanite::Mapper.start(options)
55
56
  end
56
57
 
58
+ def start_mapper_proxy(options = {})
59
+ identity = options[:identity] || Nanite::Identity.generate
60
+ @mapper = Nanite::MapperProxy.new(identity, options)
61
+ end
62
+
57
63
  def request(*args, &blk)
58
64
  ensure_mapper
59
65
  @mapper.request(*args, &blk)
data/lib/nanite/agent.rb CHANGED
@@ -55,6 +55,9 @@ module Nanite
55
55
  # services : list of services provided by this agent, by default
56
56
  # all methods exposed by actors are listed
57
57
  #
58
+ # prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6). Use value of 1 for long
59
+ # running jobs (greater than a second) to avoid slamming/stalling your agent.
60
+ #
58
61
  # single_threaded: Run all operations in one thread
59
62
  #
60
63
  # threadpool_size: Number of threads to run operations in
@@ -92,18 +95,14 @@ module Nanite
92
95
  end
93
96
 
94
97
  def run
95
- log_path = false
96
- if @options[:daemonize]
97
- log_path = (@options[:log_dir] || @options[:root] || Dir.pwd)
98
- end
99
- Log.init(@identity, log_path)
98
+ Log.init(@identity, @options[:log_path])
100
99
  Log.level = @options[:log_level] if @options[:log_level]
101
100
  @serializer = Serializer.new(@options[:format])
102
101
  @status_proc = lambda { parse_uptime(`uptime 2> /dev/null`) rescue 'no status' }
103
102
  pid_file = PidFile.new(@identity, @options)
104
103
  pid_file.check
105
104
  if @options[:daemonize]
106
- daemonize
105
+ daemonize(@identity, @options)
107
106
  pid_file.write
108
107
  at_exit { pid_file.remove }
109
108
  end
@@ -149,6 +148,11 @@ module Nanite
149
148
  opts.delete(:identity) unless opts[:identity]
150
149
  @options.update(custom_config.merge(opts))
151
150
  @options[:file_root] ||= File.join(@options[:root], 'files')
151
+ @options[:log_path] = false
152
+ if @options[:daemonize]
153
+ @options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
154
+ end
155
+
152
156
  return @identity = "nanite-#{@options[:identity]}" if @options[:identity]
153
157
  token = Identity.generate
154
158
  @identity = "nanite-#{token}"
@@ -160,6 +164,7 @@ module Nanite
160
164
  def load_actors
161
165
  return unless options[:root]
162
166
  actors_dir = @options[:actors_dir] || "#{@options[:root]}/actors"
167
+ Nanite::Log.warn("Actors dir #{actors_dir} does not exist or is not reachable") unless File.directory?(actors_dir)
163
168
  actors = @options[:actors]
164
169
  Dir["#{actors_dir}/*.rb"].each do |actor|
165
170
  next if actors && !actors.include?(File.basename(actor, ".rb"))
@@ -167,14 +172,17 @@ module Nanite
167
172
  require actor
168
173
  end
169
174
  init_path = @options[:initrb] || File.join(options[:root], 'init.rb')
170
- instance_eval(File.read(init_path), init_path) if File.exist?(init_path)
175
+ if File.exist?(init_path)
176
+ instance_eval(File.read(init_path), init_path)
177
+ else
178
+ Nanite::Log.warn("init.rb #{init_path} does not exist or is not reachable") unless File.exists?(init_path)
179
+ end
171
180
  end
172
181
 
173
182
  def receive(packet)
174
183
  Nanite::Log.debug("RECV #{packet.to_s}")
175
184
  case packet
176
185
  when Advertise
177
- Nanite::Log.info("RECV #{packet.to_s}") unless Nanite::Log.level == :debug
178
186
  advertise_services
179
187
  when Request, Push
180
188
  if @security && !@security.authorize(packet)
@@ -184,14 +192,11 @@ module Nanite
184
192
  amq.queue(packet.reply_to, :no_declare => options[:secure]).publish(serializer.dump(r))
185
193
  end
186
194
  else
187
- Nanite::Log.info("RECV #{packet.to_s([:from, :tags])}") unless Nanite::Log.level == :debug
188
195
  dispatcher.dispatch(packet)
189
196
  end
190
197
  when Result
191
- Nanite::Log.info("RECV #{packet.to_s([])}") unless Nanite::Log.level == :debug
192
198
  @mapper_proxy.handle_result(packet)
193
199
  when IntermediateMessage
194
- Nanite::Log.info("RECV #{packet.to_s([])}") unless Nanite::Log.level == :debug
195
200
  @mapper_proxy.handle_intermediate_result(packet)
196
201
  end
197
202
  end
@@ -202,6 +207,9 @@ module Nanite
202
207
  end
203
208
 
204
209
  def setup_queue
210
+ if amq.respond_to?(:prefetch) && @options.has_key?(:prefetch)
211
+ amq.prefetch(@options[:prefetch])
212
+ end
205
213
  amq.queue(identity, :durable => true).subscribe(:ack => true) do |info, msg|
206
214
  begin
207
215
  info.ack
data/lib/nanite/amqp.rb CHANGED
@@ -1,5 +1,18 @@
1
1
  class MQ
2
2
  class Queue
3
+
4
+ # Monkey patch to add :no_declare => true for new queue objects. See the
5
+ # explanation for MQ::Exchange#initialize below.
6
+ def initialize mq, name, opts = {}
7
+ @mq = mq
8
+ @opts = opts
9
+ @bindings ||= {}
10
+ @mq.queues[@name = name] ||= self
11
+ @mq.callback{
12
+ @mq.send Protocol::Queue::Declare.new({ :queue => name,
13
+ :nowait => true }.merge(opts))
14
+ } unless opts[:no_declare]
15
+ end
3
16
  # Asks the broker to redeliver all unacknowledged messages on a
4
17
  # specifieid channel. Zero or more messages may be redelivered.
5
18
  #
@@ -46,7 +59,11 @@ module Nanite
46
59
  :host => options[:host],
47
60
  :port => (options[:port] || ::AMQP::PORT).to_i,
48
61
  :insist => options[:insist] || false,
49
- :retry => options[:retry] || 5
62
+ :retry => options[:retry] || 5,
63
+ :connection_status => options[:connection_callback] || proc {|event|
64
+ Nanite::Log.debug("Connected to MQ") if event == :connected
65
+ Nanite::Log.debug("Disconnected from MQ") if event == :disconnected
66
+ }
50
67
  })
51
68
  MQ.new(connection)
52
69
  end
@@ -29,15 +29,16 @@ module Nanite
29
29
  case reg
30
30
  when Register
31
31
  if @security.authorize_registration(reg)
32
- Nanite::Log.info("RECV #{reg.to_s}")
33
- nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags }
34
- reaper.timeout(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
32
+ Nanite::Log.debug("RECV #{reg.to_s}")
33
+ nanites[reg.identity] = { :services => reg.services, :status => reg.status, :tags => reg.tags, :timestamp => Time.now.utc.to_i }
34
+ reaper.register(reg.identity, agent_timeout + 1) { nanite_timed_out(reg.identity) }
35
35
  callbacks[:register].call(reg.identity, mapper) if callbacks[:register]
36
36
  else
37
37
  Nanite::Log.warn("RECV NOT AUTHORIZED #{reg.to_s}")
38
38
  end
39
39
  when UnRegister
40
40
  Nanite::Log.info("RECV #{reg.to_s}")
41
+ reaper.unregister(reg.identity)
41
42
  nanites.delete(reg.identity)
42
43
  callbacks[:unregister].call(reg.identity, mapper) if callbacks[:unregister]
43
44
  else
@@ -46,8 +47,13 @@ module Nanite
46
47
  end
47
48
 
48
49
  def nanite_timed_out(token)
49
- nanite = nanites.delete(token)
50
- callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
50
+ nanite = nanites[token]
51
+ if nanite && timed_out?(nanite)
52
+ Nanite::Log.info("Nanite #{token} timed out")
53
+ nanite = nanites.delete(token)
54
+ callbacks[:timeout].call(token, mapper) if callbacks[:timeout]
55
+ true
56
+ end
51
57
  end
52
58
 
53
59
  def route(request, targets)
@@ -60,8 +66,8 @@ module Nanite
60
66
  begin
61
67
  old_target = request.target
62
68
  request.target = target unless target == 'mapper-offline'
63
- Nanite::Log.info("SEND #{request.to_s([:from, :tags, :target])}")
64
- amq.queue(target).publish(serializer.dump(request), :persistent => request.persistent)
69
+ Nanite::Log.debug("SEND #{request.to_s([:from, :tags, :target])}")
70
+ amq.queue(target).publish(serializer.dump(request, enforce_format?(target)), :persistent => request.persistent)
65
71
  ensure
66
72
  request.target = old_target
67
73
  end
@@ -69,16 +75,20 @@ module Nanite
69
75
 
70
76
  protected
71
77
 
78
+ def enforce_format?(target)
79
+ target == 'mapper-offline' ? :insecure : nil
80
+ end
81
+
72
82
  # updates nanite information (last ping timestamps, status)
73
83
  # when heartbeat message is received
74
84
  def handle_ping(ping)
75
85
  begin
76
86
  if nanite = nanites[ping.identity]
77
- nanite[:status] = ping.status
78
- reaper.reset_with_autoregister_hack(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
87
+ nanites.update_status(ping.identity, ping.status)
88
+ reaper.update(ping.identity, agent_timeout + 1) { nanite_timed_out(ping.identity) }
79
89
  else
80
- packet = Advertise.new
81
- Nanite::Log.info("SEND #{packet.to_s} to #{ping.identity}")
90
+ packet = Advertise.new(nil, ping.identity)
91
+ Nanite::Log.debug("SEND #{packet.to_s} to #{ping.identity}")
82
92
  amq.queue(ping.identity).publish(serializer.dump(packet))
83
93
  end
84
94
  end
@@ -87,7 +97,6 @@ module Nanite
87
97
  # forward request coming from agent
88
98
  def handle_request(request)
89
99
  if @security.authorize_request(request)
90
- Nanite::Log.info("RECV #{request.to_s([:from, :target, :tags])}") unless Nanite::Log.level == :debug
91
100
  Nanite::Log.debug("RECV #{request.to_s}")
92
101
  case request
93
102
  when Push
@@ -115,7 +124,7 @@ module Nanite
115
124
 
116
125
  # forward response back to agent that originally made the request
117
126
  def forward_response(res, persistent)
118
- Nanite::Log.info("SEND #{res.to_s([:to])}")
127
+ Nanite::Log.debug("SEND #{res.to_s([:to])}")
119
128
  amq.queue(res.to).publish(serializer.dump(res), :persistent => persistent)
120
129
  end
121
130
 
@@ -152,10 +161,21 @@ module Nanite
152
161
  @last[service] += 1
153
162
  [candidate]
154
163
  end
164
+
165
+ def timed_out?(nanite)
166
+ nanite[:timestamp].to_i < (Time.now.utc - agent_timeout).to_i
167
+ end
155
168
 
156
169
  # returns all nanites that provide the given service
157
170
  def nanites_providing(service, *tags)
158
- nanites.nanites_for(service, *tags)
171
+ nanites.nanites_for(service, *tags).delete_if do |nanite|
172
+ nanite_id, nanite_attributes = nanite
173
+ if timed_out?(nanite_attributes)
174
+ reaper.unregister(nanite_id)
175
+ nanites.delete(nanite_id)
176
+ Nanite::Log.debug("Nanite #{nanite_id} timed out - ignoring in target selection and deleting from state - last seen at #{nanite_attributes[:timestamp]}")
177
+ end
178
+ end
159
179
  end
160
180
 
161
181
  def setup_queues
data/lib/nanite/config.rb CHANGED
@@ -31,7 +31,7 @@ module Nanite
31
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
32
  options[:offline_failsafe] = true
33
33
  end
34
-
34
+
35
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
36
  redishost, redisport = redis.split(':')
37
37
  redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
@@ -1,13 +1,13 @@
1
1
  module Nanite
2
2
  module DaemonizeHelper
3
- def daemonize
3
+ def daemonize(identity, options = {})
4
4
  exit if fork
5
5
  Process.setsid
6
6
  exit if fork
7
- File.umask 0000
8
7
  STDIN.reopen "/dev/null"
9
- STDOUT.reopen "/dev/null", "a"
10
- STDERR.reopen STDOUT
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
11
  end
12
12
  end
13
13
  end
@@ -24,6 +24,10 @@ module Nanite
24
24
  end.to_a
25
25
  end
26
26
 
27
+ def update_status(name, status)
28
+ self[name].update(:status => status, :timestamp => Time.now.utc.to_i)
29
+ end
30
+
27
31
  private
28
32
 
29
33
  def all(key)
data/lib/nanite/log.rb CHANGED
@@ -35,7 +35,7 @@ module Nanite
35
35
  # Throws an ArgumentError if you feed it a bogus log level (that is not
36
36
  # one of :debug, :info, :warn, :error, :fatal or the corresponding strings or a valid Logger level)
37
37
  def level=(loglevel)
38
- init() unless @logger
38
+ init unless @logger
39
39
  lvl = case loglevel
40
40
  when String then loglevel.intern
41
41
  when Integer then LEVELS.invert[loglevel]
data/lib/nanite/mapper.rb CHANGED
@@ -117,21 +117,18 @@ module Nanite
117
117
  @options.update(custom_config.merge(options))
118
118
  @identity = "mapper-#{@options[:identity]}"
119
119
  @options[:file_root] ||= File.join(@options[:root], 'files')
120
+ @options[:log_path] = false
121
+ if @options[:daemonize]
122
+ @options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
123
+ end
120
124
  @options.freeze
125
+ @offline_queue = 'mapper-offline'
121
126
  end
122
127
 
123
128
  def run
124
129
  setup_logging
125
130
  @serializer = Serializer.new(@options[:format])
126
- pid_file = PidFile.new(@identity, @options)
127
- pid_file.check
128
- if @options[:daemonize]
129
- daemonize
130
- pid_file.write
131
- at_exit { pid_file.remove }
132
- else
133
- trap("INT") {exit}
134
- end
131
+ setup_process
135
132
  @amq = start_amqp(@options)
136
133
  @job_warden = JobWarden.new(@serializer)
137
134
  setup_cluster
@@ -192,9 +189,9 @@ module Nanite
192
189
  job = job_warden.new_job(request, targets, intm_handler, blk)
193
190
  cluster.route(request, job.targets)
194
191
  job
195
- elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
192
+ elsif offline_failsafe?(opts)
196
193
  job_warden.new_job(request, [], intm_handler, blk)
197
- cluster.publish(request, 'mapper-offline')
194
+ cluster.publish(request, @offline_queue)
198
195
  :offline
199
196
  else
200
197
  false
@@ -231,14 +228,22 @@ module Nanite
231
228
  if !targets.empty?
232
229
  cluster.route(push, targets)
233
230
  true
234
- elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
235
- cluster.publish(push, 'mapper-offline')
231
+ elsif offline_failsafe?(opts)
232
+ cluster.publish(push, @offline_queue)
236
233
  :offline
237
234
  else
238
235
  false
239
236
  end
240
237
  end
241
238
 
239
+ # <<<<<<< HEAD
240
+
241
+ def offline_failsafe?(opts)
242
+ opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
243
+ end
244
+
245
+ # =======
246
+
242
247
  private
243
248
 
244
249
  def build_deliverable(deliverable_type, type, payload, opts)
@@ -259,11 +264,12 @@ module Nanite
259
264
  end
260
265
 
261
266
  def setup_offline_queue
262
- offline_queue = amq.queue('mapper-offline', :durable => true)
267
+ offline_queue = amq.queue(@offline_queue, :durable => true)
263
268
  offline_queue.subscribe(:ack => true) do |info, deliverable|
264
- deliverable = serializer.load(deliverable)
269
+ deliverable = serializer.load(deliverable, :insecure)
265
270
  targets = cluster.targets_for(deliverable)
266
271
  unless targets.empty?
272
+ Nanite::Log.debug("Recovering message from offline queue: #{deliverable.to_s([:from, :tags, :target])}")
267
273
  info.ack
268
274
  if deliverable.kind_of?(Request)
269
275
  if job = job_warden.jobs[deliverable.token]
@@ -285,7 +291,6 @@ module Nanite
285
291
  begin
286
292
  msg = serializer.load(msg)
287
293
  Nanite::Log.debug("RECV #{msg.to_s}")
288
- Nanite::Log.info("RECV #{msg.to_s([:from])}") unless Nanite::Log.level == :debug
289
294
  job_warden.process(msg)
290
295
  rescue Exception => e
291
296
  Nanite::Log.error("RECV [result] #{e.message}")
@@ -294,17 +299,25 @@ module Nanite
294
299
  end
295
300
 
296
301
  def setup_logging
297
- log_path = false
298
- if @options[:daemonize]
299
- log_path = (@options[:log_dir] || @options[:root] || Dir.pwd)
300
- end
301
- Nanite::Log.init(@identity, log_path)
302
+ Nanite::Log.init(@identity, @options[:log_path])
302
303
  Nanite::Log.level = @options[:log_level] if @options[:log_level]
303
304
  end
304
305
 
305
306
  def setup_cluster
306
307
  @cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
307
308
  end
309
+
310
+ def setup_process
311
+ pid_file = PidFile.new(@identity, @options)
312
+ pid_file.check
313
+ if @options[:daemonize]
314
+ daemonize(@identity, @options)
315
+ pid_file.write
316
+ at_exit { pid_file.remove }
317
+ else
318
+ trap("INT") {exit}
319
+ end
320
+ end
308
321
  end
309
322
  end
310
323