br-nanite 0.3.0

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.
@@ -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