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
data/README.rdoc
CHANGED
@@ -228,7 +228,7 @@ Second shell
|
|
228
228
|
|
229
229
|
Now run a mapper. Mappers can be run from within your Merb or Rails app, from an interactive irb shell, or from the command line. For this example we'll run it from the command line so open a third shell window and run the following:
|
230
230
|
|
231
|
-
cd examples
|
231
|
+
cd examples/simpleagent
|
232
232
|
./cli.rb
|
233
233
|
|
234
234
|
Which should soon return something like the following.
|
@@ -296,32 +296,82 @@ Mongrel on the other hand does not use EventMachine and therefore requires to wr
|
|
296
296
|
|
297
297
|
Using nanite with Passenger:
|
298
298
|
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
# catch these, stop AMQP, stop eventmachine, and re-throw to
|
308
|
-
Mongrel/Passenger's signal traps
|
309
|
-
EM.run do
|
310
|
-
['INT', 'TERM'].each do |sig|
|
311
|
-
old = trap(sig) do
|
312
|
-
AMQP.stop do
|
313
|
-
EM.stop
|
314
|
-
old.call
|
315
|
-
end
|
316
|
-
end
|
317
|
-
end
|
299
|
+
if defined?(PhusionPassenger)
|
300
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
301
|
+
if forked
|
302
|
+
if EM.reactor_running?
|
303
|
+
EM.stop_event_loop
|
304
|
+
EM.release_machine
|
305
|
+
EM.instance_variable_set( '@reactor_running', false )
|
318
306
|
end
|
307
|
+
Thread.current[:mq] = nil
|
308
|
+
AMQP.instance_variable_set('@conn', nil)
|
309
|
+
end
|
310
|
+
|
311
|
+
th = Thread.current
|
312
|
+
Thread.new{
|
313
|
+
Nanite.start_mapper(:host => 'localhost', :user => 'mapper', :pass => 'testing', :vhost => '/nanite', :log_level => 'info')
|
314
|
+
}
|
315
|
+
Thread.stop
|
316
|
+
end
|
317
|
+
end
|
319
318
|
|
320
319
|
=======
|
321
320
|
Where to put the mapper initialization code depends on the framework and our preference.
|
322
321
|
For Rails the canonical place to start our mapper is within nanite.rb (or any other filename you prefer) in config/initalizers.
|
323
322
|
In Merb we can use init.rb in config.
|
324
323
|
|
324
|
+
== Security
|
325
|
+
|
326
|
+
Nanite implements a secure serializer which can be used in place of the other serializers to encrypt the
|
327
|
+
AMQP messages exchanged between the mappers and the agents.
|
328
|
+
|
329
|
+
The secure serializer uses X.509 certificates and cryptographic keys to sign and encrypt the messages.
|
330
|
+
|
331
|
+
It is important to understand that:
|
332
|
+
1. A certificate only includes the public key component of a cryptographic key
|
333
|
+
pair.
|
334
|
+
2. Signing requires the use of a certificate and its private key, checking the
|
335
|
+
signature then only requires the certificate (the idea is that only the
|
336
|
+
signer has the secret private key and thus can sign but anyone can check the
|
337
|
+
signature).
|
338
|
+
3. Encrypting only requires the certificate but decrypting also requires the
|
339
|
+
private key (anyone can encrypt the data but only the intended recipient can
|
340
|
+
decrypt it).
|
341
|
+
|
342
|
+
A signing serializer thus needs access to the signer certificate and private
|
343
|
+
key. An encrypting serializer *also* needs access to the intended recipients
|
344
|
+
certificates. There needs to be a way to dynamically retrieve the corresponding
|
345
|
+
certificates. This is done using certificate stores.
|
346
|
+
|
347
|
+
Certificate stores associate identities with certificates. The identity is
|
348
|
+
associated when the data is serialized and can be keyed off to retrieve the
|
349
|
+
right certificate upon deserialization.
|
350
|
+
|
351
|
+
Nanite provides a static store implementation which can be used when the
|
352
|
+
certificates used for serialization are always the same and can be kept in
|
353
|
+
memory. Nanite also provides a certificate store proxy cache which can be
|
354
|
+
associated with any store implementation and will cache the most used
|
355
|
+
certificates.
|
356
|
+
|
357
|
+
The serializer should be initialized prior to being used by calling the 'init'
|
358
|
+
method:
|
359
|
+
|
360
|
+
# Initialize serializer, must be called prior to using it.
|
361
|
+
#
|
362
|
+
# - 'identity': Identity associated with serialized messages
|
363
|
+
# - 'cert': Certificate used to sign serialized messages and
|
364
|
+
# decrypt encrypted messages
|
365
|
+
# - 'key': Private key corresponding to 'cert'
|
366
|
+
# - 'store': Certificate store. Exposes certificates used for
|
367
|
+
# encryption and signature validation.
|
368
|
+
# - 'encrypt': Whether data should be signed and encrypted ('true')
|
369
|
+
# or just signed ('false'), 'true' by default.
|
370
|
+
#
|
371
|
+
def SecureSerializer.init(identity, cert, key, store, encrypt = true)
|
372
|
+
|
373
|
+
The 'secure' agent example (examples\secure) shows how the mappers and agents should be
|
374
|
+
configured to use the secure serializer.
|
325
375
|
|
326
376
|
== Troubleshooting
|
327
377
|
|
data/Rakefile
CHANGED
data/bin/nanite-agent
CHANGED
@@ -21,25 +21,51 @@ opts = OptionParser.new do |opts|
|
|
21
21
|
opts.on("--ping-time PINGTIME", "Specify how often the agents contacts the mapper") do |ping|
|
22
22
|
options[:ping_time] = ping
|
23
23
|
end
|
24
|
+
|
25
|
+
opts.on("--actors-dir DIR", "Path to directory containing actors (NANITE_ROOT/actors by default)") do |dir|
|
26
|
+
options[:actors_dir] = dir
|
27
|
+
end
|
28
|
+
|
29
|
+
opts.on("--actors ACTORS", "Comma separated list of actors to load (all ruby files in actors directory by default)") do |a|
|
30
|
+
options[:actors] = a.split(',')
|
31
|
+
end
|
32
|
+
|
33
|
+
opts.on("--initrb FILE", "Path to agent initialization file (NANITE_ROOT/init.rb by default)") do |initrb|
|
34
|
+
options[:initrb] = initrb
|
35
|
+
end
|
36
|
+
|
37
|
+
opts.on("--single-threaded", "Run all operations in one thread") do
|
38
|
+
options[:single_threaded] = true
|
39
|
+
end
|
24
40
|
end
|
25
41
|
|
26
42
|
opts.parse!
|
27
43
|
|
28
|
-
if ARGV[0] == 'stop'
|
44
|
+
if ARGV[0] == 'stop' || ARGV[0] == 'status'
|
29
45
|
agent = Nanite::Agent.new(options)
|
30
46
|
pid_file = Nanite::PidFile.new(agent.identity, agent.options)
|
31
47
|
unless pid = pid_file.read_pid
|
32
48
|
puts "#{pid_file} not found"
|
33
49
|
exit
|
34
50
|
end
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
51
|
+
if ARGV[0] == 'stop'
|
52
|
+
puts "Stopping nanite agent #{agent.identity} (pid #{pid})"
|
53
|
+
begin
|
54
|
+
Process.kill('TERM', pid)
|
55
|
+
rescue Errno::ESRCH
|
56
|
+
puts "Process does not exist (pid #{pid})"
|
57
|
+
exit
|
58
|
+
end
|
59
|
+
puts 'Done.'
|
60
|
+
else
|
61
|
+
if Process.getpgid(pid) != -1
|
62
|
+
psdata = `ps up #{pid}`.split("\n").last.split
|
63
|
+
memory = (psdata[5].to_i / 1024)
|
64
|
+
puts "The agent is alive, using #{memory}MB of memory"
|
65
|
+
else
|
66
|
+
puts "The agent is not running but has a stale pid file at #{pid_file}"
|
67
|
+
end
|
41
68
|
end
|
42
|
-
puts 'Done.'
|
43
69
|
exit
|
44
70
|
end
|
45
71
|
|
data/bin/nanite-mapper
CHANGED
@@ -17,21 +17,31 @@ end
|
|
17
17
|
|
18
18
|
opts.parse!
|
19
19
|
|
20
|
-
if ARGV[0] == 'stop'
|
20
|
+
if ARGV[0] == 'stop' || ARGV[0] == 'status'
|
21
21
|
mapper = Nanite::Mapper.new(options)
|
22
22
|
pid_file = Nanite::PidFile.new(mapper.identity, mapper.options)
|
23
23
|
unless pid = pid_file.read_pid
|
24
24
|
puts "#{pid_file} not found"
|
25
25
|
exit
|
26
26
|
end
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
27
|
+
if ARGV[0] == 'stop'
|
28
|
+
puts "Stopping nanite mapper #{mapper.identity} (pid #{pid})"
|
29
|
+
begin
|
30
|
+
Process.kill('TERM', pid)
|
31
|
+
rescue Errno::ESRCH
|
32
|
+
puts "Process does not exist (pid #{pid})"
|
33
|
+
exit
|
34
|
+
end
|
35
|
+
puts 'Done.'
|
36
|
+
else
|
37
|
+
if Process.getpgid(pid) != -1
|
38
|
+
psdata = `ps up #{pid}`.split("\n").last.split
|
39
|
+
memory = (psdata[5].to_i / 1024)
|
40
|
+
puts "The mapper is alive, using #{memory}MB of memory"
|
41
|
+
else
|
42
|
+
puts "The mapper is not running but has a stale pid file at #{pid_file}"
|
43
|
+
end
|
33
44
|
end
|
34
|
-
puts 'Done.'
|
35
45
|
exit
|
36
46
|
end
|
37
47
|
|
data/lib/nanite.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'amqp'
|
3
|
+
require 'mq'
|
4
|
+
require 'json'
|
5
|
+
require 'logger'
|
6
|
+
require 'yaml'
|
7
|
+
require 'openssl'
|
8
|
+
|
9
|
+
$:.unshift File.dirname(__FILE__)
|
10
|
+
require 'nanite/amqp'
|
11
|
+
require 'nanite/util'
|
12
|
+
require 'nanite/config'
|
13
|
+
require 'nanite/packets'
|
14
|
+
require 'nanite/identity'
|
15
|
+
require 'nanite/console'
|
16
|
+
require 'nanite/daemonize'
|
17
|
+
require 'nanite/pid_file'
|
18
|
+
require 'nanite/job'
|
19
|
+
require 'nanite/mapper'
|
20
|
+
require 'nanite/actor'
|
21
|
+
require 'nanite/actor_registry'
|
22
|
+
require 'nanite/streaming'
|
23
|
+
require 'nanite/dispatcher'
|
24
|
+
require 'nanite/agent'
|
25
|
+
require 'nanite/cluster'
|
26
|
+
require 'nanite/reaper'
|
27
|
+
require 'nanite/log'
|
28
|
+
require 'nanite/mapper_proxy'
|
29
|
+
require 'nanite/security_provider'
|
30
|
+
require 'nanite/security/cached_certificate_store_proxy'
|
31
|
+
require 'nanite/security/certificate'
|
32
|
+
require 'nanite/security/certificate_cache'
|
33
|
+
require 'nanite/security/distinguished_name'
|
34
|
+
require 'nanite/security/encrypted_document'
|
35
|
+
require 'nanite/security/rsa_key_pair'
|
36
|
+
require 'nanite/security/secure_serializer'
|
37
|
+
require 'nanite/security/signature'
|
38
|
+
require 'nanite/security/static_certificate_store'
|
39
|
+
require 'nanite/serializer'
|
40
|
+
|
41
|
+
module Nanite
|
42
|
+
VERSION = '0.4.0' unless defined?(Nanite::VERSION)
|
43
|
+
|
44
|
+
class MapperNotRunning < StandardError; end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
attr_reader :mapper, :agent
|
48
|
+
|
49
|
+
def start_agent(options = {})
|
50
|
+
@agent = Nanite::Agent.start(options)
|
51
|
+
end
|
52
|
+
|
53
|
+
def start_mapper(options = {})
|
54
|
+
@mapper = Nanite::Mapper.start(options)
|
55
|
+
end
|
56
|
+
|
57
|
+
def request(*args, &blk)
|
58
|
+
ensure_mapper
|
59
|
+
@mapper.request(*args, &blk)
|
60
|
+
end
|
61
|
+
|
62
|
+
def push(*args)
|
63
|
+
ensure_mapper
|
64
|
+
@mapper.push(*args)
|
65
|
+
end
|
66
|
+
|
67
|
+
def ensure_mapper
|
68
|
+
raise MapperNotRunning.new('A mapper needs to be started via Nanite.start_mapper') unless @mapper
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
data/lib/nanite/actor.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
module Nanite
|
2
|
+
# This mixin provides Nanite actor functionality.
|
3
|
+
#
|
4
|
+
# To use it simply include it your class containing the functionality to be exposed:
|
5
|
+
#
|
6
|
+
# class Foo
|
7
|
+
# include Nanite::Actor
|
8
|
+
# expose :bar
|
9
|
+
#
|
10
|
+
# def bar(payload)
|
11
|
+
# # ...
|
12
|
+
# end
|
13
|
+
#
|
14
|
+
# end
|
15
|
+
module Actor
|
16
|
+
|
17
|
+
def self.included(base)
|
18
|
+
base.class_eval do
|
19
|
+
include Nanite::Actor::InstanceMethods
|
20
|
+
extend Nanite::Actor::ClassMethods
|
21
|
+
end # base.class_eval
|
22
|
+
end # self.included
|
23
|
+
|
24
|
+
module ClassMethods
|
25
|
+
def default_prefix
|
26
|
+
to_s.to_const_path
|
27
|
+
end
|
28
|
+
|
29
|
+
def expose(*meths)
|
30
|
+
@exposed ||= []
|
31
|
+
meths.each do |meth|
|
32
|
+
@exposed << meth unless @exposed.include?(meth)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def provides_for(prefix)
|
37
|
+
return [] unless @exposed
|
38
|
+
@exposed.map {|meth| "/#{prefix}/#{meth}".squeeze('/')}
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_exception(proc = nil, &blk)
|
42
|
+
raise 'No callback provided for on_exception' unless proc || blk
|
43
|
+
@exception_callback = proc || blk
|
44
|
+
end
|
45
|
+
|
46
|
+
def exception_callback
|
47
|
+
@exception_callback
|
48
|
+
end
|
49
|
+
|
50
|
+
end # ClassMethods
|
51
|
+
|
52
|
+
module InstanceMethods
|
53
|
+
# send nanite request to another agent (through the mapper)
|
54
|
+
def request(*args, &blk)
|
55
|
+
MapperProxy.instance.request(*args, &blk)
|
56
|
+
end
|
57
|
+
end # InstanceMethods
|
58
|
+
|
59
|
+
end # Actor
|
60
|
+
end # Nanite
|
@@ -0,0 +1,24 @@
|
|
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
|
+
Nanite::Log.info("Registering #{actor.inspect} with prefix #{prefix.inspect}")
|
12
|
+
prefix ||= actor.class.default_prefix
|
13
|
+
actors[prefix.to_s] = actor
|
14
|
+
end
|
15
|
+
|
16
|
+
def services
|
17
|
+
actors.map {|prefix, actor| actor.class.provides_for(prefix) }.flatten.uniq
|
18
|
+
end
|
19
|
+
|
20
|
+
def actor_for(prefix)
|
21
|
+
actor = actors[prefix]
|
22
|
+
end
|
23
|
+
end # ActorRegistry
|
24
|
+
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
|