noah 0.0.5 → 0.0.9

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.gemtest +0 -0
  2. data/.gitignore +9 -0
  3. data/LICENSE +201 -0
  4. data/README.md +68 -212
  5. data/Rakefile +70 -41
  6. data/TODO.md +59 -0
  7. data/bin/noah +2 -1
  8. data/bin/noah-watcher.rb +93 -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 +32 -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 +5 -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/ephemeral_routes.rb +19 -0
  33. data/lib/noah/helpers.rb +12 -14
  34. data/lib/noah/host_routes.rb +69 -0
  35. data/lib/noah/models.rb +86 -5
  36. data/lib/noah/models/applications.rb +41 -0
  37. data/lib/noah/models/configurations.rb +49 -0
  38. data/lib/noah/models/ephemerals.rb +33 -0
  39. data/lib/noah/models/hosts.rb +56 -0
  40. data/lib/noah/models/services.rb +54 -0
  41. data/lib/noah/models/watchers.rb +54 -0
  42. data/lib/noah/passthrough.rb +11 -0
  43. data/lib/noah/service_routes.rb +71 -0
  44. data/lib/noah/validations.rb +1 -0
  45. data/lib/noah/validations/watcher_validations.rb +48 -0
  46. data/lib/noah/version.rb +1 -1
  47. data/lib/noah/watcher.rb +75 -0
  48. data/lib/noah/watcher_routes.rb +12 -0
  49. data/lib/vendor/em-hiredis/Gemfile +4 -0
  50. data/lib/vendor/em-hiredis/README.md +61 -0
  51. data/lib/vendor/em-hiredis/Rakefile +2 -0
  52. data/lib/vendor/em-hiredis/em-hiredis-0.0.1.gem +0 -0
  53. data/lib/vendor/em-hiredis/em-hiredis.gemspec +23 -0
  54. data/lib/vendor/em-hiredis/lib/em-hiredis.rb +22 -0
  55. data/lib/vendor/em-hiredis/lib/em-hiredis/client.rb +131 -0
  56. data/lib/vendor/em-hiredis/lib/em-hiredis/connection.rb +61 -0
  57. data/lib/vendor/em-hiredis/lib/em-hiredis/event_emitter.rb +29 -0
  58. data/lib/vendor/em-hiredis/lib/em-hiredis/version.rb +5 -0
  59. data/noah.gemspec +21 -17
  60. data/spec/application_spec.rb +30 -30
  61. data/spec/configuration_spec.rb +81 -14
  62. data/spec/ephemeral_spec.rb +52 -0
  63. data/spec/host_spec.rb +21 -21
  64. data/spec/noahapp_application_spec.rb +6 -6
  65. data/spec/noahapp_configuration_spec.rb +3 -3
  66. data/spec/noahapp_host_spec.rb +2 -2
  67. data/spec/noahapp_service_spec.rb +9 -9
  68. data/spec/noahapp_watcher_spec.rb +34 -0
  69. data/spec/service_spec.rb +27 -27
  70. data/spec/spec_helper.rb +13 -22
  71. data/spec/support/db/.keep +0 -0
  72. data/spec/support/test-redis.conf +8 -0
  73. data/spec/watcher_spec.rb +62 -0
  74. data/views/index.haml +21 -15
  75. metadata +124 -148
  76. data/Gemfile.lock +0 -85
  77. data/doc/coverage/index.html +0 -138
  78. data/doc/coverage/jquery-1.3.2.min.js +0 -19
  79. data/doc/coverage/jquery.tablesorter.min.js +0 -15
  80. data/doc/coverage/lib-helpers_rb.html +0 -393
  81. data/doc/coverage/lib-models_rb.html +0 -1449
  82. data/doc/coverage/noah_rb.html +0 -2019
  83. data/doc/coverage/print.css +0 -12
  84. data/doc/coverage/rcov.js +0 -42
  85. data/doc/coverage/screen.css +0 -270
  86. data/lib/noah/applications.rb +0 -46
  87. data/lib/noah/configurations.rb +0 -49
  88. data/lib/noah/hosts.rb +0 -54
  89. data/lib/noah/services.rb +0 -57
  90. data/lib/noah/watchers.rb +0 -18
@@ -0,0 +1,54 @@
1
+ require 'digest/sha1'
2
+ module Noah
3
+ class Watcher < Model #NYI
4
+ # Don't trust anything in here yet
5
+ # I'm still trying a few things
6
+ include WatcherValidations
7
+
8
+ attribute :pattern
9
+ attribute :endpoint
10
+
11
+ index :pattern
12
+ index :endpoint
13
+
14
+ def validate
15
+ super
16
+ assert_present :endpoint
17
+ assert_present :pattern
18
+ assert_unique [:endpoint, :pattern]
19
+ assert_not_superset
20
+ assert_not_subset
21
+ end
22
+
23
+ def name
24
+ @name = Base64.encode64("#{pattern}|#{endpoint}").gsub("\n","")
25
+ end
26
+
27
+ def to_hash
28
+ h = {:pattern => pattern, :name => name, :endpoint => endpoint, :created_at => created_at, :updated_at => updated_at}
29
+ super.merge(h)
30
+ end
31
+
32
+ def self.watch_list
33
+ arr = []
34
+ watches = self.all.sort_by(:pattern)
35
+ watches.each {|w| arr << w.name}
36
+ arr
37
+ end
38
+
39
+ private
40
+ # Not sure about these next two.
41
+ # Could get around patterns changing due to namespace changes
42
+ def path_to_pattern
43
+ end
44
+
45
+ def pattern_to_path
46
+ end
47
+ end
48
+
49
+ class Watchers
50
+ def self.all(options = {})
51
+ options.empty? ? Watcher.all.sort : Watcher.find(options).sort
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,11 @@
1
+ module Noah
2
+ module Passthrough
3
+ def passthrough(*methods)
4
+ methods.each do |method|
5
+ raise ArgumentError if ! method.is_a?(Symbol)
6
+ meth = method.to_s
7
+ self.class_eval("def #{meth}(*args); self.class.#{meth}(*args); end")
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,71 @@
1
+ class Noah::App
2
+ # Service URIs
3
+
4
+ # get named {Service} for named {Host}
5
+ get '/s/:servicename/:hostname/?' do |servicename, hostname|
6
+ hs = host_service(hostname, servicename)
7
+ if hs.nil?
8
+ halt 404
9
+ else
10
+ hs.to_json
11
+ end
12
+ end
13
+
14
+ get '/s/:servicename/?' do |servicename|
15
+ s = services(:name => servicename)
16
+ s.map {|x| x.to_hash}
17
+ if s.empty?
18
+ halt 404
19
+ else
20
+ s.to_json
21
+ end
22
+ end
23
+
24
+ get '/s/?' do
25
+ if services.empty?
26
+ halt 404
27
+ else
28
+ services.map {|s| s.to_hash}
29
+ services.to_json
30
+ end
31
+ end
32
+
33
+ put '/s/:servicename/watch' do |servicename|
34
+ required_params = ["endpoint"]
35
+ data = JSON.parse(request.body.read)
36
+ (data.keys.sort == required_params.sort) ? (s = Noah::Service.find(:name => servicename).first) : (raise "Missing Parameters")
37
+ s.nil? ? (halt 404) : (w = s.watch!(:endpoint => data['endpoint']))
38
+ w.to_json
39
+ end
40
+
41
+ put '/s/:servicename/?' do |servicename|
42
+ required_params = ["status", "host", "name"]
43
+ data = JSON.parse(request.body.read)
44
+ if data.keys.sort == required_params.sort
45
+ h = Noah::Host.find(:name => data['host']).first || (raise "Invalid Host")
46
+ service = Noah::Service.find_or_create(:name => servicename, :status => data['status'], :host => h)
47
+ if service.valid?
48
+ action = service.is_new? ? "create" : "update"
49
+ service.save
50
+ r = {"action" => action, "result" => "success", "id" => service.id, "host" => h.name, "name" => service.name}
51
+ r.to_json
52
+ else
53
+ raise "#{service.errors}"
54
+ end
55
+ else
56
+ raise "Missing Parameters"
57
+ end
58
+ end
59
+
60
+ delete '/s/:servicename/:hostname/?' do |servicename, hostname|
61
+ host = Noah::Host.find(:name => hostname).first || (halt 404)
62
+ service = Noah::Service.find(:name => servicename, :host_id => host.id).first || (halt 404)
63
+ if host && service
64
+ service.delete
65
+ r = {"action" => "delete", "result" => "success", "id" => service.id, "host" => host.name, "service" => servicename}
66
+ r.to_json
67
+ else
68
+ halt 404
69
+ end
70
+ end
71
+ end
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), 'validations','watcher_validations')
@@ -0,0 +1,48 @@
1
+ module Noah
2
+ module WatcherValidations
3
+
4
+ def assert_not_subset(error = [:pattern, :already_provided])
5
+ self.instance_of?(Noah::Watcher) ? (assert endpoint_covered?, error) : (assert false, "Validation not applicable")
6
+ end
7
+
8
+ def assert_not_superset(error = [:pattern, :replaces_existing])
9
+ self.instance_of?(Noah::Watcher) ? (assert endpoint_overrides?, error) : (assert false, "Validation not applicable")
10
+ end
11
+
12
+ def assert_valid_watch(error = [:pattern, :invalid_format])
13
+ self.instance_of?(Noah::Watcher) ? (assert pattern_valid?, error) : (assert false, "Validation not applicable")
14
+ end
15
+
16
+ private
17
+ def endpoint_covered?
18
+ watches = Watcher.all.find(:endpoint => self.endpoint).sort
19
+ watches.each do |w|
20
+ if (w.pattern.size < self.pattern.size) && self.pattern.match(/^#{w.pattern}/)
21
+ return false
22
+ end
23
+ end
24
+ rescue ArgumentError
25
+ return false
26
+ end
27
+
28
+ def endpoint_overrides?
29
+ watches = Watcher.all.find(:endpoint => self.endpoint).sort
30
+ watches.each do |w|
31
+ if (w.pattern.size > self.pattern.size) && w.pattern.match(/^#{self.pattern}/)
32
+ return false
33
+ end
34
+ end
35
+ rescue ArgumentError
36
+ return false
37
+ end
38
+
39
+ def pattern_valid?
40
+ unless self.pattern.match(/^\/\/noah\/.*\/$/)
41
+ return false
42
+ end
43
+ rescue ArgumentError
44
+ return false
45
+ end
46
+
47
+ end
48
+ end
data/lib/noah/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Noah
2
- VERSION = "0.0.5"
2
+ VERSION = "0.0.9"
3
3
  end
@@ -0,0 +1,75 @@
1
+ require 'eventmachine'
2
+ require 'uri'
3
+ require 'logger'
4
+
5
+ @log = Logger.new(STDOUT)
6
+ @log.level = Logger::DEBUG
7
+
8
+ require File.join(File.dirname(__FILE__), 'passthrough')
9
+ require File.join(File.dirname(__FILE__), '..','vendor','em-hiredis','lib','em-hiredis')
10
+
11
+ module Noah
12
+
13
+ class Watcher
14
+ extend Passthrough
15
+
16
+ passthrough :redis_host, :pattern, :destination, :run!, :run_watcher
17
+
18
+ attr_accessor :my_pattern, :my_destination, :my_redis
19
+
20
+ def self.watch(&blk)
21
+ watcher = Noah::Watcher.new
22
+ watcher.instance_eval(&blk) if block_given?
23
+ watcher
24
+ end
25
+
26
+ def initialize
27
+ @my_redis ||= ENV['REDIS_URL']
28
+ @my_pattern ||= '//noah'
29
+ end
30
+
31
+ def self.redis_host(host)
32
+ @my_redis = host
33
+ end
34
+
35
+ def self.pattern(pattern)
36
+ @my_pattern = pattern
37
+ end
38
+
39
+ def self.destination(destination)
40
+ @my_destination = destination
41
+ end
42
+
43
+ def self.run!
44
+ @my_destination.nil? ? (raise ArgumentError) : run_watcher(@my_destination)
45
+ end
46
+
47
+ private
48
+ def self.run_watcher(dest)
49
+ log = Logger.new(STDOUT)
50
+ log.level = Logger::INFO
51
+ log.debug "#{dest.inspect}"
52
+ redis_url = URI.parse(@my_redis)
53
+ db = redis_url.path.gsub(/\//,'')
54
+
55
+ EventMachine.run do
56
+ trap("TERM") { log.info "Killed"; EventMachine.stop }
57
+ trap("INT") { log.info "Interrupted"; EventMachine.stop }
58
+ channel = EventMachine::Channel.new
59
+ r = EventMachine::Hiredis::Client.connect(redis_url.host, redis_url.port)
60
+ # Pulling out dbnum for now. Need to rethink it
61
+ #log.info "Binding to pattern #{db}:#{@my_pattern}"
62
+ log.info "Binding to pattern #{@my_pattern}"
63
+ r.psubscribe("#{@my_pattern}*")
64
+ r.on(:pmessage) do |pattern, event, message|
65
+ log.debug "Got message"
66
+ channel.push "#{message}"
67
+ end
68
+ r.errback { log.info "Something went tango-uniform" }
69
+
70
+ sub = channel.subscribe {|msg| log.info "Calling message handler"; dest.call(msg)}
71
+ end
72
+ end
73
+ end
74
+
75
+ end
@@ -0,0 +1,12 @@
1
+ class Noah::App
2
+
3
+ get '/w/?' do
4
+ w = Noah::Watcher.all.sort_by(:pattern)
5
+ if w.size == 0
6
+ halt 404
7
+ else
8
+ w.to_json
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in em-hiredis.gemspec
4
+ gemspec
@@ -0,0 +1,61 @@
1
+ Getting started
2
+ ===============
3
+
4
+ Connect to redis
5
+
6
+ redis_client = EM::Hiredis.connect
7
+
8
+ The client is a deferrable which succeeds when the underlying connection is established so you can bind to this. This isn't necessary however - any commands sent before the connection is established (or while reconnecting) will be sent to redis on connect.
9
+
10
+ redis_client.callback { puts "Redis now connected" }
11
+
12
+ All redis commands are available without any remapping of names
13
+
14
+ redis.set('foo', 'bar').callback {
15
+ redis.get('foo').callback { |value|
16
+ p [:returned, value]
17
+ }
18
+ }
19
+
20
+ As a shortcut, if you're only interested in binding to the success case you can simply provide a block to any command
21
+
22
+ redis.get('foo') { |value|
23
+ p [:returned, value]
24
+ }
25
+
26
+ Handling failure
27
+ ----------------
28
+
29
+ All commands return a deferrable. In the case that redis replies with an error (for example you called a hash operation against a set), or in the case that the redis connection is broken before the command returns, the deferrable will fail. If you care about the failure case you should bind to the errback - for example:
30
+
31
+ redis.sadd('aset', 'member').callback {
32
+ response_deferrable = redis.hget('aset', 'member')
33
+ response_deferrable.errback { |e|
34
+ p e # => #<RuntimeError: ERR Operation against a key holding the wrong kind of value>
35
+ }
36
+ }
37
+
38
+ Pubsub
39
+ ------
40
+
41
+ This example should explain things. Once a redis connection is in a pubsub state, you must make sure you only send pubsub commands.
42
+
43
+ redis = EM::Hiredis::Client.connect
44
+ subscriber = EM::Hiredis::Client.connect
45
+
46
+ subscriber.subscribe('bar.0')
47
+ subscriber.psubscribe('bar.*')
48
+
49
+ subscriber.on(:message) { |channel, message|
50
+ p [:message, channel, message]
51
+ }
52
+
53
+ subscriber.on(:pmessage) { |key, channel, message|
54
+ p [:pmessage, key, channel, message]
55
+ }
56
+
57
+ EM.add_periodic_timer(1) {
58
+ redis.publish("bar.#{rand(2)}", "hello").errback { |e|
59
+ p [:publisherror, e]
60
+ }
61
+ }
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "em-hiredis/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "em-hiredis"
7
+ s.version = EM::Hiredis::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Martyn Loughran"]
10
+ s.email = ["me@mloughran.com"]
11
+ s.homepage = "http://github.com/mloughran/em-hiredis"
12
+ s.summary = %q{Eventmachine redis client}
13
+ s.description = %q{Eventmachine redis client using hiredis native parser}
14
+
15
+ s.add_dependency 'hiredis', '~> 0.2.0'
16
+
17
+ s.rubyforge_project = "em-hiredis"
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
@@ -0,0 +1,22 @@
1
+ require 'eventmachine'
2
+
3
+ module EM
4
+ module Hiredis
5
+ class << self
6
+ attr_writer :logger
7
+
8
+ def logger
9
+ @logger ||= begin
10
+ require 'logger'
11
+ log = Logger.new(STDOUT)
12
+ log.level = Logger::WARN
13
+ log
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
19
+
20
+ require 'em-hiredis/event_emitter'
21
+ require 'em-hiredis/connection'
22
+ require 'em-hiredis/client'
@@ -0,0 +1,131 @@
1
+ module EM::Hiredis
2
+ class Client
3
+ PUBSUB_MESSAGES = %w{message pmessage}.freeze
4
+
5
+ include EM::Hiredis::EventEmitter
6
+ include EM::Deferrable
7
+
8
+ def self.connect(host = 'localhost', port = 6379)
9
+ new(host, port)
10
+ end
11
+
12
+ def initialize(host, port)
13
+ @host, @port = host, port
14
+ @subs, @psubs = [], []
15
+ @defs = []
16
+ @connection = EM.connect(host, port, Connection, host, port)
17
+
18
+ @connection.on(:closed) {
19
+ if @connected
20
+ @defs.each { |d| d.fail("Redis disconnected") }
21
+ @defs = []
22
+ @deferred_status = nil
23
+ @connected = false
24
+ @reconnecting = true
25
+ reconnect
26
+ else
27
+ EM.add_timer(1) { reconnect }
28
+ end
29
+ }
30
+
31
+ @connection.on(:connected) {
32
+ @connected = true
33
+ select(@db) if @db
34
+ @subs.each { |s| method_missing(:subscribe, s) }
35
+ @psubs.each { |s| method_missing(:psubscribe, s) }
36
+ succeed
37
+
38
+ if @reconnecting
39
+ @reconnecting = false
40
+ emit(:reconnected)
41
+ end
42
+ }
43
+
44
+ @connection.on(:message) { |reply|
45
+ if RuntimeError === reply
46
+ raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
47
+ deferred = @defs.shift
48
+ deferred.fail(reply) if deferred
49
+ else
50
+ if reply && PUBSUB_MESSAGES.include?(reply[0]) # reply can be nil
51
+ kind, subscription, d1, d2 = *reply
52
+
53
+ case kind.to_sym
54
+ when :message
55
+ emit(:message, subscription, d1)
56
+ when :pmessage
57
+ emit(:pmessage, subscription, d1, d2)
58
+ end
59
+ else
60
+ raise "Replies out of sync: #{reply.inspect}" if @defs.empty?
61
+ deferred = @defs.shift
62
+ deferred.succeed(reply) if deferred
63
+ end
64
+ end
65
+ }
66
+
67
+ @connected = false
68
+ @reconnecting = false
69
+ end
70
+
71
+ # Indicates that commands have been sent to redis but a reply has not yet
72
+ # been received.
73
+ #
74
+ # This can be useful for example to avoid stopping the
75
+ # eventmachine reactor while there are outstanding commands
76
+ #
77
+ def pending_commands?
78
+ @connected && @defs.size > 0
79
+ end
80
+
81
+ def subscribe(channel)
82
+ @subs << channel
83
+ method_missing(:subscribe, channel)
84
+ end
85
+
86
+ def unsubscribe(channel)
87
+ @subs.delete(channel)
88
+ method_missing(:unsubscribe, channel)
89
+ end
90
+
91
+ def psubscribe(channel)
92
+ @psubs << channel
93
+ method_missing(:psubscribe, channel)
94
+ end
95
+
96
+ def punsubscribe(channel)
97
+ @psubs.delete(channel)
98
+ method_missing(:punsubscribe, channel)
99
+ end
100
+
101
+ def select(db)
102
+ @db = db
103
+ method_missing(:select, db)
104
+ end
105
+
106
+ def method_missing(sym, *args)
107
+ deferred = EM::DefaultDeferrable.new
108
+ # Shortcut for defining the callback case with just a block
109
+ deferred.callback { |result| yield(result) } if block_given?
110
+
111
+ if @connected
112
+ @connection.send_command(sym, *args)
113
+ @defs.push(deferred)
114
+ else
115
+ callback {
116
+ @connection.send_command(sym, *args)
117
+ @defs.push(deferred)
118
+ }
119
+ end
120
+
121
+ return deferred
122
+ end
123
+
124
+ private
125
+
126
+ def reconnect
127
+ EM::Hiredis.logger.debug("Trying to reconnect to Redis")
128
+ @connection.reconnect @host, @port
129
+ end
130
+ end
131
+ end