noah 0.0.5-jruby → 0.1-jruby

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +10 -0
  3. data/LICENSE +201 -0
  4. data/README.md +68 -212
  5. data/Rakefile +65 -41
  6. data/TODO.md +65 -0
  7. data/bin/noah +2 -1
  8. data/bin/noah-watcher.rb +103 -0
  9. data/config.ru +6 -3
  10. data/config/warble.rb +18 -0
  11. data/examples/README.md +116 -0
  12. data/examples/cluster.ru +2 -0
  13. data/examples/custom-watcher.rb +10 -0
  14. data/examples/httpclient-server.rb +7 -0
  15. data/examples/httpclient.rb +12 -0
  16. data/examples/httpclient2.rb +28 -0
  17. data/examples/js/FABridge.js +1452 -0
  18. data/examples/js/WebSocketMain.swf +830 -0
  19. data/examples/js/swfobject.js +851 -0
  20. data/examples/js/web_socket.js +312 -0
  21. data/examples/logger.rb +11 -0
  22. data/examples/reconfiguring-sinatra-watcher.rb +11 -0
  23. data/examples/reconfiguring-sinatra.rb +33 -0
  24. data/examples/simple-post.rb +17 -0
  25. data/examples/websocket.html +24 -0
  26. data/examples/websocket.rb +41 -0
  27. data/lib/noah.rb +6 -8
  28. data/lib/noah/app.rb +20 -268
  29. data/lib/noah/application_routes.rb +70 -0
  30. data/lib/noah/ark.rb +0 -0
  31. data/lib/noah/configuration_routes.rb +81 -0
  32. data/lib/noah/custom_watcher.rb +79 -0
  33. data/lib/noah/ephemeral_routes.rb +47 -0
  34. data/lib/noah/helpers.rb +37 -14
  35. data/lib/noah/host_routes.rb +69 -0
  36. data/lib/noah/models.rb +86 -5
  37. data/lib/noah/models/applications.rb +41 -0
  38. data/lib/noah/models/configurations.rb +49 -0
  39. data/lib/noah/models/ephemerals.rb +54 -0
  40. data/lib/noah/models/hosts.rb +56 -0
  41. data/lib/noah/models/services.rb +54 -0
  42. data/lib/noah/models/watchers.rb +62 -0
  43. data/lib/noah/passthrough.rb +11 -0
  44. data/lib/noah/service_routes.rb +71 -0
  45. data/lib/noah/validations.rb +1 -0
  46. data/lib/noah/validations/watcher_validations.rb +48 -0
  47. data/lib/noah/version.rb +1 -1
  48. data/lib/noah/watcher_routes.rb +45 -0
  49. data/noah.gemspec +25 -17
  50. data/spec/application_spec.rb +30 -30
  51. data/spec/configuration_spec.rb +78 -14
  52. data/spec/ephemeral_spec.rb +59 -0
  53. data/spec/host_spec.rb +21 -21
  54. data/spec/noahapp_application_spec.rb +6 -6
  55. data/spec/noahapp_configuration_spec.rb +5 -5
  56. data/spec/noahapp_ephemeral_spec.rb +115 -0
  57. data/spec/noahapp_host_spec.rb +3 -3
  58. data/spec/noahapp_service_spec.rb +10 -10
  59. data/spec/noahapp_watcher_spec.rb +123 -0
  60. data/spec/service_spec.rb +27 -27
  61. data/spec/spec_helper.rb +13 -22
  62. data/spec/support/db/.keep +0 -0
  63. data/spec/support/test-redis.conf +8 -0
  64. data/spec/watcher_spec.rb +62 -0
  65. data/views/index.haml +21 -15
  66. metadata +189 -146
  67. data/Gemfile.lock +0 -83
  68. data/doc/coverage/index.html +0 -138
  69. data/doc/coverage/jquery-1.3.2.min.js +0 -19
  70. data/doc/coverage/jquery.tablesorter.min.js +0 -15
  71. data/doc/coverage/lib-helpers_rb.html +0 -393
  72. data/doc/coverage/lib-models_rb.html +0 -1449
  73. data/doc/coverage/noah_rb.html +0 -2019
  74. data/doc/coverage/print.css +0 -12
  75. data/doc/coverage/rcov.js +0 -42
  76. data/doc/coverage/screen.css +0 -270
  77. data/lib/noah/applications.rb +0 -46
  78. data/lib/noah/configurations.rb +0 -49
  79. data/lib/noah/hosts.rb +0 -54
  80. data/lib/noah/services.rb +0 -57
  81. data/lib/noah/watchers.rb +0 -18
@@ -0,0 +1,79 @@
1
+ require 'eventmachine'
2
+ require 'uri'
3
+ require 'logger'
4
+ begin
5
+ require 'em-hiredis'
6
+ rescue LoadError
7
+ puts "Please install: em-hiredis"
8
+ end
9
+
10
+ @log = Logger.new(STDOUT)
11
+ @log.level = Logger::DEBUG
12
+
13
+ require File.join(File.dirname(__FILE__), 'passthrough')
14
+
15
+ module Noah
16
+
17
+ class CustomWatcher
18
+ extend Passthrough
19
+
20
+ passthrough :redis_host, :pattern, :destination, :run!, :run_watcher
21
+
22
+ attr_accessor :my_pattern, :my_destination, :my_redis
23
+
24
+ def self.watch(&blk)
25
+ watcher = Noah::Watcher.new
26
+ watcher.instance_eval(&blk) if block_given?
27
+ watcher
28
+ end
29
+
30
+ def initialize
31
+ @my_redis ||= ENV['REDIS_URL']
32
+ @my_pattern ||= '//noah'
33
+ end
34
+
35
+ def self.redis_host(host)
36
+ @my_redis = host
37
+ end
38
+
39
+ def self.pattern(pattern)
40
+ @my_pattern = pattern
41
+ end
42
+
43
+ def self.destination(destination)
44
+ @my_destination = destination
45
+ end
46
+
47
+ def self.run!
48
+ @my_destination.nil? ? (raise ArgumentError) : run_watcher(@my_destination)
49
+ end
50
+
51
+ private
52
+ def self.run_watcher(dest)
53
+ log = Logger.new(STDOUT)
54
+ log.level = Logger::INFO
55
+ log.debug "#{dest.inspect}"
56
+ redis_url = URI.parse(@my_redis)
57
+ db = redis_url.path.gsub(/\//,'')
58
+
59
+ EventMachine.run do
60
+ trap("TERM") { log.info "Killed"; EventMachine.stop }
61
+ trap("INT") { log.info "Interrupted"; EventMachine.stop }
62
+ channel = EventMachine::Channel.new
63
+ r = EventMachine::Hiredis::Client.connect(redis_url.host, redis_url.port)
64
+ # Pulling out dbnum for now. Need to rethink it
65
+ #log.info "Binding to pattern #{db}:#{@my_pattern}"
66
+ log.info "Binding to pattern #{@my_pattern}"
67
+ r.psubscribe("#{@my_pattern}*")
68
+ r.on(:pmessage) do |pattern, event, message|
69
+ log.debug "Got message"
70
+ channel.push "#{message}"
71
+ end
72
+ r.errback { log.info "Something went tango-uniform" }
73
+
74
+ sub = channel.subscribe {|msg| log.info "Calling message handler"; dest.call(msg)}
75
+ end
76
+ end
77
+ end
78
+
79
+ end
@@ -0,0 +1,47 @@
1
+ class Noah::App
2
+ get '/e/?' do
3
+ halt 404
4
+ end
5
+
6
+ get '/e/*' do
7
+ params["splat"].size == 0 ? (halt 404) : (e=Noah::Ephemeral.find(:path => "/#{params["splat"][0]}").first)
8
+ (halt 404) if e.nil?
9
+ content_type "application/octet-stream"
10
+ e.data.nil? ? "" : "#{e.data}"
11
+ end
12
+
13
+ put '/e/*/watch' do
14
+ required_params = ["endpoint"]
15
+ data = JSON.parse(request.body.read)
16
+ (data.keys.sort == required_params.sort) ? (e = Noah::Watcher.find(:path => params[:splat][0]).first) : (raise "Missing Parameters")
17
+ e.nil? ? (halt 404) : (w = e.watch!(:endpoint => data["endpoint"]))
18
+ w.to_json
19
+ end
20
+
21
+ put '/e/*' do
22
+ raise("Data too large") if request.body.size > 512
23
+ d = request.body.read || nil
24
+ opts = {:path => "/#{params[:splat][0]}", :data => d}
25
+ e = Noah::Ephemeral.find_or_create(opts)
26
+ if e.valid?
27
+ action = e.is_new? ? "create" : "update"
28
+ r = {"action" => action, "result" => "success", "id" => e.id, "path" => e.path, "data" => e.data}
29
+ r.to_json
30
+ else
31
+ raise "#{format_errors(e)}"
32
+ end
33
+ end
34
+
35
+ delete '/e/*' do
36
+ p = params[:splat][0]
37
+ e = Noah::Ephemeral.find(:path => "/"+p).first
38
+ if e
39
+ e.delete
40
+ r = {"result" => "success", "id" => "#{e.id}", "action" => "delete", "path" => e.name}
41
+ r.to_json
42
+ else
43
+ halt 404
44
+ end
45
+ end
46
+
47
+ end
data/lib/noah/helpers.rb CHANGED
@@ -1,56 +1,79 @@
1
- require File.join(File.dirname(__FILE__), 'models')
2
1
  module Noah
3
2
  module SinatraHelpers
4
- extend(Ohm)
3
+
4
+ def format_errors(model)
5
+ error_messages = model.errors.present do |e|
6
+ # Missing attributes
7
+ e.on [:name, :not_present], "Name attribute missing"
8
+ e.on [:status, :not_present], "Status attribute missing"
9
+ e.on [:format, :not_present], "Format attribute missing"
10
+ e.on [:body, :not_present], "Body attribute missing"
11
+ e.on [:application_id, :not_present], "Application attribute missing"
12
+ e.on [:path, :not_present], "Path attribute missing"
13
+ e.on [:pattern, :not_present], "Pattern attribute missing"
14
+ e.on [:endpoint, :not_present], "Endpoint attribute missing"
15
+ # Invalid option
16
+ e.on [:status, :not_member], "Status must be up, down or pending"
17
+ # Duplicate keys
18
+ e.on [[:name, :application_id], :not_unique], "Record already exists"
19
+ e.on [[:name, :host_id], :not_unique], "Record already exists"
20
+ e.on [[:endpoint, :pattern], :not_unique], "Record already exists"
21
+ e.on [:path, :not_unique], "Record already exists"
22
+ # Custom exceptions
23
+ e.on [:pattern, :already_provided], "Pattern is already provided"
24
+ e.on [:pattern, :replaces_existing], "Pattern would overwrite existing"
25
+ end
26
+ error_messages.first
27
+ end
5
28
 
6
29
  def host(opts = {})
7
- Host.find(opts).first
30
+ Noah::Host.find(opts).first
8
31
  end
9
32
 
10
33
  def hosts(opts = {})
11
- Hosts.all(opts)
34
+ Noah::Hosts.all(opts)
12
35
  end
13
36
 
14
37
  def service(opts = {})
15
- Service.find(options)
38
+ Noah::Service.find(options)
16
39
  end
17
40
 
18
41
  def services(opts = {})
19
- Services.all(opts)
42
+ Noah::Services.all(opts)
20
43
  end
21
44
 
22
45
  def host_service(hostname, servicename)
23
- h = Host.find(:name => hostname).first
46
+ h = Noah::Host.find(:name => hostname).first
24
47
  if h.nil?
25
48
  nil
26
49
  else
27
- Service.find(:host_id => h.id, :name => servicename).first
50
+ Noah::Service.find(:host_id => h.id, :name => servicename).first
28
51
  end
29
52
  end
30
53
 
31
54
  def host_services(hostname)
32
- h = Host.find(:name => hostname).first
55
+ h = Noah::Host.find(:name => hostname).first
33
56
  if h.nil?
34
57
  nil
35
58
  else
36
- Services.all(:host_id => id)
59
+ Noah::Services.all(:host_id => id)
37
60
  end
38
61
  end
39
62
 
40
63
  def application(opts = {})
41
- Application.find(opts).first
64
+ Noah::Application.find(opts).first
42
65
  end
43
66
 
44
67
  def applications(opts = {})
45
- Applications.all(opts)
68
+ Noah::Applications.all(opts)
46
69
  end
47
70
 
48
71
  def configuration(opts = {})
49
- Configuration.find(opts).first
72
+ Noah::Configuration.find(opts).first
50
73
  end
51
74
 
52
75
  def configurations(opts = {})
53
- Configurations.all(opts)
76
+ Noah::Configurations.all(opts)
54
77
  end
55
78
  end
56
79
 
@@ -0,0 +1,69 @@
1
+ class Noah::App
2
+ # Host URIs
3
+
4
+ # GET named {Service} for named {Host}
5
+ get '/h/:hostname/:servicename/?' do |hostname, servicename|
6
+ h = host_service(hostname, servicename)
7
+ if h.nil?
8
+ halt 404
9
+ else
10
+ h.to_json
11
+ end
12
+ end
13
+
14
+ # GET named {Host}
15
+ # @param :hostname name of {Host}
16
+ # @return [JSON] representation of {Host}
17
+ get '/h/:hostname/?' do |hostname|
18
+ h = host(:name => hostname)
19
+ if h.nil?
20
+ halt 404
21
+ else
22
+ h.to_json
23
+ end
24
+ end
25
+
26
+ # GET all {Hosts}
27
+ get '/h/?' do
28
+ hosts.map {|h| h.to_hash}
29
+ if hosts.size == 0
30
+ halt 404
31
+ else
32
+ hosts.to_json
33
+ end
34
+ end
35
+
36
+ put '/h/:hostname/watch' do |hostname|
37
+ required_params = ["endpoint"]
38
+ data = JSON.parse(request.body.read)
39
+ (data.keys.sort == required_params.sort) ? (h = Noah::Host.find(:name => hostname).first) : (raise "Missing Parameters")
40
+ h.nil? ? (halt 404) : (w = h.watch!(:endpoint => data['endpoint']))
41
+ w.to_json
42
+ end
43
+
44
+ put '/h/:hostname/?' do |hostname|
45
+ required_params = ["name", "status"]
46
+ data = JSON.parse(request.body.read)
47
+ (data.keys.sort == required_params.sort && data['name'] == hostname) ? (host = Noah::Host.find_or_create(:name => data['name'], :status => data['status'])) : (raise "Missing Parameters")
48
+ if host.valid?
49
+ r = {"result" => "success","id" => "#{host.id}","status" => "#{host.status}", "name" => "#{host.name}", "new_record" => host.is_new?}
50
+ r.to_json
51
+ else
52
+ raise "#{format_errors(host)}"
53
+ end
54
+ end
55
+
56
+ delete '/h/:hostname/?' do |hostname|
57
+ host = Noah::Host.find(:name => hostname).first
58
+ if host
59
+ services = []
60
+ Noah::Service.find(:host_id => host.id).sort.each {|x| services << x; x.delete} if host.services.size > 0
61
+ host.delete
62
+ r = {"result" => "success", "id" => "#{host.id}", "name" => "#{hostname}", "service_count" => "#{services.size}"}
63
+ r.to_json
64
+ else
65
+ halt 404
66
+ end
67
+ end
68
+
69
+ end
data/lib/noah/models.rb CHANGED
@@ -1,5 +1,86 @@
1
- require File.join(File.dirname(__FILE__),'hosts')
2
- require File.join(File.dirname(__FILE__),'services')
3
- require File.join(File.dirname(__FILE__),'applications')
4
- require File.join(File.dirname(__FILE__),'configurations')
5
- require File.join(File.dirname(__FILE__),'watchers')
1
+ require 'ohm'
2
+ require 'ohm/contrib'
3
+ module Noah
4
+ class Model < Ohm::Model
5
+ def self.inherited(model)
6
+
7
+ model.send :include, Ohm::Timestamping
8
+ model.send :include, Ohm::Typecast
9
+ model.send :include, Ohm::Callbacks
10
+ model.send :include, Ohm::ExtraValidations
11
+
12
+ # removing this as it's simply redundant
13
+ # model.after :save, :notify_via_redis_save
14
+ model.after :create, :notify_via_redis_create
15
+ model.after :update, :notify_via_redis_update
16
+ model.before :delete, :stash_name
17
+ model.after :delete, :notify_via_redis_delete
18
+ model.send :include, ModelClassMethods
19
+ end
20
+ end
21
+
22
+ module ModelClassMethods
23
+
24
+ def is_new?
25
+ self.created_at == self.updated_at
26
+ end
27
+
28
+
29
+ def watch!(opts={:endpoint => nil, :pattern => nil})
30
+ base_pattern = "#{self.patternize_me}"
31
+ opts[:endpoint].nil? ? (raise ArgumentError, "Need an endpoint") : endpoint=opts[:endpoint]
32
+ opts[:pattern].nil? ? pattern=base_pattern : pattern=opts[:pattern]
33
+
34
+ begin
35
+ w = Watcher.new :pattern => pattern, :endpoint => endpoint
36
+ w.valid? ? w.save : (raise "#{w.errors}")
37
+ w.name
38
+ rescue Exception => e
39
+ e.message
40
+ end
41
+ end
42
+
43
+ protected
44
+ def patternize_me
45
+ "//noah/#{self.class_to_lower}/#{name}"
46
+ end
47
+
48
+ def stash_name
49
+ @deleted_name = self.name
50
+ end
51
+
52
+ def class_to_lower
53
+ self.class.to_s.gsub(/(.*)::(\w)/,'\2').downcase
54
+ end
55
+ def dbnum
56
+ o = Ohm.options.first
57
+ return "0" if o.nil?
58
+ return "0" if (o[:db].nil? && o[:url].nil?)
59
+ o[:db].nil? ? "#{o[:url].split('/').last}" : "#{o[:db]}"
60
+ end
61
+
62
+ ["create", "update", "delete"].each do |meth|
63
+ class_eval do
64
+ define_method("notify_via_redis_#{meth}".to_sym) do
65
+ db = self.dbnum
66
+ self.name.nil? ? name=@deleted_name : name=self.name
67
+ # Pulling out dbnum for now. Need to rethink it
68
+ #pub_category = "#{db}:noah.#{self.class.to_s}[#{name}].#{meth}"
69
+ pub_category = "#{self.patternize_me}"
70
+ Ohm.redis.publish(pub_category, self.to_hash.merge({"action" => meth, "pubcategory" => pub_category}).to_json)
71
+
72
+ # The following provides a post post-action hook. It allows a class to provide it's own handling after the fact
73
+ # good example is in [Noah::Ephemeral] where it's used to check for/clean up expired ephemeral nodes entries
74
+ self.send("#{meth}_hook".to_sym) unless self.protected_methods.member?("#{meth}_hook".to_sym) == false
75
+ end
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ require File.join(File.dirname(__FILE__), 'models','hosts')
82
+ require File.join(File.dirname(__FILE__), 'models','services')
83
+ require File.join(File.dirname(__FILE__), 'models','applications')
84
+ require File.join(File.dirname(__FILE__), 'models','configurations')
85
+ require File.join(File.dirname(__FILE__), 'models','watchers')
86
+ require File.join(File.dirname(__FILE__), 'models','ephemerals')
@@ -0,0 +1,41 @@
1
+ require File.join(File.dirname(__FILE__), 'configurations')
2
+ module Noah
3
+ class Application < Model
4
+ attribute :name
5
+ collection :configurations, Configuration
6
+
7
+ index :name
8
+
9
+ def validate
10
+ super
11
+ assert_present :name
12
+ end
13
+
14
+ def to_hash
15
+ arr = []
16
+ configurations.sort.each {|c| arr << c.to_hash}
17
+ super.merge(:name => name, :created_at => created_at, :updated_at => updated_at, :configurations => arr)
18
+ end
19
+
20
+ class << self
21
+ def find_or_create(opts = {})
22
+ begin
23
+ find(opts).first.nil? ? (app = create(opts)) : (app = find(opts).first)
24
+ if app.valid?
25
+ app.save
26
+ end
27
+ app
28
+ rescue Exception => e
29
+ e.message
30
+ end
31
+ end
32
+ end
33
+
34
+ end
35
+
36
+ class Applications
37
+ def self.all(options = {})
38
+ options.empty? ? Application.all.sort : Application.find(options).sort
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,49 @@
1
+ module Noah
2
+ class Configuration < Model
3
+
4
+ attribute :name
5
+ attribute :format
6
+ attribute :body
7
+ attribute :new_record
8
+ reference :application, Application
9
+
10
+ index :name
11
+ index :format
12
+ index :body
13
+
14
+ def validate
15
+ super
16
+ assert_present :name
17
+ assert_present :format
18
+ assert_present :body
19
+ assert_present :application_id
20
+ assert_unique [:name, :application_id]
21
+ end
22
+
23
+ def to_hash
24
+ Application[application_id].nil? ? app_name=nil : app_name=Application[application_id].name
25
+ super.merge(:name => name, :format => format, :body => body, :created_at => created_at, :updated_at => updated_at, :application => app_name)
26
+ end
27
+
28
+ class << self
29
+ def find_or_create(opts={})
30
+ begin
31
+ if find(opts).first.nil?
32
+ conf = create(opts)
33
+ else
34
+ conf = find(opts).first
35
+ end
36
+ rescue Exception => e
37
+ e.message
38
+ end
39
+ end
40
+ end
41
+
42
+ end
43
+
44
+ class Configurations
45
+ def self.all(options = {})
46
+ options.empty? ? Configuration.all.sort : Configuration.find(options).sort
47
+ end
48
+ end
49
+ end