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.
- data/README.rdoc +70 -20
- data/Rakefile +1 -1
- data/bin/nanite-agent +34 -8
- data/bin/nanite-mapper +18 -8
- data/lib/nanite.rb +71 -0
- data/lib/nanite/actor.rb +60 -0
- data/lib/nanite/actor_registry.rb +24 -0
- data/lib/nanite/admin.rb +138 -0
- data/lib/nanite/agent.rb +250 -0
- data/lib/nanite/amqp.rb +47 -0
- data/lib/nanite/cluster.rb +203 -0
- data/lib/nanite/config.rb +102 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +13 -0
- data/lib/nanite/dispatcher.rb +90 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +104 -0
- data/lib/nanite/local_state.rb +34 -0
- data/lib/nanite/log.rb +64 -0
- data/lib/nanite/log/formatter.rb +39 -0
- data/lib/nanite/mapper.rb +277 -0
- data/lib/nanite/mapper_proxy.rb +56 -0
- data/lib/nanite/packets.rb +231 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +38 -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 +67 -0
- data/lib/nanite/security/signature.rb +40 -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 +164 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +51 -0
- data/spec/actor_registry_spec.rb +62 -0
- data/spec/actor_spec.rb +59 -0
- data/spec/agent_spec.rb +235 -0
- data/spec/cached_certificate_store_proxy_spec.rb +34 -0
- data/spec/certificate_cache_spec.rb +49 -0
- data/spec/certificate_spec.rb +27 -0
- data/spec/cluster_spec.rb +300 -0
- data/spec/dispatcher_spec.rb +136 -0
- data/spec/distinguished_name_spec.rb +24 -0
- data/spec/encrypted_document_spec.rb +21 -0
- data/spec/job_spec.rb +219 -0
- data/spec/local_state_spec.rb +112 -0
- data/spec/packet_spec.rb +218 -0
- data/spec/rsa_key_pair_spec.rb +33 -0
- data/spec/secure_serializer_spec.rb +41 -0
- data/spec/serializer_spec.rb +107 -0
- data/spec/signature_spec.rb +30 -0
- data/spec/spec_helper.rb +23 -0
- data/spec/static_certificate_store_spec.rb +30 -0
- data/spec/util_spec.rb +63 -0
- metadata +63 -2
@@ -0,0 +1,102 @@
|
|
1
|
+
module Nanite
|
2
|
+
|
3
|
+
COMMON_DEFAULT_OPTIONS = {:pass => 'testing', :vhost => '/nanite', :secure => false, :host => '0.0.0.0',
|
4
|
+
:log_level => :info, :format => :marshal, :daemonize => false, :console => false, :root => Dir.pwd}
|
5
|
+
|
6
|
+
module CommonConfig
|
7
|
+
def setup_mapper_options(opts, options)
|
8
|
+
setup_common_options(opts, options, 'mapper')
|
9
|
+
|
10
|
+
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|
|
11
|
+
options[:agent_timeout] = timeout
|
12
|
+
end
|
13
|
+
|
14
|
+
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|
|
15
|
+
options[:offline_redelivery_frequency] = frequency
|
16
|
+
end
|
17
|
+
|
18
|
+
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
|
19
|
+
options[:persistent] = true
|
20
|
+
end
|
21
|
+
|
22
|
+
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
|
23
|
+
options[:offline_failsafe] = true
|
24
|
+
end
|
25
|
+
|
26
|
+
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|
|
27
|
+
redishost, redisport = redis.split(':')
|
28
|
+
redishost = '127.0.0.1' if (redishost.nil? || redishost.empty?)
|
29
|
+
redisport = '6379' if (redishost.nil? || redishost.empty?)
|
30
|
+
redis = "#{redishost}:#{redisport}"
|
31
|
+
options[:redis] = redis
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
def setup_common_options(opts, options, type)
|
37
|
+
opts.version = Nanite::VERSION
|
38
|
+
|
39
|
+
opts.on("-i", "--irb-console", "Start #{type} in irb console mode.") do |console|
|
40
|
+
options[:console] = 'irb'
|
41
|
+
end
|
42
|
+
|
43
|
+
opts.on("-u", "--user USER", "Specify the rabbitmq username.") do |user|
|
44
|
+
options[:user] = user
|
45
|
+
end
|
46
|
+
|
47
|
+
opts.on("-h", "--host HOST", "Specify the rabbitmq hostname.") do |host|
|
48
|
+
options[:host] = host
|
49
|
+
end
|
50
|
+
|
51
|
+
opts.on("-P", "--port PORT", "Specify the rabbitmq PORT, default 5672.") do |port|
|
52
|
+
options[:port] = port
|
53
|
+
end
|
54
|
+
|
55
|
+
opts.on("-p", "--pass PASSWORD", "Specify the rabbitmq password") do |pass|
|
56
|
+
options[:pass] = pass
|
57
|
+
end
|
58
|
+
|
59
|
+
opts.on("-t", "--token IDENITY", "Specify the #{type} identity.") do |ident|
|
60
|
+
options[:identity] = ident
|
61
|
+
end
|
62
|
+
|
63
|
+
opts.on("-v", "--vhost VHOST", "Specify the rabbitmq vhost") do |vhost|
|
64
|
+
options[:vhost] = vhost
|
65
|
+
end
|
66
|
+
|
67
|
+
opts.on("-s", "--secure", "Use Security features of rabbitmq to restrict nanites to themselves") do
|
68
|
+
options[:secure] = true
|
69
|
+
end
|
70
|
+
|
71
|
+
opts.on("-f", "--format FORMAT", "The serialization type to use for transfering data. Can be marshal, json or yaml. Default is marshal") do |fmt|
|
72
|
+
options[:format] = fmt.to_sym
|
73
|
+
end
|
74
|
+
|
75
|
+
opts.on("-d", "--daemonize", "Run #{type} as a daemon") do |d|
|
76
|
+
options[:daemonize] = true
|
77
|
+
end
|
78
|
+
|
79
|
+
opts.on("--pid-dir PATH", "Specify the pid path, only used with daemonize") do |dir|
|
80
|
+
options[:pid_dir] = dir
|
81
|
+
end
|
82
|
+
|
83
|
+
opts.on("-l", "--log-level LEVEL", "Specify the log level (fatal, error, warn, info, debug). Default is info") do |level|
|
84
|
+
options[:log_level] = level
|
85
|
+
end
|
86
|
+
|
87
|
+
opts.on("--log-dir PATH", "Specify the log path") do |dir|
|
88
|
+
options[:log_dir] = dir
|
89
|
+
end
|
90
|
+
|
91
|
+
opts.on("--tag TAG", "Specify a tag. Can issue multiple times.") do |tag|
|
92
|
+
options[:tag] ||= []
|
93
|
+
options[:tag] << tag
|
94
|
+
end
|
95
|
+
|
96
|
+
opts.on("--version", "Show the nanite version number") do |res|
|
97
|
+
puts "Nanite Version #{opts.version}"
|
98
|
+
exit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
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,90 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Dispatcher
|
3
|
+
attr_reader :registry, :serializer, :identity, :amq, :options
|
4
|
+
attr_accessor :evmclass
|
5
|
+
|
6
|
+
def initialize(amq, registry, serializer, identity, options)
|
7
|
+
@amq = amq
|
8
|
+
@registry = registry
|
9
|
+
@serializer = serializer
|
10
|
+
@identity = identity
|
11
|
+
@options = options
|
12
|
+
@evmclass = EM
|
13
|
+
end
|
14
|
+
|
15
|
+
def dispatch(deliverable)
|
16
|
+
prefix, meth = deliverable.type.split('/')[1..-1]
|
17
|
+
meth ||= :index
|
18
|
+
actor = registry.actor_for(prefix)
|
19
|
+
|
20
|
+
operation = lambda do
|
21
|
+
begin
|
22
|
+
intermediate_results_proc = lambda { |*args| self.handle_intermediate_results(actor, meth, deliverable, *args) }
|
23
|
+
args = [ deliverable.payload ]
|
24
|
+
args.push(deliverable) if actor.method(meth).arity == 2
|
25
|
+
actor.send(meth, *args, &intermediate_results_proc)
|
26
|
+
rescue Exception => e
|
27
|
+
handle_exception(actor, meth, deliverable, e)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
callback = lambda do |r|
|
32
|
+
if deliverable.kind_of?(Request)
|
33
|
+
r = Result.new(deliverable.token, deliverable.reply_to, r, identity)
|
34
|
+
amq.queue(deliverable.reply_to, :no_declare => options[:secure]).publish(serializer.dump(r))
|
35
|
+
end
|
36
|
+
r # For unit tests
|
37
|
+
end
|
38
|
+
|
39
|
+
if @options[:single_threaded]
|
40
|
+
@evmclass.next_tick { callback.call(operation.call) }
|
41
|
+
else
|
42
|
+
@evmclass.defer(operation, callback)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def handle_intermediate_results(actor, meth, deliverable, *args)
|
49
|
+
messagekey = case args.size
|
50
|
+
when 1
|
51
|
+
'defaultkey'
|
52
|
+
when 2
|
53
|
+
args.first.to_s
|
54
|
+
else
|
55
|
+
raise ArgumentError, "handle_intermediate_results passed unexpected number of arguments (#{args.size})"
|
56
|
+
end
|
57
|
+
message = args.last
|
58
|
+
@evmclass.defer(lambda {
|
59
|
+
[deliverable.reply_to, IntermediateMessage.new(deliverable.token, deliverable.reply_to, identity, messagekey, message)]
|
60
|
+
}, lambda { |r|
|
61
|
+
amq.queue(r.first, :no_declare => options[:secure]).publish(serializer.dump(r.last))
|
62
|
+
})
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def describe_error(e)
|
68
|
+
"#{e.class.name}: #{e.message}\n #{e.backtrace.join("\n ")}"
|
69
|
+
end
|
70
|
+
|
71
|
+
def handle_exception(actor, meth, deliverable, e)
|
72
|
+
error = describe_error(e)
|
73
|
+
Nanite::Log.error(error)
|
74
|
+
begin
|
75
|
+
if actor.class.exception_callback
|
76
|
+
case actor.class.exception_callback
|
77
|
+
when Symbol, String
|
78
|
+
actor.send(actor.class.exception_callback, meth.to_sym, deliverable, e)
|
79
|
+
when Proc
|
80
|
+
actor.instance_exec(meth.to_sym, deliverable, e, &actor.class.exception_callback)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
rescue Exception => e1
|
84
|
+
error = describe_error(e1)
|
85
|
+
Nanite::Log.error(error)
|
86
|
+
end
|
87
|
+
error
|
88
|
+
end
|
89
|
+
end
|
90
|
+
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
|
+
Nanite::Log.debug("processing message: #{msg.inspect}")
|
18
|
+
|
19
|
+
if job = jobs[msg.token]
|
20
|
+
job.process(msg)
|
21
|
+
|
22
|
+
if job.intermediate_handler && (job.pending_keys.size > 0)
|
23
|
+
|
24
|
+
unless job.pending_keys.size == 1
|
25
|
+
raise "IntermediateMessages are currently dispatched as they arrive, shouldn't have more than one key in pending_keys: #{job.pending_keys.inspect}"
|
26
|
+
end
|
27
|
+
|
28
|
+
key = job.pending_keys.first
|
29
|
+
handler = job.intermediate_handler_for_key(key)
|
30
|
+
if handler
|
31
|
+
case handler.arity
|
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
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module Nanite
|
2
|
+
class LocalState < ::Hash
|
3
|
+
def initialize(hsh={})
|
4
|
+
hsh.each do |k,v|
|
5
|
+
self[k] = v
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def all_services
|
10
|
+
all(:services)
|
11
|
+
end
|
12
|
+
|
13
|
+
def all_tags
|
14
|
+
all(:tags)
|
15
|
+
end
|
16
|
+
|
17
|
+
def nanites_for(service, *tags)
|
18
|
+
tags = tags.dup.flatten
|
19
|
+
nanites = select { |name, state| state[:services].include?(service) }
|
20
|
+
unless tags.empty?
|
21
|
+
nanites.select { |a| !(a[1][:tags] & tags).empty? }
|
22
|
+
else
|
23
|
+
nanites
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def all(key)
|
30
|
+
map { |n,s| s[key] }.flatten.uniq.compact
|
31
|
+
end
|
32
|
+
|
33
|
+
end # LocalState
|
34
|
+
end # Nanite
|
data/lib/nanite/log.rb
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'nanite/config'
|
2
|
+
require 'nanite/log/formatter'
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Nanite
|
6
|
+
class Log
|
7
|
+
|
8
|
+
@logger = nil
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :logger, :level, :file #:nodoc
|
12
|
+
|
13
|
+
# Use Nanite::Logger.init when you want to set up the logger manually.
|
14
|
+
# If this method is called with no arguments, it will log to STDOUT at the :info level.
|
15
|
+
# It also configures the Logger instance it creates to use the custom Nanite::Log::Formatter class.
|
16
|
+
def init(identity, path = false)
|
17
|
+
if path
|
18
|
+
@file = File.join(path, "nanite.#{identity}.log")
|
19
|
+
else
|
20
|
+
@file = STDOUT
|
21
|
+
end
|
22
|
+
@logger = Logger.new(file)
|
23
|
+
@logger.formatter = Nanite::Log::Formatter.new
|
24
|
+
level = @log_level = :info
|
25
|
+
end
|
26
|
+
|
27
|
+
# Sets the level for the Logger by symbol or by command line argument.
|
28
|
+
# Throws an ArgumentError if you feed it a bogus log level (that is not
|
29
|
+
# one of :debug, :info, :warn, :error, :fatal or the corresponding strings)
|
30
|
+
def level=(loglevel)
|
31
|
+
init() unless @logger
|
32
|
+
loglevel = loglevel.intern if loglevel.is_a?(String)
|
33
|
+
@logger.info("Setting log level to #{loglevel.to_s.upcase}")
|
34
|
+
case loglevel
|
35
|
+
when :debug
|
36
|
+
@logger.level = Logger::DEBUG
|
37
|
+
when :info
|
38
|
+
@logger.level = Logger::INFO
|
39
|
+
when :warn
|
40
|
+
@logger.level = Logger::WARN
|
41
|
+
when :error
|
42
|
+
@logger.level = Logger::ERROR
|
43
|
+
when :fatal
|
44
|
+
@logger.level = Logger::FATAL
|
45
|
+
else
|
46
|
+
raise ArgumentError, "Log level must be one of :debug, :info, :warn, :error, or :fatal"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Passes any other method calls on directly to the underlying Logger object created with init. If
|
51
|
+
# this method gets hit before a call to Nanite::Logger.init has been made, it will call
|
52
|
+
# Nanite::Logger.init() with no arguments.
|
53
|
+
def method_missing(method_symbol, *args)
|
54
|
+
init(identity) unless @logger
|
55
|
+
if args.length > 0
|
56
|
+
@logger.send(method_symbol, *args)
|
57
|
+
else
|
58
|
+
@logger.send(method_symbol)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
end # class << self
|
63
|
+
end
|
64
|
+
end
|