rightscale-nanite-dev 0.4.1.10
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/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
|