rightscale-nanite 0.4.1 → 0.4.1.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 +153 -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 +62 -1
@@ -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
|
@@ -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
|
+
|