rightscale-nanite-dev 0.4.1.10
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 +78 -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 +274 -0
- data/lib/nanite/amqp.rb +58 -0
- data/lib/nanite/cluster.rb +256 -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 +315 -0
- data/lib/nanite/mapper_proxy.rb +75 -0
- data/lib/nanite/nanite_dispatcher.rb +92 -0
- data/lib/nanite/packets.rb +401 -0
- data/lib/nanite/pid_file.rb +52 -0
- data/lib/nanite/reaper.rb +39 -0
- data/lib/nanite/redis_tag_store.rb +141 -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 +135 -0
- data/lib/nanite/streaming.rb +125 -0
- data/lib/nanite/util.rb +78 -0
- metadata +111 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
module Nanite
|
2
|
+
class ActorRegistry
|
3
|
+
attr_reader :actors
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@actors = {}
|
7
|
+
end
|
8
|
+
|
9
|
+
def register(actor, prefix)
|
10
|
+
raise ArgumentError, "#{actor.inspect} is not a Nanite::Actor subclass instance" unless Nanite::Actor === actor
|
11
|
+
log_msg = "[actor] #{actor.class.to_s}"
|
12
|
+
log_msg += ", prefix #{prefix}" if prefix && !prefix.empty?
|
13
|
+
Nanite::Log.info(log_msg)
|
14
|
+
prefix ||= actor.class.default_prefix
|
15
|
+
actors[prefix.to_s] = actor
|
16
|
+
end
|
17
|
+
|
18
|
+
def services
|
19
|
+
actors.map {|prefix, actor| actor.class.provides_for(prefix) }.flatten.uniq
|
20
|
+
end
|
21
|
+
|
22
|
+
def actor_for(prefix)
|
23
|
+
actor = actors[prefix]
|
24
|
+
end
|
25
|
+
end # ActorRegistry
|
26
|
+
end # Nanite
|
data/lib/nanite/admin.rb
ADDED
@@ -0,0 +1,138 @@
|
|
1
|
+
require 'rack'
|
2
|
+
|
3
|
+
module Nanite
|
4
|
+
# This is a Rack app for nanite-admin. You need to have an async capable
|
5
|
+
# version of Thin installed for this to work. See bin/nanite-admin for install
|
6
|
+
# instructions.
|
7
|
+
class Admin
|
8
|
+
def initialize(mapper)
|
9
|
+
@mapper = mapper
|
10
|
+
end
|
11
|
+
|
12
|
+
AsyncResponse = [-1, {}, []].freeze
|
13
|
+
|
14
|
+
def call(env)
|
15
|
+
req = Rack::Request.new(env)
|
16
|
+
if cmd = req.params['command']
|
17
|
+
@command = cmd
|
18
|
+
@selection = req.params['type'] if req.params['type']
|
19
|
+
|
20
|
+
options = {}
|
21
|
+
case @selection
|
22
|
+
when 'least_loaded', 'random', 'all', 'rr'
|
23
|
+
options[:selector] = @selection
|
24
|
+
else
|
25
|
+
options[:target] = @selection
|
26
|
+
end
|
27
|
+
|
28
|
+
@mapper.request(cmd, req.params['payload'], options) do |response, responsejob|
|
29
|
+
env['async.callback'].call [200, {'Content-Type' => 'text/html'}, [layout(ul(response, responsejob))]]
|
30
|
+
end
|
31
|
+
AsyncResponse
|
32
|
+
else
|
33
|
+
[200, {'Content-Type' => 'text/html'}, layout]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def services
|
38
|
+
buf = "<select name='command'>"
|
39
|
+
@mapper.cluster.nanites.all_services.each do |srv|
|
40
|
+
buf << "<option value='#{srv}' #{@command == srv ? 'selected="true"' : ''}>#{srv}</option>"
|
41
|
+
end
|
42
|
+
buf << "</select>"
|
43
|
+
buf
|
44
|
+
end
|
45
|
+
|
46
|
+
def ul(hash, job)
|
47
|
+
buf = "<ul>"
|
48
|
+
hash.each do |k,v|
|
49
|
+
buf << "<li><div class=\"nanite\">#{k}:</div><div class=\"response\">#{v.inspect}</div>"
|
50
|
+
if job.intermediate_state && job.intermediate_state[k]
|
51
|
+
buf << "<div class=\"intermediatestates\"><span class=\"statenote\">intermediate state:</span> #{job.intermediate_state[k].inspect}</div>"
|
52
|
+
end
|
53
|
+
buf << "</li>"
|
54
|
+
end
|
55
|
+
buf << "</ul>"
|
56
|
+
buf
|
57
|
+
end
|
58
|
+
|
59
|
+
def layout(content=nil)
|
60
|
+
%Q{
|
61
|
+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
62
|
+
<html xmlns='http://www.w3.org/1999/xhtml'>
|
63
|
+
<head>
|
64
|
+
<meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
|
65
|
+
<meta content='en' http-equiv='Content-Language' />
|
66
|
+
<meta content='Engineyard' name='author' />
|
67
|
+
<title>Nanite Control Tower</title>
|
68
|
+
|
69
|
+
<style>
|
70
|
+
body {margin: 0; font-family: verdana; background-color: #fcfcfc;}
|
71
|
+
ul {margin: 0; padding: 0; margin-left: 10px}
|
72
|
+
li {list-style-type: none; margin-bottom: 6px}
|
73
|
+
li .nanite {font-weight: bold; font-size: 12px}
|
74
|
+
li .response {padding: 8px}
|
75
|
+
li .intermediatestates {padding: 8px; font-size: 10px;}
|
76
|
+
li .intermediatestates span.statenote {font-style: italic;}
|
77
|
+
h1, h2, h3 {margin-top: none; padding: none; margin-left: 40px;}
|
78
|
+
h1 {font-size: 22px; margin-top: 40px; margin-bottom: 30px; border-bottom: 1px solid #ddd; padding-bottom: 6px;
|
79
|
+
margin-right: 40px}
|
80
|
+
h2 {font-size: 16px;}
|
81
|
+
h3 {margin-left: 0; font-size: 14px}
|
82
|
+
.section {border: 1px solid #ccc; background-color: #fefefe; padding: 10px; margin: 20px 40px; padding: 20px;
|
83
|
+
font-size: 14px}
|
84
|
+
#footer {text-align: center; color: #AAA; font-size: 12px}
|
85
|
+
</style>
|
86
|
+
|
87
|
+
</head>
|
88
|
+
|
89
|
+
<body>
|
90
|
+
<div id="header">
|
91
|
+
<h1>Nanite Control Tower</h1>
|
92
|
+
</div>
|
93
|
+
|
94
|
+
<h2>#{@mapper.options[:vhost]}</h2>
|
95
|
+
<div class="section">
|
96
|
+
<form method="post" action="/">
|
97
|
+
<input type="hidden" value="POST" name="_method"/>
|
98
|
+
|
99
|
+
<label>Send</label>
|
100
|
+
<select name="type">
|
101
|
+
<option #{@selection == 'least_loaded' ? 'selected="true"' : ''} value="least_loaded">the least loaded nanite</option>
|
102
|
+
<option #{@selection == 'random' ? 'selected="true"' : ''} value="random">a random nanite</option>
|
103
|
+
<option #{@selection == 'all' ? 'selected="true"' : ''} value="all">all nanites</option>
|
104
|
+
<option #{@selection == 'rr' ? 'selected="true"' : ''} value="rr">a nanite chosen by round robin</option>
|
105
|
+
#{@mapper.cluster.nanites.map {|k,v| "<option #{@selection == k ? 'selected="true"' : ''} value='#{k}'>#{k}</option>" }.join}
|
106
|
+
</select>
|
107
|
+
|
108
|
+
<label>providing service</label>
|
109
|
+
#{services}
|
110
|
+
|
111
|
+
<label>the payload</label>
|
112
|
+
<input type="text" class="text" name="payload" id="payload"/>
|
113
|
+
|
114
|
+
<input type="submit" class="submit" value="Go!" name="submit"/>
|
115
|
+
</form>
|
116
|
+
|
117
|
+
#{"<h3>Responses</h3>" if content}
|
118
|
+
#{content}
|
119
|
+
</div>
|
120
|
+
|
121
|
+
<h2>Running nanites</h2>
|
122
|
+
<div class="section">
|
123
|
+
#{"No nanites online." if @mapper.cluster.nanites.size == 0}
|
124
|
+
<ul>
|
125
|
+
#{@mapper.cluster.nanites.map {|k,v| "<li>identity : #{k}<br />load : #{v[:status]}<br />services : #{v[:services].to_a.inspect}<br />tags: #{v[:tags].to_a.inspect}</li>" }.join}
|
126
|
+
</ul>
|
127
|
+
</div>
|
128
|
+
<div id="footer">
|
129
|
+
Nanite #{Nanite::VERSION}
|
130
|
+
<br />
|
131
|
+
© 2009 a bunch of random geeks
|
132
|
+
</div>
|
133
|
+
</body>
|
134
|
+
</html>
|
135
|
+
}
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
data/lib/nanite/agent.rb
ADDED
@@ -0,0 +1,274 @@
|
|
1
|
+
module Nanite
|
2
|
+
class Agent
|
3
|
+
include AMQPHelper
|
4
|
+
include FileStreaming
|
5
|
+
include ConsoleHelper
|
6
|
+
include DaemonizeHelper
|
7
|
+
|
8
|
+
attr_reader :identity, :options, :serializer, :dispatcher, :registry, :amq, :tags
|
9
|
+
attr_accessor :status_proc
|
10
|
+
|
11
|
+
DEFAULT_OPTIONS = COMMON_DEFAULT_OPTIONS.merge({
|
12
|
+
:user => 'nanite',
|
13
|
+
:ping_time => 15,
|
14
|
+
:default_services => []
|
15
|
+
}) unless defined?(DEFAULT_OPTIONS)
|
16
|
+
|
17
|
+
# Initializes a new agent and establishes AMQP connection.
|
18
|
+
# This must be used inside EM.run block or if EventMachine reactor
|
19
|
+
# is already started, for instance, by a Thin server that your Merb/Rails
|
20
|
+
# application runs on.
|
21
|
+
#
|
22
|
+
# Agent options:
|
23
|
+
#
|
24
|
+
# identity : identity of this agent, may be any string
|
25
|
+
#
|
26
|
+
# status_proc : a callable object that returns agent load as a string,
|
27
|
+
# defaults to load averages string extracted from `uptime`
|
28
|
+
# format : format to use for packets serialization. One of the three:
|
29
|
+
# :marshall, :json, or :yaml. Defaults to
|
30
|
+
# Ruby's Marshall format. For interoperability with
|
31
|
+
# AMQP clients implemented in other languages, use JSON.
|
32
|
+
#
|
33
|
+
# Note that Nanite uses JSON gem,
|
34
|
+
# and ActiveSupport's JSON encoder may cause clashes
|
35
|
+
# if ActiveSupport is loaded after JSON gem.
|
36
|
+
#
|
37
|
+
# root : application root for this agent, defaults to Dir.pwd
|
38
|
+
#
|
39
|
+
# log_dir : path to directory where agent stores it's log file
|
40
|
+
# if not given, app_root is used.
|
41
|
+
#
|
42
|
+
# file_root : path to directory to files this agent provides
|
43
|
+
# defaults to app_root/files
|
44
|
+
#
|
45
|
+
# ping_time : time interval in seconds between two subsequent heartbeat messages
|
46
|
+
# this agent broadcasts. Default value is 15.
|
47
|
+
#
|
48
|
+
# console : true tells Nanite to start interactive console
|
49
|
+
#
|
50
|
+
# daemonize : true tells Nanite to daemonize
|
51
|
+
#
|
52
|
+
# pid_dir : path to the directory where the agent stores its pid file (only if daemonized)
|
53
|
+
# defaults to the root or the current working directory.
|
54
|
+
#
|
55
|
+
# services : list of services provided by this agent, by default
|
56
|
+
# all methods exposed by actors are listed
|
57
|
+
#
|
58
|
+
# single_threaded: Run all operations in one thread
|
59
|
+
#
|
60
|
+
# threadpool_size: Number of threads to run operations in
|
61
|
+
#
|
62
|
+
# Connection options:
|
63
|
+
#
|
64
|
+
# vhost : AMQP broker vhost that should be used
|
65
|
+
#
|
66
|
+
# user : AMQP broker user
|
67
|
+
#
|
68
|
+
# pass : AMQP broker password
|
69
|
+
#
|
70
|
+
# host : host AMQP broker (or node of interest) runs on,
|
71
|
+
# defaults to 0.0.0.0
|
72
|
+
#
|
73
|
+
# port : port AMQP broker (or node of interest) runs on,
|
74
|
+
# this defaults to 5672, port used by some widely
|
75
|
+
# used AMQP brokers (RabbitMQ and ZeroMQ)
|
76
|
+
#
|
77
|
+
# On start Nanite reads config.yml, so it is common to specify
|
78
|
+
# options in the YAML file. However, when both Ruby code options
|
79
|
+
# and YAML file specify option, Ruby code options take precedence.
|
80
|
+
def self.start(options = {})
|
81
|
+
agent = new(options)
|
82
|
+
agent.run
|
83
|
+
agent
|
84
|
+
end
|
85
|
+
|
86
|
+
def initialize(opts)
|
87
|
+
set_configuration(opts)
|
88
|
+
@tags = []
|
89
|
+
@tags << opts[:tag] if opts[:tag]
|
90
|
+
@tags.flatten!
|
91
|
+
@options.freeze
|
92
|
+
end
|
93
|
+
|
94
|
+
def run
|
95
|
+
Log.init(@identity, @options[:log_path])
|
96
|
+
Log.level = @options[:log_level] if @options[:log_level]
|
97
|
+
@serializer = Serializer.new(@options[:format])
|
98
|
+
@status_proc = lambda { parse_uptime(`uptime 2> /dev/null`) rescue 'no status' }
|
99
|
+
pid_file = PidFile.new(@identity, @options)
|
100
|
+
pid_file.check
|
101
|
+
if @options[:daemonize]
|
102
|
+
daemonize(@identity, @options)
|
103
|
+
pid_file.write
|
104
|
+
at_exit { pid_file.remove }
|
105
|
+
end
|
106
|
+
@amq = start_amqp(@options)
|
107
|
+
@registry = ActorRegistry.new
|
108
|
+
@dispatcher = Dispatcher.new(@amq, @registry, @serializer, @identity, @options)
|
109
|
+
setup_mapper_proxy
|
110
|
+
load_actors
|
111
|
+
setup_traps
|
112
|
+
setup_queue
|
113
|
+
advertise_services
|
114
|
+
setup_heartbeat
|
115
|
+
at_exit { un_register } unless $TESTING
|
116
|
+
start_console if @options[:console] && !@options[:daemonize]
|
117
|
+
end
|
118
|
+
|
119
|
+
def register(actor, prefix = nil)
|
120
|
+
registry.register(actor, prefix)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Can be used in agent's initialization file to register a security module
|
124
|
+
# This security module 'authorize' method will be called back whenever the
|
125
|
+
# agent receives a request and will be given the corresponding deliverable.
|
126
|
+
# It should return 'true' for the request to proceed.
|
127
|
+
# Requests will return 'deny_token' or the string "Denied" by default when
|
128
|
+
# 'authorize' does not return 'true'.
|
129
|
+
def register_security(security, deny_token = "Denied")
|
130
|
+
@security = security
|
131
|
+
@deny_token = deny_token
|
132
|
+
end
|
133
|
+
|
134
|
+
# Update set of tags published by agent and notify mapper
|
135
|
+
# Add tags in 'new_tags' and remove tags in 'old_tags'
|
136
|
+
def update_tags(new_tags, old_tags)
|
137
|
+
@tags += (new_tags || [])
|
138
|
+
@tags -= (old_tags || [])
|
139
|
+
@tags.uniq!
|
140
|
+
tag_update = TagUpdate.new(identity, new_tags, old_tags)
|
141
|
+
amq.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(tag_update))
|
142
|
+
end
|
143
|
+
|
144
|
+
protected
|
145
|
+
|
146
|
+
def set_configuration(opts)
|
147
|
+
@options = DEFAULT_OPTIONS.clone
|
148
|
+
root = opts[:root] || @options[:root]
|
149
|
+
custom_config = if root
|
150
|
+
file = File.expand_path(File.join(root, 'config.yml'))
|
151
|
+
File.exists?(file) ? (YAML.load(IO.read(file)) || {}) : {}
|
152
|
+
else
|
153
|
+
{}
|
154
|
+
end
|
155
|
+
opts.delete(:identity) unless opts[:identity]
|
156
|
+
@options.update(custom_config.merge(opts))
|
157
|
+
@options[:file_root] ||= File.join(@options[:root], 'files')
|
158
|
+
@options[:log_path] = false
|
159
|
+
if @options[:daemonize]
|
160
|
+
@options[:log_path] = (@options[:log_dir] || @options[:root] || Dir.pwd)
|
161
|
+
end
|
162
|
+
|
163
|
+
return @identity = "nanite-#{@options[:identity]}" if @options[:identity]
|
164
|
+
token = Identity.generate
|
165
|
+
@identity = "nanite-#{token}"
|
166
|
+
File.open(File.expand_path(File.join(@options[:root], 'config.yml')), 'w') do |fd|
|
167
|
+
fd.write(YAML.dump(custom_config.merge(:identity => token)))
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def load_actors
|
172
|
+
return unless options[:root]
|
173
|
+
actors_dir = @options[:actors_dir] || "#{@options[:root]}/actors"
|
174
|
+
Nanite::Log.warn("Actors dir #{actors_dir} does not exist or is not reachable") unless File.directory?(actors_dir)
|
175
|
+
actors = @options[:actors]
|
176
|
+
Dir["#{actors_dir}/*.rb"].each do |actor|
|
177
|
+
next if actors && !actors.include?(File.basename(actor, ".rb"))
|
178
|
+
Nanite::Log.info("[setup] loading #{actor}")
|
179
|
+
require actor
|
180
|
+
end
|
181
|
+
init_path = @options[:initrb] || File.join(options[:root], 'init.rb')
|
182
|
+
if File.exist?(init_path)
|
183
|
+
instance_eval(File.read(init_path), init_path)
|
184
|
+
else
|
185
|
+
Nanite::Log.warn("init.rb #{init_path} does not exist or is not reachable") unless File.exists?(init_path)
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def receive(packet)
|
190
|
+
Nanite::Log.debug("RECV #{packet.to_s}")
|
191
|
+
case packet
|
192
|
+
when Advertise
|
193
|
+
Nanite::Log.info("RECV #{packet.to_s}") unless Nanite::Log.level == :debug
|
194
|
+
advertise_services
|
195
|
+
when Request, Push
|
196
|
+
if @security && !@security.authorize(packet)
|
197
|
+
Nanite::Log.warn("RECV NOT AUTHORIZED #{packet.to_s}")
|
198
|
+
if packet.kind_of?(Request)
|
199
|
+
r = Result.new(packet.token, packet.reply_to, @deny_token, identity)
|
200
|
+
amq.queue(packet.reply_to, :no_declare => options[:secure]).publish(serializer.dump(r))
|
201
|
+
end
|
202
|
+
else
|
203
|
+
Nanite::Log.info("RECV #{packet.to_s([:from, :tags])}") unless Nanite::Log.level == :debug
|
204
|
+
dispatcher.dispatch(packet)
|
205
|
+
end
|
206
|
+
when Result
|
207
|
+
Nanite::Log.info("RECV #{packet.to_s([])}") unless Nanite::Log.level == :debug
|
208
|
+
@mapper_proxy.handle_result(packet)
|
209
|
+
when IntermediateMessage
|
210
|
+
Nanite::Log.info("RECV #{packet.to_s([])}") unless Nanite::Log.level == :debug
|
211
|
+
@mapper_proxy.handle_intermediate_result(packet)
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def tag(*tags)
|
216
|
+
tags.each {|t| @tags << t}
|
217
|
+
@tags.uniq!
|
218
|
+
end
|
219
|
+
|
220
|
+
def setup_queue
|
221
|
+
amq.queue(identity, :durable => true).subscribe(:ack => true) do |info, msg|
|
222
|
+
begin
|
223
|
+
info.ack
|
224
|
+
receive(serializer.load(msg))
|
225
|
+
rescue Exception => e
|
226
|
+
Nanite::Log.error("RECV #{e.message}")
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def setup_heartbeat
|
232
|
+
EM.add_periodic_timer(options[:ping_time]) do
|
233
|
+
amq.fanout('heartbeat', :no_declare => options[:secure]).publish(serializer.dump(Ping.new(identity, status_proc.call)))
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def setup_mapper_proxy
|
238
|
+
@mapper_proxy = MapperProxy.new(identity, options)
|
239
|
+
end
|
240
|
+
|
241
|
+
def setup_traps
|
242
|
+
['INT', 'TERM'].each do |sig|
|
243
|
+
old = trap(sig) do
|
244
|
+
un_register
|
245
|
+
amq.instance_variable_get('@connection').close do
|
246
|
+
EM.stop
|
247
|
+
old.call if old.is_a? Proc
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
def un_register
|
254
|
+
unless @unregistered
|
255
|
+
@unregistered = true
|
256
|
+
Nanite::Log.info("SEND [un_register]")
|
257
|
+
amq.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(UnRegister.new(identity)))
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def advertise_services
|
262
|
+
reg = Register.new(identity, registry.services, status_proc.call, self.tags)
|
263
|
+
Nanite::Log.info("SEND #{reg.to_s}")
|
264
|
+
amq.fanout('registration', :no_declare => options[:secure]).publish(serializer.dump(reg))
|
265
|
+
end
|
266
|
+
|
267
|
+
def parse_uptime(up)
|
268
|
+
if up =~ /load averages?: (.*)/
|
269
|
+
a,b,c = $1.split(/\s+|,\s+/)
|
270
|
+
(a.to_f + b.to_f + c.to_f) / 3
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
data/lib/nanite/amqp.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
class MQ
|
2
|
+
class Queue
|
3
|
+
# Asks the broker to redeliver all unacknowledged messages on a
|
4
|
+
# specifieid channel. Zero or more messages may be redelivered.
|
5
|
+
#
|
6
|
+
# * requeue (default false)
|
7
|
+
# If this parameter is false, the message will be redelivered to the original recipient.
|
8
|
+
# If this flag is true, the server will attempt to requeue the message, potentially then
|
9
|
+
# delivering it to an alternative subscriber.
|
10
|
+
#
|
11
|
+
def recover requeue = false
|
12
|
+
@mq.callback{
|
13
|
+
@mq.send Protocol::Basic::Recover.new({ :requeue => requeue })
|
14
|
+
}
|
15
|
+
self
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# monkey patch to the amqp gem that adds :no_declare => true option for new
|
21
|
+
# Exchange objects. This allows us to send messeages to exchanges that are
|
22
|
+
# declared by the mappers and that we have no configuration priviledges on.
|
23
|
+
# temporary until we get this into amqp proper
|
24
|
+
MQ::Exchange.class_eval do
|
25
|
+
def initialize mq, type, name, opts = {}
|
26
|
+
@mq = mq
|
27
|
+
@type, @name, @opts = type, name, opts
|
28
|
+
@mq.exchanges[@name = name] ||= self
|
29
|
+
@key = opts[:key]
|
30
|
+
|
31
|
+
@mq.callback{
|
32
|
+
@mq.send AMQP::Protocol::Exchange::Declare.new({ :exchange => name,
|
33
|
+
:type => type,
|
34
|
+
:nowait => true }.merge(opts))
|
35
|
+
} unless name == "amq.#{type}" or name == '' or opts[:no_declare]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
module Nanite
|
40
|
+
module AMQPHelper
|
41
|
+
def start_amqp(options)
|
42
|
+
connection = AMQP.connect({
|
43
|
+
:user => options[:user],
|
44
|
+
:pass => options[:pass],
|
45
|
+
:vhost => options[:vhost],
|
46
|
+
:host => options[:host],
|
47
|
+
:port => (options[:port] || ::AMQP::PORT).to_i,
|
48
|
+
:insist => options[:insist] || false,
|
49
|
+
:retry => options[:retry] || 5,
|
50
|
+
:connection_status => options[:connection_callback] || proc {|event|
|
51
|
+
Nanite::Log.debug("CONNECTED to MQ") if event == :connected
|
52
|
+
Nanite::Log.debug("DISCONNECTED from MQ") if event == :disconnected
|
53
|
+
}
|
54
|
+
})
|
55
|
+
MQ.new(connection)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|