br-nanite 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,74 @@
1
+ require 'rubygems'
2
+ require 'rake/gempackagetask'
3
+ require "spec/rake/spectask"
4
+ begin; require 'rubygems'; rescue LoadError; end
5
+ begin
6
+ require 'hanna/rdoctask'
7
+ rescue LoadError
8
+ require 'rake/rdoctask'
9
+ end
10
+ require 'rake/clean'
11
+
12
+ GEM = "br-nanite"
13
+ VER = "0.3.0"
14
+ AUTHOR = "Ezra Zygmuntowicz"
15
+ EMAIL = "ezra@engineyard.com"
16
+ HOMEPAGE = "http://github.com/ezmobius/nanite"
17
+ SUMMARY = "self assembling fabric of ruby daemons"
18
+
19
+ Dir.glob('tasks/*.rake').each { |r| Rake.application.add_import r }
20
+
21
+ spec = Gem::Specification.new do |s|
22
+ s.name = GEM
23
+ s.version = ::VER
24
+ s.platform = Gem::Platform::RUBY
25
+ s.has_rdoc = true
26
+ s.extra_rdoc_files = ["README.rdoc", "LICENSE", 'TODO']
27
+ s.summary = SUMMARY
28
+ s.description = s.summary
29
+ s.author = AUTHOR
30
+ s.email = EMAIL
31
+ s.homepage = HOMEPAGE
32
+
33
+ s.bindir = "bin"
34
+ s.executables = %w( nanite-agent nanite-mapper nanite-admin )
35
+
36
+ s.add_dependency "extlib"
37
+ s.add_dependency('amqp', '>= 0.6.0')
38
+
39
+ s.require_path = 'lib'
40
+ s.files = %w(LICENSE README.rdoc Rakefile TODO) + Dir.glob("{lib,bin,specs}/**/*")
41
+ end
42
+
43
+ Rake::GemPackageTask.new(spec) do |pkg|
44
+ pkg.gem_spec = spec
45
+ end
46
+
47
+ task :install => [:package] do
48
+ sh %{sudo gem install pkg/#{GEM}-#{VER}}
49
+ end
50
+
51
+ desc "Run unit specs"
52
+ Spec::Rake::SpecTask.new do |t|
53
+ t.spec_opts = ["--format", "specdoc", "--colour"]
54
+ t.spec_files = FileList["spec/**/*_spec.rb"]
55
+ end
56
+
57
+ desc 'Generate RDoc documentation'
58
+ Rake::RDocTask.new do |rd|
59
+ rd.title = spec.name
60
+ rd.rdoc_dir = 'rdoc'
61
+ rd.main = "README.rdoc"
62
+ rd.rdoc_files.include("lib/**/*.rb", *spec.extra_rdoc_files)
63
+ end
64
+ CLOBBER.include(:clobber_rdoc)
65
+
66
+ desc 'Generate and open documentation'
67
+ task :docs => :rdoc do
68
+ case RUBY_PLATFORM
69
+ when /darwin/ ; sh 'open rdoc/index.html'
70
+ when /mswin|mingw/ ; sh 'start rdoc\index.html'
71
+ else
72
+ sh 'firefox rdoc/index.html'
73
+ end
74
+ end
data/TODO ADDED
@@ -0,0 +1,24 @@
1
+ TODO:
2
+
3
+ - The examples/crew.rb file is pointing towards a hard coded user dir. Needs to be
4
+ documented as part of a working example.
5
+ - examples/async_rack_front/async_rack_front.ru needs to be documented and verified working.
6
+
7
+ Ian:
8
+ - Sync Mapper/Agent#start with nanite-mapper/agent
9
+ - Update docs for Agent#start and Mapper#start
10
+ - Update docs in nanite-agent and nanite-mapper
11
+ - Ensure file transfer works
12
+ - Check secure stuff still works
13
+ - Check custom status_proc works
14
+ - Check documentation, only document public methods
15
+ - ensure the removal of threaded_actors option doesn't cause shit to block
16
+
17
+ - Look into using EM deferables for actors dispatch.
18
+ - Integration specs that spawn a small cluster of nanites
19
+ - Rename Ping to Status
20
+ - request/push should take *args for payload?
21
+
22
+ Maybe:
23
+ - Make mapper queue durable and Results respect :persistent flag on the request
24
+ - Add a global result received callback
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # To work without being installed as a gem:
4
+ libdir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
5
+ $:.unshift libdir unless $:.include? libdir
6
+
7
+ require 'nanite'
8
+ require 'nanite/admin'
9
+ require 'eventmachine'
10
+ require 'thin'
11
+ # IMPORTANT!
12
+ # You need raggi's patched async version of Thin at the moment to use
13
+ # the nanite-admin tool.
14
+ #
15
+ # raggi's Git repo contains a branch called 'async_for_rack' which contains the
16
+ # version of Thin you want to install. raggi has apparently removed the 'master'
17
+ # branch of his Git repo so you may see a warning like that shown below.
18
+ #
19
+ # git clone git://github.com/raggi/thin.git thin-raggi-async
20
+ # ...
21
+ # warning: remote HEAD refers to nonexistent ref, unable to checkout. <<== IGNORE THIS
22
+ #
23
+ # cd thin-raggi-async/
24
+ # git checkout --track -b async_for_rack origin/async_for_rack
25
+ # warning: You appear to be on a branch yet to be born. <<== IGNORE THIS
26
+ # warning: Forcing checkout of origin/async_for_rack. <<== IGNORE THIS
27
+ # Branch async_for_rack set up to track remote branch refs/remotes/origin/async_for_rack.
28
+ # Switched to a new branch "async_for_rack"
29
+
30
+ # run : 'rake install' to build and install the Thin gem
31
+ # cd <NANITE>
32
+ # ./bin/nanite-admin
33
+
34
+ # When you need to update this Thin install you should be able to do a 'git pull' on the
35
+ # "async_for_rack" branch.
36
+
37
+ require File.dirname(__FILE__) + '/../lib/nanite'
38
+ require 'yaml'
39
+ require "optparse"
40
+
41
+ include Nanite::CommonConfig
42
+
43
+ options = {}
44
+
45
+ opts = OptionParser.new do |opts|
46
+ opts.banner = "Usage: nanite-admin [-flags] [argument]"
47
+ opts.define_head "Nanite Admin: a basic control interface for your nanite cluster."
48
+ opts.separator '*'*80
49
+
50
+ setup_mapper_options(opts, options)
51
+ end
52
+
53
+ opts.parse!
54
+
55
+ EM.run do
56
+ Nanite.start_mapper(options)
57
+ puts "starting nanite-admin"
58
+ Rack::Handler::Thin.run(Nanite::Admin.new(Nanite.mapper), :Port => 4000)
59
+ end
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/nanite'
4
+ require 'optparse'
5
+
6
+ include Nanite::CommonConfig
7
+
8
+ options = {}
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: nanite-agent [-flag] [argument]"
12
+ opts.define_head "Nanite Agent: ruby process that acts upon messages passed to it by a mapper."
13
+ opts.separator '*'*80
14
+
15
+ setup_common_options(opts, options, 'agent')
16
+
17
+ opts.on("-n", "--nanite NANITE_ROOT", "Specify the root of your nanite agent project.") do |nanite|
18
+ options[:root] = nanite
19
+ end
20
+
21
+ opts.on("--ping-time PINGTIME", "Specify how often the agents contacts the mapper") do |ping|
22
+ options[:ping_time] = ping
23
+ end
24
+ end
25
+
26
+ opts.parse!
27
+
28
+ EM.run do
29
+ Nanite.start_agent(options)
30
+ end
@@ -0,0 +1,22 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/nanite'
4
+ require 'optparse'
5
+
6
+ include Nanite::CommonConfig
7
+
8
+ options = {}
9
+
10
+ opts = OptionParser.new do |opts|
11
+ opts.banner = "Usage: nanite-mapper [-flags] [argument]"
12
+ opts.define_head "Nanite Mapper: clustered head unit for self assembling cluster of ruby processes."
13
+ opts.separator '*'*80
14
+
15
+ setup_mapper_options(opts, options)
16
+ end
17
+
18
+ opts.parse!
19
+
20
+ EM.run do
21
+ Nanite.start_mapper(options)
22
+ end
@@ -0,0 +1,58 @@
1
+ require 'rubygems'
2
+ require 'amqp'
3
+ require 'mq'
4
+ require 'json'
5
+ require 'logger'
6
+ require 'yaml'
7
+
8
+ $:.unshift File.dirname(__FILE__)
9
+ require 'nanite/amqp'
10
+ require 'nanite/util'
11
+ require 'nanite/config'
12
+ require 'nanite/packets'
13
+ require 'nanite/identity'
14
+ require 'nanite/console'
15
+ require 'nanite/daemonize'
16
+ require 'nanite/job'
17
+ require 'nanite/mapper'
18
+ require 'nanite/actor'
19
+ require 'nanite/actor_registry'
20
+ require 'nanite/streaming'
21
+ require 'nanite/dispatcher'
22
+ require 'nanite/agent'
23
+ require 'nanite/cluster'
24
+ require 'nanite/reaper'
25
+ require 'nanite/serializer'
26
+ require 'nanite/log'
27
+
28
+ module Nanite
29
+ VERSION = '0.3.0' unless defined?(Nanite::VERSION)
30
+
31
+ class MapperNotRunning < StandardError; end
32
+
33
+ class << self
34
+ attr_reader :mapper, :agent
35
+
36
+ def start_agent(options = {})
37
+ @agent = Nanite::Agent.start(options)
38
+ end
39
+
40
+ def start_mapper(options = {})
41
+ @mapper = Nanite::Mapper.start(options)
42
+ end
43
+
44
+ def request(*args, &blk)
45
+ ensure_mapper
46
+ @mapper.request(*args, &blk)
47
+ end
48
+
49
+ def push(*args)
50
+ ensure_mapper
51
+ @mapper.push(*args)
52
+ end
53
+
54
+ def ensure_mapper
55
+ raise MapperNotRunning.new('A mapper needs to be started via Nanite.start_mapper') unless @mapper
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,64 @@
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
+ if Nanite::Actor == self
44
+ raise 'Method name callbacks cannot be used on the Nanite::Actor superclass' if Symbol === proc || String === proc
45
+ @superclass_exception_callback = proc || blk
46
+ else
47
+ @instance_exception_callback = proc || blk
48
+ end
49
+ end
50
+
51
+ def superclass_exception_callback
52
+ @superclass_exception_callback
53
+ end
54
+
55
+ def instance_exception_callback
56
+ @instance_exception_callback
57
+ end
58
+ end # ClassMethods
59
+
60
+ module InstanceMethods
61
+ end # InstanceMethods
62
+
63
+ end # Actor
64
+ end # Nanite
@@ -0,0 +1,25 @@
1
+ module Nanite
2
+ class ActorRegistry
3
+ attr_reader :actors, :log
4
+
5
+ def initialize(log)
6
+ @log = log
7
+ @actors = {}
8
+ end
9
+
10
+ def register(actor, prefix)
11
+ raise ArgumentError, "#{actor.inspect} is not a Nanite::Actor subclass instance" unless Nanite::Actor === actor
12
+ log.info("Registering #{actor.inspect} with prefix #{prefix.inspect}")
13
+ prefix ||= actor.class.default_prefix
14
+ actors[prefix.to_s] = actor
15
+ end
16
+
17
+ def services
18
+ actors.map {|prefix, actor| actor.class.provides_for(prefix) }.flatten.uniq
19
+ end
20
+
21
+ def actor_for(prefix)
22
+ actor = actors[prefix]
23
+ end
24
+ end # ActorRegistry
25
+ end # Nanite
@@ -0,0 +1,147 @@
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|
29
+ env['async.callback'].call [200, {'Content-Type' => 'text/html'}, [layout(ul(response))]]
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.map{|n,s| s[:services] }.flatten.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)
47
+ buf = "<ul>"
48
+ hash.each do |k,v|
49
+ buf << "<li><div class=\"nanite\">#{k}:</div><div class=\"response\">#{v.inspect}</div></li>"
50
+ end
51
+ buf << "</ul>"
52
+ buf
53
+ end
54
+
55
+ def layout(content=nil)
56
+ %Q{
57
+ <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
58
+ <html xmlns='http://www.w3.org/1999/xhtml'>
59
+ <head>
60
+ <meta content='text/html; charset=utf-8' http-equiv='Content-Type' />
61
+ <meta content='en' http-equiv='Content-Language' />
62
+ <meta content='Engineyard' name='author' />
63
+ <title>Nanite Control Tower</title>
64
+
65
+ <!-- Google AJAX Libraries API -->
66
+ <script src="http://www.google.com/jsapi"></script>
67
+ <script type="text/javascript">
68
+ google.load("jquery", "1");
69
+ </script>
70
+
71
+ <script type="text/javascript">
72
+ $(document).ready(function(){
73
+
74
+ // set the focus to the payload field
75
+ $("#payload").focus();
76
+
77
+ });
78
+ </script>
79
+
80
+ <style>
81
+ body {margin: 0; font-family: verdana; background-color: #fcfcfc;}
82
+ ul {margin: 0; padding: 0; margin-left: 10px}
83
+ li {list-style-type: none; margin-bottom: 6px}
84
+ li .nanite {font-weight: bold; font-size: 12px}
85
+ li .response {padding: 8px}
86
+ h1, h2, h3 {margin-top: none; padding: none; margin-left: 40px;}
87
+ h1 {font-size: 22px; margin-top: 40px; margin-bottom: 30px; border-bottom: 1px solid #ddd; padding-bottom: 6px;
88
+ margin-right: 40px}
89
+ h2 {font-size: 16px;}
90
+ h3 {margin-left: 0; font-size: 14px}
91
+ .section {border: 1px solid #ccc; background-color: #fefefe; padding: 10px; margin: 20px 40px; padding: 20px;
92
+ font-size: 14px}
93
+ #footer {text-align: center; color: #AAA; font-size: 12px}
94
+ </style>
95
+
96
+ </head>
97
+
98
+ <body>
99
+ <div id="header">
100
+ <h1>Nanite Control Tower</h1>
101
+ </div>
102
+
103
+ <h2>#{@mapper.options[:vhost]}</h2>
104
+ <div class="section">
105
+ <form method="post" action="/">
106
+ <input type="hidden" value="POST" name="_method"/>
107
+
108
+ <label>Send</label>
109
+ <select name="type">
110
+ <option #{@selection == 'least_loaded' ? 'selected="true"' : ''} value="least_loaded">the least loaded nanite</option>
111
+ <option #{@selection == 'random' ? 'selected="true"' : ''} value="random">a random nanite</option>
112
+ <option #{@selection == 'all' ? 'selected="true"' : ''} value="all">all nanites</option>
113
+ <option #{@selection == 'rr' ? 'selected="true"' : ''} value="rr">a nanite chosen by round robin</option>
114
+ #{@mapper.cluster.nanites.map {|k,v| "<option #{@selection == k ? 'selected="true"' : ''} value='#{k}'>#{k}</option>" }.join}
115
+ </select>
116
+
117
+ <label>providing service</label>
118
+ #{services}
119
+
120
+ <label>the payload</label>
121
+ <input type="text" class="text" name="payload" id="payload"/>
122
+
123
+ <input type="submit" class="submit" value="Go!" name="submit"/>
124
+ </form>
125
+
126
+ #{"<h3>Responses</h3>" if content}
127
+ #{content}
128
+ </div>
129
+
130
+ <h2>Running nanites</h2>
131
+ <div class="section">
132
+ #{"No nanites online." if @mapper.cluster.nanites.size == 0}
133
+ <ul>
134
+ #{@mapper.cluster.nanites.map {|k,v| "<li>identity : #{k}<br />load : #{v[:status]}<br />services : #{v[:services].inspect}</li>" }.join}
135
+ </ul>
136
+ </div>
137
+ <div id="footer">
138
+ Nanite #{Nanite::VERSION}
139
+ <br />
140
+ &copy; 2009 a bunch of random geeks
141
+ </div>
142
+ </body>
143
+ </html>
144
+ }
145
+ end
146
+ end
147
+ end