Commit a0e1b16f authored by John E. Vincent's avatar John E. Vincent

updating readme. more CRUD endpoints. refactor state to status

parent 3792d7a0
...@@ -11,6 +11,8 @@ _ruby setup.rb_ ...@@ -11,6 +11,8 @@ _ruby setup.rb_
Create Service entry for noah Create Service entry for noah
Creating Application entry for 'noah' Creating Application entry for 'noah'
Creating Configuration entry for 'noah' Creating Configuration entry for 'noah'
Creating sample entries - Host and Service
Creating sample entries - Application and Configuration
Setup successful! Setup successful!
## Run it ## Run it
...@@ -45,14 +47,14 @@ _curl http://localhost:9292/s/_ ...@@ -45,14 +47,14 @@ _curl http://localhost:9292/s/_
{ {
"id":"1", "id":"1",
"name":"redis", "name":"redis",
"state":true, "status":"up",
"updated_at":"2011-01-17 14:12:43 UTC", "updated_at":"2011-01-17 14:12:43 UTC",
"host":"localhost" "host":"localhost"
}, },
{ {
"id":"2", "id":"2",
"name":"noah", "name":"noah",
"state":true, "status":"up",
"updated_at":"2011-01-17 14:12:43 UTC", "updated_at":"2011-01-17 14:12:43 UTC",
"host":"localhost" "host":"localhost"
} }
...@@ -65,20 +67,20 @@ _curl http://localhost:9292/h/_ ...@@ -65,20 +67,20 @@ _curl http://localhost:9292/h/_
{ {
"id":"1", "id":"1",
"name":"localhost", "name":"localhost",
"state":true, "status":"up",
"updated_at":"2011-01-17 14:12:43 UTC", "updated_at":"2011-01-17 14:12:43 UTC",
"services":[ "services":[
{ {
"id":"1", "id":"1",
"name":"redis", "name":"redis",
"state":true, "status":"up",
"updated_at":"2011-01-17 14:12:43 UTC", "updated_at":"2011-01-17 14:12:43 UTC",
"host":"localhost" "host":"localhost"
}, },
{ {
"id":"2", "id":"2",
"name":"noah", "name":"noah",
"state":true, "status":"up",
"updated_at":"2011-01-17 14:12:43 UTC", "updated_at":"2011-01-17 14:12:43 UTC",
"host":"localhost" "host":"localhost"
} }
...@@ -99,8 +101,7 @@ _curl http://localhost:9292/a/_ ...@@ -99,8 +101,7 @@ _curl http://localhost:9292/a/_
Most other combinations of endpoints work as well: Most other combinations of endpoints work as well:
* `http://localhost:9292/h/<hostname>/s/` - All service for `<hostname>` * `http://localhost:9292/h/<hostname>/<servicename>` - `<servicename>` on `<hostname>`
* `http://localhost:9292/h/<hostname>/s/<servicename>` - `<servicename>` on `<hostname>`
* `http://localhost:9292/a/<appname>/<configname>` - Configuration for `<appname>` * `http://localhost:9292/a/<appname>/<configname>` - Configuration for `<appname>`
* `http://localhost:9292/c/<appname>/<element>` - Specific configuration element for `<appname>` * `http://localhost:9292/c/<appname>/<element>` - Specific configuration element for `<appname>`
...@@ -114,7 +115,7 @@ Most other combinations of endpoints work as well: ...@@ -114,7 +115,7 @@ Most other combinations of endpoints work as well:
} }
# Adding new entries # Adding new entries
I've not yet flushed out the put support for each route yet. I've been doing additions via `irb` for now: I've not yet flushed out all the put support for each route yet. I've been doing additions via `irb` for now:
## Adding a new application and configuration item ## Adding a new application and configuration item
...@@ -150,13 +151,37 @@ I've not yet flushed out the put support for each route yet. I've been doing add ...@@ -150,13 +151,37 @@ I've not yet flushed out the put support for each route yet. I've been doing add
password: my super secret password password: my super secret password
# Hosts and Services/Applications and Configurations # Hosts and Services/Applications and Configurations
I'm still noodling out some things around Hosts and Services. There's a field called `state` that's a Boolean. The thought is that `state` set at `0` or `false` means that service or Host is unavailable. Services would, when they start up, send a put to Noah setting the flag to online. There's a bit of overlap between Applications and Services but I think the distinction is clear. Applications have configurations and Hosts have Services. Host/Services and Applications/Configurations are almost the same thing with a few exceptions. Here are some basic facts:
* Hosts have many Services
* Applications have many Configurations
* Hosts and Services have a status - `up`,`down` or `pending`
The intention of the `status` field for Hosts and Services is that a service might, when starting up, set the appropriate status. Same goes for said service shutting down. This also applies to hosts (i.e. a curl PUT is sent to Noah during the boot process).
While an application might have different "configurations" based on environment (production, qa, dev), the Configuration object in Noah is intended to be more holistic i.e. these are the Configuration atoms (a yaml file, property X, property Y) that form the running configuration of an Application.
Here's a holistic example using a tomcat application:
* Host running tomcat comes up. It sets its status as "pending"
* Each service on the box starts up and sets its status to "pending" and finally "up" (think steps in the init script for the service)
* Tomcat (now in the role of `Application`) given a single property in a properties file called "bootstrap.url", grabs a list of `Configuration`atoms it needs to run. Let's say, by default, Tomcat starts up with all webapps disabled. Using the `Configuration` item `webapps`, it knows which ones to start up.
* Each webapp (an application under a different context root) now has the role of `Application` and the role of `Service`. As an application, the webapp would grab things that would normally be stored in a .properties file. Maybe even the log4j.xml file. In the role of `Service`, a given webapp might be an API endpoint and so it would have a hostname (a virtual host maybe?) and services associated with it. Each of those, has a `status`.
That might be confusing and it's a fairly overly-contrived example. A more comon use case would be the above where, instead of storing the database.yml on the server, the Rails application actually reads the file from Noah. Now that might not be too exciting but try this example:
* Rails application with memcached as part of the stack.
* Instead of a local configuration file, the list of memcached servers is a `Configuration` object belonging to the rails application's `Application` object.
* As new memcached servers are brought online, your CM tool (puppet or chef) updates Noah
* Your Rails application either via restarting (and thus rebootstrapping the list of memcached servers from Noah) or using the Watcher subsystem is instantly aware of those servers. You could fairly easily implement a custom Watcher that, when the list of memcached server changes, the Passenger restart file is written.
Make sense?
# Constraints # Constraints
You can view all the constraints inside `models.rb` but here they are for now: You can view all the constraints inside `models.rb` but here they are for now:
* A new host must have at least `name` and `state` set. * A new host must have at least `name` and `status` set.
* A new service must have at least `name` and `state` set. * A new service must have at least `name` and `status` set.
* Each Host `name` must be unique * Each Host `name` must be unique
* Each Service `name` per Host must be unique * Each Service `name` per Host must be unique
* Each Application `name` must exist and be unique * Each Application `name` must exist and be unique
......
...@@ -21,16 +21,10 @@ end ...@@ -21,16 +21,10 @@ end
namespace "/h" do namespace "/h" do
get '/:hostname/s/:servicename/?' do |hostname, servicename| get '/:hostname/:servicename/?' do |hostname, servicename|
host_service(hostname, servicename).to_json host_service(hostname, servicename).to_json
end end
get '/:hostname/s/?' do |hostname|
hs = host_services(hostname)
hs.map! {|x| x.to_hash}
hs.to_json
end
get '/:hostname/?' do |hostname| get '/:hostname/?' do |hostname|
host(:name => hostname).to_json host(:name => hostname).to_json
end end
...@@ -40,11 +34,32 @@ namespace "/h" do ...@@ -40,11 +34,32 @@ namespace "/h" do
hosts.to_json hosts.to_json
end end
put '/?' do
begin
required_params = ["name", "status"]
data = JSON.parse(request.body.read)
data.keys.sort == required_params.sort ? (host = Host.find_or_create(:name => data['name'], :status => data['status'])) : (raise "Missing Parameters")
if host.valid?
status 200
r = {"result" => "success","id" => "#{host.id}","status" => "#{host.status}", "name" => "#{host.name}"}
r.to_json
else
status 500
r = {"result" => "failure","error" => "#{app.errors}"}
r.to_json
end
rescue Exception => e
status 500
r = {"result" => "failure","error" => "#{e.message}"}
r.to_json
end
end
end end
namespace "/s" do namespace "/s" do
get '/:servicename/h/:hostname/?' do |servicename, hostname| get '/:servicename/:hostname/?' do |servicename, hostname|
host_service(hostname, servicename).to_json host_service(hostname, servicename).to_json
end end
...@@ -155,10 +170,10 @@ namespace '/c' do ...@@ -155,10 +170,10 @@ namespace '/c' do
"#{configs.to_json}" "#{configs.to_json}"
end end
put '/:appname/:element?' do |appname, item| put '/:appname/:element?' do |appname, element|
begin begin
app = Application.find_or_create(:name => appname) app = Application.find_or_create(:name => appname)
config = Configuration.find_or_create(:name => item, :application_id => app.id) config = Configuration.find_or_create(:name => element, :application_id => app.id)
required_params = ["format", "body"] required_params = ["format", "body"]
data = JSON.parse(request.body.read) data = JSON.parse(request.body.read)
data.keys.sort == required_params.sort ? (config.format = data["format"]; config.body = data["body"]) : (raise "Missing Parameters") data.keys.sort == required_params.sort ? (config.format = data["format"]; config.body = data["body"]) : (raise "Missing Parameters")
...@@ -179,23 +194,28 @@ namespace '/c' do ...@@ -179,23 +194,28 @@ namespace '/c' do
end end
end end
end delete '/:appname/:element?' do |appname, element|
begin
# A route for adding a new host app = Application.find(:name => appname).first
put '/h/:hostname' do if app
host = params[:hostname] config = Configuration.find(:name=> element, :application_id => app.id).first
data = JSON.parse(request.body.read) if config
begin config.delete
h = Host.find(:name => host).first status 200
h.state = 1 r = {"result" => "success", "id" => "#{config.id}", "item" => "#{appname}/#{element}"}
rescue r.to_json
# If we have to register the host, we mark it as inactive else
# Feels like the right thing to do in case of accidental creation r = {"result" => "failure","error" => "Configuration element not found"}
# from mispelling halt 404, r.to_json
h = Host.create(:name => params[:hostname], :state => 0) end
end else
if h.save r = {"result" => "failure","error" => "Application not found"}
data['services'].each {|x| h.services << Service.create(:name=> x['name'], :state => x['state'], :host => h)} if data['services'] halt 404, r.to_json
{"msg" => "success"}.to_json end
rescue Exception => e
r = {"result" => "failure", "error" => "#{e.message}"}
halt 500, r.to_json
end
end end
end end
...@@ -4,23 +4,44 @@ class Host < Ohm::Model ...@@ -4,23 +4,44 @@ class Host < Ohm::Model
include Ohm::Typecast include Ohm::Typecast
include Ohm::Timestamping include Ohm::Timestamping
include Ohm::Callbacks include Ohm::Callbacks
include Ohm::ExtraValidations
attribute :name attribute :name
attribute :state, Boolean attribute :status
collection :services, Service collection :services, Service
index :name index :name
index :state index :status
def validate def validate
assert_present :name, :state assert_present :name, :status
assert_unique :name assert_unique :name
assert_member :status, ["up","down","pending"]
end end
def to_hash def to_hash
arr = [] arr = []
services.sort.each {|s| arr << s.to_hash} services.sort.each {|s| arr << s.to_hash}
super.merge(:name => name, :state => state, :updated_at => updated_at, :services => arr) super.merge(:name => name, :status => status, :updated_at => updated_at, :services => arr)
end
class << self
def find_or_create(opts = {})
begin
# exclude requested status from lookup
h = find(opts.reject{|key,value| key == :status}).first
host = h.nil? ? create(opts) : h
host.status = opts[:status]
if host.valid?
host.save
host
else
raise host.errors
end
rescue Exception => e
e.message
end
end
end end
end end
...@@ -28,21 +49,23 @@ class Service < Ohm::Model ...@@ -28,21 +49,23 @@ class Service < Ohm::Model
include Ohm::Typecast include Ohm::Typecast
include Ohm::Timestamping include Ohm::Timestamping
include Ohm::Callbacks include Ohm::Callbacks
include Ohm::ExtraValidations
attribute :name attribute :name
attribute :state, Boolean attribute :status
reference :host, Host reference :host, Host
index :name index :name
index :state index :status
def validate def validate
assert_present :name, :state assert_present :name, :status
assert_unique [:name, :host_id] assert_unique [:name, :host_id]
assert_member :status, ["up", "down", "pending"]
end end
def to_hash def to_hash
super.merge(:name => name, :state => state, :updated_at => updated_at, :host => Host[host_id].name) super.merge(:name => name, :status => status, :updated_at => updated_at, :host => Host[host_id].name)
end end
end end
...@@ -50,6 +73,7 @@ class Configuration < Ohm::Model ...@@ -50,6 +73,7 @@ class Configuration < Ohm::Model
include Ohm::Typecast include Ohm::Typecast
include Ohm::Timestamping include Ohm::Timestamping
include Ohm::Callbacks include Ohm::Callbacks
include Ohm::ExtraValidations
attribute :name attribute :name
attribute :format attribute :format
...@@ -84,6 +108,7 @@ class Application < Ohm::Model ...@@ -84,6 +108,7 @@ class Application < Ohm::Model
include Ohm::Typecast include Ohm::Typecast
include Ohm::Timestamping include Ohm::Timestamping
include Ohm::Callbacks include Ohm::Callbacks
include Ohm::ExtraValidations
attribute :name attribute :name
collection :configurations, Configuration collection :configurations, Configuration
......
...@@ -8,11 +8,11 @@ require File.join(File.dirname(__FILE__), 'models') ...@@ -8,11 +8,11 @@ require File.join(File.dirname(__FILE__), 'models')
Ohm.redis.flushdb Ohm.redis.flushdb
# Add an entry for my localhost # Add an entry for my localhost
puts "Creating Host entry for 'localhost'" puts "Creating Host entry for 'localhost'"
h = Host.create(:name => 'localhost', :state => 1) h = Host.create(:name => 'localhost', :status => "up")
if h.save if h.save
%w[redis noah].each do |service| %w[redis noah].each do |service|
puts "Create Service entry for #{service}" puts "Create Service entry for #{service}"
s = Service.create(:name => service, :state => 1, :host => h) s = Service.create(:name => service, :status => "up", :host => h)
h.services << s h.services << s
end end
end end
...@@ -31,10 +31,10 @@ end ...@@ -31,10 +31,10 @@ end
puts "Creating sample entries - Host and Service" puts "Creating sample entries - Host and Service"
%w[host1.domain.com host2.domain.com host3.domain.com].each do |host| %w[host1.domain.com host2.domain.com host3.domain.com].each do |host|
h = Host.create(:name => host, :state => 0) h = Host.create(:name => host, :status => "up")
if h.save if h.save
%w[http https smtp mysql].each do |service| %w[http https smtp mysql].each do |service|
s = Service.create(:name => service, :state =>0, :host => h) s = Service.create(:name => service, :status => "pending", :host => h)
h.services << s h.services << s
end end
end end
......
...@@ -12,18 +12,16 @@ ...@@ -12,18 +12,16 @@
%li %li
%a{:href => "/h/localhost"} localhost (this server) %a{:href => "/h/localhost"} localhost (this server)
%li %li
%a{:href => "/h/localhost/s"} localhost services %a{:href => "/h/localhost/noah"} localhost noah service
%li
%a{:href => "/h/localhost/s/noah"} localhost noah service
#header #header
%h2 Services %h2 Services
%ul %ul
%li %li
%a{:href => "/s/"} All registered Services %a{:href => "/s/"} All registered Services
%li
%a{:href => "/s/noah/h/localhost"} localhost noah service
%li %li
%a{:href => "/s/http"} All hosts providing 'http' %a{:href => "/s/http"} All hosts providing 'http'
%li
%a{:href => "/s/noah/localhost"} localhost noah service
#header #header
%h2 Applications %h2 Applications
%ul %ul
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment