-
Notifications
You must be signed in to change notification settings - Fork 163
Sinatra Demo
The following is an example of a self-reconfiguring Sinatra application using Noah. You'll need the version currently on github.
For the purposes of this test, I decided to use the release of the new version of Redis, as a way to demonstrate how to reconfigure a running application using Noah.
You'll need:
Getting Redis running is outside the scope of this example. Suffice to say, you'll need them either running on two different addresses or two different ports.
In my case, 2.2.0 is running on localhost:6380
and 2.0.4 is running on localhost:6379
.
Now that both instance of Redis are running, we need to set up the remaining bits of the demonstration.
You'll need to start Noah. From the directory where you cloned the source
cd bin
./noah -F -d
[2011-02-23 04:16:23 -0500] Starting 'noah'...
[2011-02-23 04:16:23 -0500] trying port 5678...
Couldn't get a file descriptor referring to the console
[2011-02-23 04:16:23 -0500] Running with Rack handler: Rack::Handler::Thin
>> Thin web server (v1.2.7 codename No Hup)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:5678, CTRL+C to stop
This will be an empty Noah install, so you'll need to add an entry for your application:
curl -X PUT -d '{"format":"string","body":"redis://localhost:6379/0"}' http://localhost:5678/applications/my_sinatra_app/configurations/redis_server
{"result":"success","id":"3acec5ae-bdce-c9fb-fbc5-984f1405715d","name":"my_sinatra_app","action":"create","configuration":{"action":"create","id":"d1a3d9c2-26bd-b9d8-f726-55e488a4460c","item":"redis_server"}}
From the directory where you cloned the source:
cd examples
ruby reconfiguring-sinatra.rb
== Sinatra/1.1.2 has taken the stage on 4567 for development with backup from Thin
>> Thin web server (v1.2.7 codename No Hup)
>> Maximum connections set to 1024
>> Listening on 0.0.0.0:4567, CTRL+C to stop
Now if you visit http://localhost:4567
in the browser, you should be greeted by this:
Redis version: 2.0.4
I'm aware that using Excon inside of EventMachine is unsafe. I forgot to pull that out before commit. It was an "experiment" on my part passing non-EM friendly objects into the reactor
Again, from the source directroy
cd examples
ruby reconfiguring-sinatra-watcher.rb
I, [2011-02-23T04:25:39.028094 #31205] INFO -- : Binding to pattern //noah/configuration/redis_server.update
What we've done is tell the watcher to match against the pattern //noah/configuration/redis_server
. Using the Watcher DSL, we're going to fire off an HTTP PUT to our running sinatra app at http://localhost:4567/webhook
.
At this point, nothing special is going on. We have a five things running:
- Noah (port 5678)
- Redis 2.2.0 (port 6380)
- Redis 2.0.4 (port 6379)
- Sinatra App (port 4567)
- Our watcher script (bound to Noah's redis instance)
So now we simulate a configuration change in Noah:
curl -X PUT -d '{"format":"string","body":"redis://localhost:6380/0"}' http://localhost:5678/applications/my_sinatra_app/configurations/redis_server
{"result":"success","id":"1","action":"update","dependencies":"updated","application":"my_sinatra_app","item":"redis_server"}
If you look over to the window where your watcher script is running, you will see this:
I, [2011-02-23T04:32:53.724005 #31205] INFO -- : Calling message handler
{"id":"1","name":"redis_server","format":"string","body":"redis://localhost:6380/0","created_at":"2011-02-23 08:01:53 UTC","updated_at":"2011-02-23 09:32:53 UTC","application":"my_sinatra_app","action":"update","pubcategory":"noah.Noah::Configuration.redis_server.update"}
And if you look at your sinatra app console:
127.0.0.1 - - [23/Feb/2011 04:32:53] "PUT /webhook HTTP/1.1" 200 85 0.0003
Let's reload the page in our browser:
Redis version: 2.2.0
Our Sinatra app is now talking to the new version of Redis. You can re-PUT the curl message above swapping between redis://localhost:6379/0
and redis://localhost:6380/0
and the Sinatra application will happily swich between them.
While a decent example of the kinds of things Noah can do, the demo is not without flaws.
The way I'm connecting to redis in the sinatra application creates a distinct connection for every single GET
. Obviously less than ideal. At one point in the test, I had 81 client connections to one of my redis servers.
If you'll notice, the pattern we're binding to is //noah/configuration/redis_server.update
. There are two problems with this:
- That's only a subset of activity hitting that Configuration object. You should probably be matching against all activity and filtering at the application level.
- Because of the current implementation of activity hooks, your pattern will actually catch ANY update to a configuration object called
redis_server
, not just the one formy_sinatra_app
. I need to fix that.
I'm not doing any error checking or validation of reconfiguration. I'm working on a wiki entry for best practices for hooking your application into Noah. Essentially, one of the key points is:
NEVER reconfigure a setting without first validating it in your application.
This is especially important with connection strings of any kind. In this example, I should actually be passing off the reconfiguration to a method that stashes the current setting away, tests the new setting via some predefined criteria and only then, changes the setting globally. You wouldn't want to be given a new database server setting only to realize that the users table is empty, would you?
Noah is somewhat insecure at this point. The reason for that is that I'm noodling out the best way to handle authentication.
However the webhook endpoint in the Sinatra application will gladly accept a post from anywhere and anyone, including a harmful one. Without the aforementioned error checking, a malicious PUT could shutdown your application. The upshot is that a single message to Noah with the correct setting will reconfigure your application properly!
Because I'm still working out Watchers (and loving it!), everything is a distinct component. Ideally, Noah will be able to run a simple in-process Watcher agent or you can fireup a boatload of distinct workers who do nothing but fire off callbacks.
Also each watcher agent is currently configured to talk to a distinct endpoint. Once the Watcher registration part of Noah is finished, these agents will use Noah itself to not only know which Redis instance to subscribe to but also what all the registered callbacks are so it can process them.
The reconfiguration done on the Sinatra side is just a quick hack, ideally there would be an installable gem called Sinatra::Noah or something that handled that and the previously mentioned criteria, for you.