nanite 0.4.1.2
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +201 -0
- data/README.rdoc +430 -0
- data/Rakefile +76 -0
- data/TODO +24 -0
- data/bin/nanite-admin +65 -0
- data/bin/nanite-agent +79 -0
- data/bin/nanite-mapper +50 -0
- data/lib/nanite.rb +74 -0
- data/lib/nanite/actor.rb +71 -0
- data/lib/nanite/actor_registry.rb +26 -0
- data/lib/nanite/admin.rb +138 -0
- data/lib/nanite/agent.rb +264 -0
- data/lib/nanite/amqp.rb +58 -0
- data/lib/nanite/cluster.rb +250 -0
- data/lib/nanite/config.rb +111 -0
- data/lib/nanite/console.rb +39 -0
- data/lib/nanite/daemonize.rb +13 -0
- data/lib/nanite/identity.rb +16 -0
- data/lib/nanite/job.rb +104 -0
- data/lib/nanite/local_state.rb +38 -0
- data/lib/nanite/log.rb +66 -0
- data/lib/nanite/log/formatter.rb +39 -0
- data/lib/nanite/mapper.rb +309 -0
- data/lib/nanite/mapper_proxy.rb +67 -0
- data/lib/nanite/nanite_dispatcher.rb +92 -0
- data/lib/nanite/packets.rb +365 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +39 -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 +68 -0
- data/lib/nanite/security/signature.rb +46 -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 +168 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +58 -0
- metadata +109 -0
@@ -0,0 +1,38 @@
|
|
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, b| !(b[:tags] & tags).empty? }
|
22
|
+
else
|
23
|
+
nanites
|
24
|
+
end.to_a
|
25
|
+
end
|
26
|
+
|
27
|
+
def update_status(name, status)
|
28
|
+
self[name].update(:status => status, :timestamp => Time.now.utc.to_i)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def all(key)
|
34
|
+
map { |n,s| s[key] }.flatten.uniq.compact
|
35
|
+
end
|
36
|
+
|
37
|
+
end # LocalState
|
38
|
+
end # Nanite
|
data/lib/nanite/log.rb
ADDED
@@ -0,0 +1,66 @@
|
|
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
|
+
# Map log levels symbols to values
|
11
|
+
LEVELS = { :debug => Logger::DEBUG,
|
12
|
+
:info => Logger::INFO,
|
13
|
+
:warn => Logger::WARN,
|
14
|
+
:error => Logger::ERROR,
|
15
|
+
:fatal => Logger::FATAL }
|
16
|
+
|
17
|
+
class << self
|
18
|
+
attr_accessor :logger, :level, :file #:nodoc
|
19
|
+
|
20
|
+
# Use Nanite::Logger.init when you want to set up the logger manually.
|
21
|
+
# If this method is called with no arguments, it will log to STDOUT at the :info level.
|
22
|
+
# It also configures the Logger instance it creates to use the custom Nanite::Log::Formatter class.
|
23
|
+
def init(identity = nil, path = false)
|
24
|
+
if path
|
25
|
+
@file = File.join(path, "nanite.#{identity}.log")
|
26
|
+
else
|
27
|
+
@file = STDOUT
|
28
|
+
end
|
29
|
+
@logger = Logger.new(file)
|
30
|
+
@logger.formatter = Nanite::Log::Formatter.new
|
31
|
+
Log.level = :info
|
32
|
+
end
|
33
|
+
|
34
|
+
# Sets the level for the Logger by symbol or by command line argument.
|
35
|
+
# Throws an ArgumentError if you feed it a bogus log level (that is not
|
36
|
+
# one of :debug, :info, :warn, :error, :fatal or the corresponding strings or a valid Logger level)
|
37
|
+
def level=(loglevel)
|
38
|
+
init unless @logger
|
39
|
+
lvl = case loglevel
|
40
|
+
when String then loglevel.intern
|
41
|
+
when Integer then LEVELS.invert[loglevel]
|
42
|
+
else loglevel
|
43
|
+
end
|
44
|
+
unless LEVELS.include?(lvl)
|
45
|
+
raise(ArgumentError, 'Log level must be one of :debug, :info, :warn, :error, or :fatal')
|
46
|
+
end
|
47
|
+
@logger.info("[setup] setting log level to #{lvl.to_s.upcase}")
|
48
|
+
@level = lvl
|
49
|
+
@logger.level = LEVELS[lvl]
|
50
|
+
end
|
51
|
+
|
52
|
+
# Passes any other method calls on directly to the underlying Logger object created with init. If
|
53
|
+
# this method gets hit before a call to Nanite::Logger.init has been made, it will call
|
54
|
+
# Nanite::Logger.init() with no arguments.
|
55
|
+
def method_missing(method_symbol, *args)
|
56
|
+
init unless @logger
|
57
|
+
if args.length > 0
|
58
|
+
@logger.send(method_symbol, *args)
|
59
|
+
else
|
60
|
+
@logger.send(method_symbol)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end # class << self
|
65
|
+
end
|
66
|
+
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,309 @@
|
|
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({
|
27
|
+
:user => 'mapper',
|
28
|
+
:identity => Identity.generate,
|
29
|
+
:agent_timeout => 15,
|
30
|
+
:offline_redelivery_frequency => 10,
|
31
|
+
:persistent => false,
|
32
|
+
:offline_failsafe => false,
|
33
|
+
:callbacks => {}
|
34
|
+
}) unless defined?(DEFAULT_OPTIONS)
|
35
|
+
|
36
|
+
# Initializes a new mapper and establishes
|
37
|
+
# AMQP connection. This must be used inside EM.run block or if EventMachine reactor
|
38
|
+
# is already started, for instance, by a Thin server that your Merb/Rails
|
39
|
+
# application runs on.
|
40
|
+
#
|
41
|
+
# Mapper options:
|
42
|
+
#
|
43
|
+
# identity : identity of this mapper, may be any string
|
44
|
+
#
|
45
|
+
# format : format to use for packets serialization. Can be :marshal, :json or :yaml or :secure.
|
46
|
+
# Defaults to Ruby's Marshall format. For interoperability with
|
47
|
+
# AMQP clients implemented in other languages, use JSON.
|
48
|
+
#
|
49
|
+
# Note that Nanite uses JSON gem,
|
50
|
+
# and ActiveSupport's JSON encoder may cause clashes
|
51
|
+
# if ActiveSupport is loaded after JSON gem.
|
52
|
+
#
|
53
|
+
# Also using the secure format requires prior initialization of the serializer, see
|
54
|
+
# SecureSerializer.init
|
55
|
+
#
|
56
|
+
# log_level : the verbosity of logging, can be debug, info, warn, error or fatal.
|
57
|
+
#
|
58
|
+
# agent_timeout : how long to wait before an agent is considered to be offline
|
59
|
+
# and thus removed from the list of available agents.
|
60
|
+
#
|
61
|
+
# log_dir : log file path, defaults to the current working directory.
|
62
|
+
#
|
63
|
+
# console : true tells mapper to start interactive console
|
64
|
+
#
|
65
|
+
# daemonize : true tells mapper to daemonize
|
66
|
+
#
|
67
|
+
# pid_dir : path to the directory where the agent stores its pid file (only if daemonized)
|
68
|
+
# defaults to the root or the current working directory.
|
69
|
+
#
|
70
|
+
# offline_redelivery_frequency : The frequency in seconds that messages stored in the offline queue will be retrieved
|
71
|
+
# for attempted redelivery to the nanites. Default is 10 seconds.
|
72
|
+
#
|
73
|
+
# persistent : true instructs the AMQP broker to save messages to persistent storage so that they aren't lost when the
|
74
|
+
# broker is restarted. Default is false. Can be overriden on a per-message basis using the request and push methods.
|
75
|
+
#
|
76
|
+
# secure : use Security features of rabbitmq to restrict nanites to themselves
|
77
|
+
#
|
78
|
+
# prefetch : Sets prefetch (only supported in RabbitMQ >= 1.6)
|
79
|
+
# callbacks : A set of callbacks to have code executed on specific events, supported events are :register,
|
80
|
+
# :unregister and :timeout. Parameter must be a hash with the corresponding events as keys and
|
81
|
+
# a block as value. The block will get the corresponding nanite's identity and a copy of the
|
82
|
+
# mapper
|
83
|
+
#
|
84
|
+
# Connection options:
|
85
|
+
#
|
86
|
+
# vhost : AMQP broker vhost that should be used
|
87
|
+
#
|
88
|
+
# user : AMQP broker user
|
89
|
+
#
|
90
|
+
# pass : AMQP broker password
|
91
|
+
#
|
92
|
+
# host : host AMQP broker (or node of interest) runs on,
|
93
|
+
# defaults to 0.0.0.0
|
94
|
+
#
|
95
|
+
# port : port AMQP broker (or node of interest) runs on,
|
96
|
+
# this defaults to 5672, port used by some widely
|
97
|
+
# used AMQP brokers (RabbitMQ and ZeroMQ)
|
98
|
+
#
|
99
|
+
# @api :public:
|
100
|
+
def self.start(options = {})
|
101
|
+
mapper = new(options)
|
102
|
+
mapper.run
|
103
|
+
mapper
|
104
|
+
end
|
105
|
+
|
106
|
+
def initialize(options)
|
107
|
+
@options = DEFAULT_OPTIONS.clone.merge(options)
|
108
|
+
root = options[:root] || @options[:root]
|
109
|
+
custom_config = if root
|
110
|
+
file = File.expand_path(File.join(root, 'config.yml'))
|
111
|
+
File.exists?(file) ? (YAML.load(IO.read(file)) || {}) : {}
|
112
|
+
else
|
113
|
+
{}
|
114
|
+
end
|
115
|
+
options.delete(:identity) unless options[:identity]
|
116
|
+
@options.update(custom_config.merge(options))
|
117
|
+
@identity = "mapper-#{@options[:identity]}"
|
118
|
+
@options[:file_root] ||= File.join(@options[:root], 'files')
|
119
|
+
@options[:log_path] = false
|
120
|
+
if @options[:daemonize]
|
121
|
+
@options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
|
122
|
+
end
|
123
|
+
@options.freeze
|
124
|
+
end
|
125
|
+
|
126
|
+
def run
|
127
|
+
setup_logging
|
128
|
+
@serializer = Serializer.new(@options[:format])
|
129
|
+
pid_file = PidFile.new(@identity, @options)
|
130
|
+
pid_file.check
|
131
|
+
if @options[:daemonize]
|
132
|
+
daemonize(@identity, @options)
|
133
|
+
pid_file.write
|
134
|
+
at_exit { pid_file.remove }
|
135
|
+
else
|
136
|
+
trap("INT") {exit}
|
137
|
+
end
|
138
|
+
@amq = start_amqp(@options)
|
139
|
+
@job_warden = JobWarden.new(@serializer)
|
140
|
+
setup_cluster
|
141
|
+
Nanite::Log.info('[setup] starting mapper')
|
142
|
+
setup_queues
|
143
|
+
start_console if @options[:console] && !@options[:daemonize]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Make a nanite request which expects a response.
|
147
|
+
#
|
148
|
+
# ==== Parameters
|
149
|
+
# type<String>:: The dispatch route for the request
|
150
|
+
# payload<Object>:: Payload to send. This will get marshalled en route
|
151
|
+
#
|
152
|
+
# ==== Options
|
153
|
+
# :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
|
154
|
+
# :least_loaded:: Pick the nanite which has the lowest load.
|
155
|
+
# :all:: Send the request to all nanites which respond to the service.
|
156
|
+
# :random:: Randomly pick a nanite.
|
157
|
+
# :rr: Select a nanite according to round robin ordering.
|
158
|
+
# :target<String>:: Select a specific nanite via identity, rather than using
|
159
|
+
# a selector.
|
160
|
+
# :offline_failsafe<Boolean>:: Store messages in an offline queue when all
|
161
|
+
# the nanites are offline. Messages will be redelivered when nanites come online.
|
162
|
+
# Default is false unless the mapper was started with the --offline-failsafe flag.
|
163
|
+
# :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
|
164
|
+
# storage so that it isnt lost when the broker is restarted.
|
165
|
+
# Default is false unless the mapper was started with the --persistent flag.
|
166
|
+
# :intermediate_handler:: Takes a lambda to call when an IntermediateMessage
|
167
|
+
# event arrives from a nanite. If passed a Hash, hash keys should correspond to
|
168
|
+
# the IntermediateMessage keys provided by the nanite, and each should have a value
|
169
|
+
# that is a lambda/proc taking the parameters specified here. Can supply a key '*'
|
170
|
+
# as a catch-all for unmatched keys.
|
171
|
+
#
|
172
|
+
# ==== Block Parameters for intermediate_handler
|
173
|
+
# key<String>:: array of unique keys for which intermediate state has been received
|
174
|
+
# since the last call to this block.
|
175
|
+
# nanite<String>:: nanite which sent the message.
|
176
|
+
# state:: most recently delivered intermediate state for the key provided.
|
177
|
+
# job:: (optional) -- if provided, this parameter gets the whole job object, if there's
|
178
|
+
# a reason to do more complex work with the job.
|
179
|
+
#
|
180
|
+
# ==== Block Parameters
|
181
|
+
# :results<Object>:: The returned value from the nanite actor.
|
182
|
+
#
|
183
|
+
# @api :public:
|
184
|
+
def request(type, payload = '', opts = {}, &blk)
|
185
|
+
request = build_deliverable(Request, type, payload, opts)
|
186
|
+
send_request(request, opts, &blk)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Send request with pre-built request instance
|
190
|
+
def send_request(request, opts = {}, &blk)
|
191
|
+
request.reply_to = identity
|
192
|
+
intm_handler = opts.delete(:intermediate_handler)
|
193
|
+
targets = cluster.targets_for(request)
|
194
|
+
if !targets.empty?
|
195
|
+
job = job_warden.new_job(request, targets, intm_handler, blk)
|
196
|
+
cluster.route(request, job.targets)
|
197
|
+
job
|
198
|
+
elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
|
199
|
+
job_warden.new_job(request, [], intm_handler, blk)
|
200
|
+
cluster.publish(request, 'mapper-offline')
|
201
|
+
:offline
|
202
|
+
else
|
203
|
+
false
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
# Make a nanite request which does not expect a response.
|
208
|
+
#
|
209
|
+
# ==== Parameters
|
210
|
+
# type<String>:: The dispatch route for the request
|
211
|
+
# payload<Object>:: Payload to send. This will get marshalled en route
|
212
|
+
#
|
213
|
+
# ==== Options
|
214
|
+
# :selector<Symbol>:: Method for selecting an actor. Default is :least_loaded.
|
215
|
+
# :least_loaded:: Pick the nanite which has the lowest load.
|
216
|
+
# :all:: Send the request to all nanites which respond to the service.
|
217
|
+
# :random:: Randomly pick a nanite.
|
218
|
+
# :rr: Select a nanite according to round robin ordering.
|
219
|
+
# :offline_failsafe<Boolean>:: Store messages in an offline queue when all
|
220
|
+
# the nanites are offline. Messages will be redelivered when nanites come online.
|
221
|
+
# Default is false unless the mapper was started with the --offline-failsafe flag.
|
222
|
+
# :persistent<Boolean>:: Instructs the AMQP broker to save the message to persistent
|
223
|
+
# storage so that it isnt lost when the broker is restarted.
|
224
|
+
# Default is false unless the mapper was started with the --persistent flag.
|
225
|
+
#
|
226
|
+
# @api :public:
|
227
|
+
def push(type, payload = '', opts = {})
|
228
|
+
push = build_deliverable(Push, type, payload, opts)
|
229
|
+
send_push(push, opts)
|
230
|
+
end
|
231
|
+
|
232
|
+
def send_push(push, opts = {})
|
233
|
+
targets = cluster.targets_for(push)
|
234
|
+
if !targets.empty?
|
235
|
+
cluster.route(push, targets)
|
236
|
+
true
|
237
|
+
elsif opts.key?(:offline_failsafe) ? opts[:offline_failsafe] : options[:offline_failsafe]
|
238
|
+
cluster.publish(push, 'mapper-offline')
|
239
|
+
:offline
|
240
|
+
else
|
241
|
+
false
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
def build_deliverable(deliverable_type, type, payload, opts)
|
248
|
+
deliverable = deliverable_type.new(type, payload, nil, opts)
|
249
|
+
deliverable.from = identity
|
250
|
+
deliverable.token = Identity.generate
|
251
|
+
deliverable.persistent = opts.key?(:persistent) ? opts[:persistent] : options[:persistent]
|
252
|
+
deliverable
|
253
|
+
end
|
254
|
+
|
255
|
+
def setup_queues
|
256
|
+
if amq.respond_to?(:prefetch) && @options.has_key?(:prefetch)
|
257
|
+
amq.prefetch(@options[:prefetch])
|
258
|
+
end
|
259
|
+
|
260
|
+
setup_offline_queue
|
261
|
+
setup_message_queue
|
262
|
+
end
|
263
|
+
|
264
|
+
def setup_offline_queue
|
265
|
+
offline_queue = amq.queue('mapper-offline', :durable => true)
|
266
|
+
offline_queue.subscribe(:ack => true) do |info, deliverable|
|
267
|
+
deliverable = serializer.load(deliverable)
|
268
|
+
targets = cluster.targets_for(deliverable)
|
269
|
+
unless targets.empty?
|
270
|
+
info.ack
|
271
|
+
if deliverable.kind_of?(Request)
|
272
|
+
if job = job_warden.jobs[deliverable.token]
|
273
|
+
job.targets = targets
|
274
|
+
else
|
275
|
+
deliverable.reply_to = identity
|
276
|
+
job_warden.new_job(deliverable, targets)
|
277
|
+
end
|
278
|
+
end
|
279
|
+
cluster.route(deliverable, targets)
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
283
|
+
EM.add_periodic_timer(options[:offline_redelivery_frequency]) { offline_queue.recover }
|
284
|
+
end
|
285
|
+
|
286
|
+
def setup_message_queue
|
287
|
+
amq.queue(identity, :exclusive => true).bind(amq.fanout(identity)).subscribe do |msg|
|
288
|
+
begin
|
289
|
+
msg = serializer.load(msg)
|
290
|
+
Nanite::Log.debug("RECV #{msg.to_s}")
|
291
|
+
Nanite::Log.info("RECV #{msg.to_s([:from])}") unless Nanite::Log.level == :debug
|
292
|
+
job_warden.process(msg)
|
293
|
+
rescue Exception => e
|
294
|
+
Nanite::Log.error("RECV [result] #{e.message}")
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def setup_logging
|
300
|
+
Nanite::Log.init(@identity, @options[:log_path])
|
301
|
+
Nanite::Log.level = @options[:log_level] if @options[:log_level]
|
302
|
+
end
|
303
|
+
|
304
|
+
def setup_cluster
|
305
|
+
@cluster = Cluster.new(@amq, @options[:agent_timeout], @options[:identity], @serializer, self, @options[:redis], @options[:callbacks])
|
306
|
+
end
|
307
|
+
end
|
308
|
+
end
|
309
|
+
|