Skip to content
This repository has been archived by the owner on Dec 27, 2022. It is now read-only.

Sinatra Demo

lusis edited this page Oct 20, 2011 · 7 revisions

Example Application - Sinatra

The following is an example of a self-reconfiguring Sinatra application using Noah. You'll need the version currently on github.

Requirements

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.

Setting it all up

Now that both instance of Redis are running, we need to set up the remaining bits of the demonstration.

Noah

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

Add an entry for your application

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"}}

Sample Sinatra Webserver

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

Watcher agent

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.

Update Noah

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.

Gotchas/Caveats/Colophon


While a decent example of the kinds of things Noah can do, the demo is not without flaws.

Redis client connections

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.

Namespacing on patterns

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 for my_sinatra_app. I need to fix that.

No error checking whatsoever

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?

No security anywhere

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!

Separate watcher agent

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.

Quick Hack

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.