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
data/Rakefile CHANGED
@@ -1,48 +1,74 @@
1
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "lib")))
1
2
  require 'bundler'
2
- begin
3
- Bundler.setup(:default, :development)
4
- rescue Bundler::BundlerError => e
5
- $stderr.puts e.message
6
- $stderr.puts "Run `bundle install` to install missing gems"
7
- exit e.status_code
8
- end
9
- require 'rake'
10
-
11
3
  require 'rspec/core'
12
4
  require 'rspec/core/rake_task'
13
5
 
6
+ REDIS_DIR = File.expand_path(File.join("..", "spec", "support"), __FILE__)
7
+ REDIS_CNF = File.join(REDIS_DIR, "test-redis.conf")
8
+
14
9
  Bundler::GemHelper.install_tasks
10
+ RSpec::Core::RakeTask.new(:spec) do |spec|
11
+ spec.pattern = FileList['spec/**/*_spec.rb']
12
+ end
13
+
14
+
15
+ task :default => :run
16
+ task :test => [:start, :spec, :stop]
17
+
18
+ desc "Run tests and manage server start/stop"
19
+ task :run => [:start, :spec, :stop]
20
+
21
+ desc "Start the Redis server"
22
+ task :start do
23
+ puts "Starting redis-server"
24
+ system "redis-server #{REDIS_CNF}"
25
+ end
26
+
27
+ desc "Stop the Redis server"
28
+ task :stop do
29
+ puts "Killing redis"
30
+ system "killall -TERM redis-server"
31
+ end
32
+
33
+ namespace :coverage do
34
+ desc "Delete aggregate coverage data."
35
+ task(:clean) { rm_f "coverage.data" }
36
+ end
37
+ desc "Run Rcov code coverage analysis"
38
+ RSpec::Core::RakeTask.new(:coverage) do |t|
39
+ t.rcov = true
40
+ t.verbose = true
41
+ t.rcov_opts = %q[--aggregate coverage.data --sort coverage --text-report --exclude "config,.bundle/*,gems/*,spec/*" -o doc/coverage -Ilib -i "noah.rb"]
42
+ end
15
43
 
16
44
  desc "Populate database with sample dataset"
17
45
  task :sample, :redis_url do |t, args|
18
- require 'ohm'
19
- begin
20
- require 'yajl'
21
- rescue LoadError
22
- require 'json'
23
- end
24
- require File.join(File.dirname(__FILE__), 'lib','noah')
46
+ require 'noah'
25
47
 
26
-
27
48
  Ohm::connect(:url => args.redis_url)
28
49
  Ohm::redis.flushdb
50
+ puts "Creating watchers..."
51
+ Noah::Watcher.create :endpoint => "http://localhost:3000/webhook", :pattern => "//noah/application"
52
+ Noah::Watcher.create :endpoint => "http://localhost:3001/webhook", :pattern => "//noah/configuration"
53
+ Noah::Watcher.create :endpoint => "http://localhost:3002/webhook", :pattern => "//noah/host"
54
+ Noah::Watcher.create :endpoint => "http://localhost:3003/webhook", :pattern => "//noah/service"
29
55
  puts "Creating Host entry for 'localhost'"
30
- h = Host.create(:name => 'localhost', :status => "up")
56
+ h = Noah::Host.create(:name => 'localhost', :status => "up")
31
57
  if h.save
32
58
  %w[redis noah].each do |service|
33
59
  puts "Create Service entry for #{service}"
34
- s = Service.create(:name => service, :status => "up", :host => h)
60
+ s = Noah::Service.create(:name => service, :status => "up", :host => h)
35
61
  h.services << s
36
62
  end
37
63
  end
38
64
 
39
65
  puts "Creating Application entry for 'noah'"
40
- a = Application.create(:name => 'noah')
66
+ a = Noah::Application.create(:name => 'noah')
41
67
  if a.save
42
68
  puts "Creating Configuration entry for 'noah'"
43
- cr = Configuration.create(:name => 'redis', :format => 'string', :body => 'redis://127.0.0.1:6379/0', :application => a)
44
- ch = Configuration.create(:name => 'host', :format => 'string', :body => 'localhost', :application => a)
45
- cp = Configuration.create(:name => 'port', :format => 'string', :body => '9292', :application => a)
69
+ cr = Noah::Configuration.create(:name => 'redis', :format => 'string', :body => 'redis://127.0.0.1:6379/0', :application => a)
70
+ ch = Noah::Configuration.create(:name => 'host', :format => 'string', :body => 'localhost', :application => a)
71
+ cp = Noah::Configuration.create(:name => 'port', :format => 'string', :body => '9292', :application => a)
46
72
  %w[cr ch cp].each do |c|
47
73
  a.configurations << eval(c)
48
74
  end
@@ -50,10 +76,10 @@ task :sample, :redis_url do |t, args|
50
76
 
51
77
  puts "Creating sample entries - Host and Service"
52
78
  %w[host1.domain.com host2.domain.com host3.domain.com].each do |host|
53
- h = Host.create(:name => host, :status => "up")
79
+ h = Noah::Host.create(:name => host, :status => "up")
54
80
  if h.save
55
81
  %w[http https smtp mysql].each do |service|
56
- s = Service.create(:name => service, :status => "pending", :host => h)
82
+ s = Noah::Service.create(:name => service, :status => "pending", :host => h)
57
83
  h.services << s
58
84
  end
59
85
  end
@@ -74,32 +100,35 @@ EOY
74
100
  }
75
101
  EOJ
76
102
 
77
- a1 = Application.create(:name => 'myrailsapp1')
103
+ a1 = Noah::Application.create(:name => 'myrailsapp1')
78
104
  if a1.save
79
- c1 = Configuration.create(:name => 'database.yml', :format => 'yaml', :body => my_yaml, :application => a1)
105
+ c1 = Noah::Configuration.create(:name => 'database.yml', :format => 'yaml', :body => my_yaml, :application => a1)
80
106
  a1.configurations << c1
81
107
  end
82
108
 
83
- a2 = Application.create(:name => 'myrestapp1')
109
+ a2 = Noah::Application.create(:name => 'myrestapp1')
84
110
  if a2.save
85
- c2 = Configuration.create(:name => 'config.json', :format => 'json', :body => my_json, :application => a2)
111
+ c2 = Noah::Configuration.create(:name => 'config.json', :format => 'json', :body => my_json, :application => a2)
86
112
  a2.configurations << c2
87
113
  end
88
114
  puts "Sample data populated!"
89
115
  end
90
-
91
116
 
92
- RSpec::Core::RakeTask.new(:spec) do |spec|
93
- spec.pattern = FileList['spec/**/*_spec.rb']
117
+ begin
118
+ require 'yard'
119
+ require 'yard/sinatra'
120
+ desc "Generate documentation"
121
+ YARD::Rake::YardocTask.new do |t|
122
+ t.files = ['lib/**/*.rb'] # optional
123
+ t.options = ['--title', "Noah #{Noah::VERSION} Documentation"]
124
+ t.options += ['--plugin', "yard-sinatra"]
125
+ t.options += ['--protected', '--private'] # optional
126
+ end
127
+ rescue LoadError
128
+ "You need YARD installed to generate docs"
94
129
  end
95
130
 
96
- namespace :coverage do
97
- desc "Delete aggregate coverage data."
98
- task(:clean) { rm_f "coverage.data" }
99
- end
100
- desc "Run Rcov code coverage analysis"
101
- RSpec::Core::RakeTask.new(:coverage) do |t|
102
- t.rcov = true
103
- t.verbose = true
104
- t.rcov_opts = %q[--aggregate coverage.data --sort coverage --text-report --exclude "config,.bundle/*,gems/*,spec/*" -o doc/coverage -Ilib -i "noah.rb"]
131
+ desc "Demo environment"
132
+ task :start_demo do
133
+ puts "Soon, young padawan"
105
134
  end
data/TODO.md ADDED
@@ -0,0 +1,59 @@
1
+ # Brain dump of stuff
2
+ * Documentation
3
+
4
+ Need to finish documenting everything for YARD to pick up.
5
+
6
+ * Stabilize API
7
+
8
+ I'm seriously considering moving to a single endpoint with JSON request bodies. Still up in the air. I'm not totally sold on the current layout.
9
+
10
+ * Write my own CLI class
11
+
12
+ I think I might be bumping into some issues related to Vegas as well.
13
+
14
+ * Watchers
15
+
16
+ This is going to be a fun task. I'm serious.
17
+
18
+ * Ephemeral nodes
19
+
20
+ Not sure how I want to implement that. Not too keen on storing them as in-memory hashes. Maybe a LRU in Redis?
21
+
22
+ * Examples
23
+
24
+ I need to make some example apps to really demonstrate what I'm trying to accomplish.
25
+
26
+ * Clean up deps
27
+
28
+ Feels like I'm relying too much on external libraries.
29
+
30
+ * Benchmark
31
+
32
+ Need some client libs for this
33
+
34
+ # Done TODOS
35
+ * Github pages
36
+ - see [[http://lusis.github.com/Noah/]]
37
+
38
+ * Bundle a war
39
+ - see [[https://github.com/downloads/lusis/Noah/noah.war]]
40
+
41
+ * Examples
42
+ - Sort of done. Demo app is up! [[http://noah-demo.heroku.com/]]. Also see `examples` directory.
43
+
44
+ * Consider ditching Sinatra::Namespace
45
+ - Done. It was also causing issues with YARD generation. @rkh confirmed known issue via twitter.
46
+
47
+ * Namespace models
48
+ - Done.
49
+
50
+ * Watchers
51
+ - Partially done. Framework is in place to create a custom Watcher by hooking directly into Redis. Need to expand that to "official" watchers
52
+
53
+
54
+ ## Watcher specific stuff
55
+ * Implement a watcher endpoint
56
+ * Implement webhooks
57
+ * Implement AMQP
58
+ * Implement REST
59
+ * Implement JMX on JRuby
data/bin/noah CHANGED
@@ -1,8 +1,9 @@
1
1
  #!/usr/bin/env ruby
2
2
  $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
3
+ require 'rubygems'
3
4
  require 'noah'
4
5
  require 'vegas'
5
6
 
6
7
  Vegas::Runner.new(Noah::App, 'noah') do |runner, opts, app|
7
- opts.on("-r", "--redis URL", "redis url to connect to (default: redis://localhost:6379/0)") {|r| ENV["REDIS_URL"] = r }
8
+ opts.on("-r", "--redis URL", "redis url to connect to (default: redis://localhost:6379/0)") {|r| ENV["REDIS_URL"] = r; Noah::App.set :redis_url, r }
8
9
  end
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env ruby
2
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
3
+ require 'rubygems'
4
+ require 'rbtrace'
5
+ require 'logger'
6
+ require 'optparse'
7
+ require 'em-hiredis'
8
+ require 'eventmachine'
9
+ require 'em-http-request'
10
+ require 'thin'
11
+ require 'noah'
12
+ require 'json'
13
+
14
+ LOGGER = Logger.new(STDOUT)
15
+
16
+ class EventMachine::NoahAgent
17
+ include EM::Deferrable
18
+
19
+ @@watchers = Noah::Watcher.watch_list
20
+
21
+ def initialize
22
+ @logger = LOGGER
23
+ @logger.debug("Initializing with #{@@watchers.size} registered watches")
24
+ if EventMachine.reactor_running?
25
+ @worker = EM.spawn {|event, message, watch_list|
26
+ logger = LOGGER
27
+ logger.debug("Worker initiated")
28
+ logger.info("got event on http worker: #{event}")
29
+ logger.info("got message on http worker: #{message}")
30
+ matches = watch_list.find_all{|w| event =~ /^#{Base64.decode64(w)}/}
31
+ logger.debug("Found #{matches.size} matches for #{event}")
32
+ EM::Iterator.new(matches).each do |watch, iter|
33
+ p, ep = Base64.decode64(watch).split("|")
34
+ logger.info("Sending message to: #{ep} for pattern: #{p}")
35
+ http = EM::HttpRequest.new(ep, :connection_timeout => 2, :inactivity_timeout => 4).post :body => message
36
+ http.callback {
37
+ LOGGER.debug("Message posted to #{ep} successfully")
38
+ #iter.next
39
+ }
40
+ http.errback {
41
+ LOGGER.debug("Something went wrong")
42
+ #iter.net
43
+ }
44
+ iter.next
45
+ end
46
+ }
47
+ else
48
+ logger.fatal("Must be inside a reactor!")
49
+ end
50
+ end
51
+
52
+ def watchers
53
+ @@watchers.size
54
+ end
55
+
56
+ def reread_watchers
57
+ @logger.debug("Found new watches")
58
+ @logger.debug("Current watch count: #{@@watchers.size}")
59
+ @@watchers = Noah::Watcher.watch_list
60
+ @logger.debug("New watch count: #{@@watchers.size}")
61
+ #@logger.debug(@@watchers)
62
+ end
63
+
64
+ def broker(msg)
65
+ # This is just for testing for now
66
+ @logger.warn(msg)
67
+ e,m = msg.split("|")
68
+ be = Base64.encode64(e).gsub("\n","")
69
+ @logger.info("Encoded event: #{be}")
70
+ @worker.notify e, m, @@watchers.clone
71
+ end
72
+ end
73
+
74
+ EventMachine.run do
75
+ logger = LOGGER
76
+ noah = EventMachine::NoahAgent.new
77
+ # Passing messages...like a boss
78
+ master_channel = EventMachine::Channel.new
79
+
80
+ r = EventMachine::Hiredis::Client.connect
81
+ logger.debug("Starting up")
82
+ r.psubscribe("//noah/*")
83
+ r.on(:pmessage) do |pattern, event, message|
84
+ noah.reread_watchers if event =~ /^\/\/noah\/watcher\/.*/
85
+ master_channel.push "#{event}|#{message}"
86
+ logger.debug("Saw[#{event}]")
87
+ end
88
+
89
+ sub = master_channel.subscribe {|msg|
90
+ # We short circuit if we have no watchers
91
+ noah.broker(msg) unless noah.watchers == 0
92
+ }
93
+ end
data/config.ru CHANGED
@@ -1,3 +1,6 @@
1
- require File.join(File.dirname(__FILE__), 'lib','noah')
2
- ENV['REDIS_URL'] = "redis://localhost:6379/0"
3
- run Noah::App
1
+ require 'rubygems'
2
+ require File.join('.', 'lib','noah')
3
+ ## Uncomment the following to hardcode a redis url
4
+ ENV['REDIS_URL'] = "redis://localhost:6379/1"
5
+ noah = Noah::App.new
6
+ run noah
data/config/warble.rb ADDED
@@ -0,0 +1,18 @@
1
+ # Disable automatic framework detection by uncommenting/setting to false
2
+ # Warbler.framework_detection = false
3
+
4
+ # Warbler web application assembly configuration file
5
+ Warbler::Config.new do |config|
6
+ #config.features = %w(gemjar)
7
+ config.dirs = %w(config lib views)
8
+ config.includes = FileList["config.ru"]
9
+ config.excludes = FileList["noah.gemspec", "Gemfile", "Gemfile.lock"]
10
+ config.bundler = false
11
+ config.gems += ["json", "ohm", "ohm-contrib", "sinatra", "sinatra-namespace", "haml"]
12
+ config.gem_excludes = [/^(test|spec)\//]
13
+ config.public_html = FileList["views/**/*"]
14
+ config.webxml.booter = :rack
15
+ #config.webxml.rackup.path = 'WEB-INF/config.ru'
16
+ #config.webxml.rackup = %{require './lib/noah'; run Noah::App}
17
+ # config.webxml.rackup = require 'cgi' && CGI::escapeHTML(File.read("config.ru"))
18
+ end
@@ -0,0 +1,116 @@
1
+ # Examples
2
+ The following is a list of notes regarding the examples in this directory.
3
+
4
+ ## General Requirements
5
+
6
+ You'll need a few additional gems
7
+
8
+ * [em-hiredis](https://github.com/mloughran/em-hiredis)
9
+ You'll have to compile/install from source. Sorry. Should pull in the `hiredis` native ext.
10
+ * [em-http-request](https://github.com/igrigorik/em-http-request)
11
+ Available via rubygems
12
+ * [em-websocket](https://github.com/igrigorik/em-websocket)
13
+ Available via rubygems
14
+
15
+ ## custom-watcher.rb
16
+ This is an idea I'm tossing around for allowing easy custom watchers to be written.
17
+ Essentially the idea is that you tap into the Redis subscription with a defined pattern and a destination.
18
+
19
+ ### Example
20
+
21
+ require './watcher-idea.rb'
22
+
23
+ Noah::Watcher.watch do
24
+ pattern "//noah/configuration/*"
25
+ destination Proc.new {|x| something_with(x)}
26
+ run!
27
+ end
28
+
29
+ ## logger.rb
30
+ An example using logger as a watcher. Pretty straighforward.
31
+
32
+
33
+ ## httpclient.rb/httpclient-server.rb
34
+ This is an example of how the Webhook system would work
35
+
36
+ ### Running
37
+
38
+ To get the maximum effect, start with a clean Redis database
39
+
40
+ * Start the webhook reciever
41
+
42
+ noah/examples$ ruby httpclient-server.rb
43
+ == Sinatra/1.1.2 has taken the stage on 4567 for development with backup from Thin
44
+ >> Thin web server (v1.2.7 codename No Hup)
45
+ >> Maximum connections set to 1024
46
+ >> Listening on 0.0.0.0:4567, CTRL+C to stop
47
+
48
+ * Start the webhook publisher
49
+
50
+ noah/examples$ ruby httpclient.rb
51
+
52
+ * Run the rake sample script
53
+
54
+ In the publisher window, you should see some messages like so:
55
+
56
+ Got message for noah.Host[localhost].create
57
+ Got message for noah.Host[localhost].save
58
+ Got message for noah.Host[localhost].save
59
+ Got message for noah.Host[localhost].update
60
+
61
+ In the server window, you should see the following:
62
+
63
+ "{\"id\":\"1\",\"name\":\"localhost\",\"status\":\"up\",\"created_at\":\"2011-02-15 05:19:05 UTC\",\"updated_at\":\"2011-02-15 05:19:05 UTC\",\"services\":[]}"
64
+ 127.0.0.1 - - [15/Feb/2011 00:19:05] "POST /webhook HTTP/1.1" 200 135 0.0024
65
+ "{\"id\":\"1\",\"name\":\"localhost\",\"status\":\"up\",\"created_at\":\"2011-02-15 05:19:05 UTC\",\"updated_at\":\"2011-02-15 05:19:05 UTC\",\"services\":[]}"
66
+ 127.0.0.1 - - [15/Feb/2011 00:19:05] "POST /webhook HTTP/1.1" 200 135 0.0004
67
+ "{\"id\":\"1\",\"name\":\"localhost\",\"status\":\"up\",\"created_at\":\"2011-02-15 05:19:05 UTC\",\"updated_at\":\"2011-02-15 05:19:05 UTC\",\"services\":[]}"
68
+
69
+
70
+ ## websocket.rb
71
+ This is an example of using Websockets, EventMachine and Redis PubSub to provide a "status" console of sorts.
72
+
73
+ ### Running
74
+
75
+ To get the maximum effect, start with a clean Redis database.
76
+
77
+ * Start the server:
78
+
79
+ ~/development/noah/examples$ ./websocket.rb
80
+ >> Thin web server (v1.2.7 codename No Hup)
81
+ >> Maximum connections set to 1024
82
+ >> Listening on 0.0.0.0:3000, CTRL+C to stop
83
+
84
+ You should be able to load up the "normal" Noah sample page on [http://localhost:3000].
85
+
86
+ * Load the "websocket" file
87
+
88
+ In another browser window, open the `websocket.html` file.
89
+
90
+ * Send a message
91
+
92
+ From another terminal window send the following:
93
+
94
+ curl -X PUT -d '{"name":"testhost2","status":"down"}' http://localhost:3000/h/testhost2
95
+
96
+ You should see the message come across in the browser window like so:
97
+
98
+ connected...
99
+
100
+ 2 connected and waiting....
101
+
102
+ (noah.Host[testhost2].create) {"id":"1","name":"testhost2","status":"down","created_at":"2011-02-14 20:58:04 UTC","updated_at":"2011-02-14 20:58:04 UTC","services":[]}
103
+
104
+ (noah.Host[testhost2].save) {"id":"1","name":"testhost2","status":"down","created_at":"2011-02-14 20:58:04 UTC","updated_at":"2011-02-14 20:58:04 UTC","services":[]}
105
+
106
+ (noah.Host[testhost2].save) {"id":"1","name":"testhost2","status":"down","created_at":"2011-02-14 20:58:04 UTC","updated_at":"2011-02-14 20:58:04 UTC","services":[]}
107
+
108
+ (noah.Host[testhost2].update) {"id":"1","name":"testhost2","status":"down","created_at":"2011-02-14 20:58:04 UTC","updated_at":"2011-02-14 20:58:04 UTC","services":[]}
109
+
110
+ You can see the Watcher pattern in the parenthesis and then the JSON message body.
111
+
112
+ For fun, refresh the page to clear it and then run the sample data population rake task.
113
+
114
+ ### Known issues
115
+ When I started working on the Watcher stuff, I realized that I'm sending A LOT of extranous messages. These are mostly the result of the way I'm creating new objects with Ohm (i.e. via `.create`).
116
+ I'll be cleaning that up and trying to get down to a single message per operation.