noah 0.0.5 → 0.0.9

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