Skip to content

Commit

Permalink
delete, update, and add sentries works, pulling sentries configs via …
Browse files Browse the repository at this point in the history
…json with http auth works, sentry.survey! works
  • Loading branch information
Jonathan Hoyt authored and Jonathan Hoyt committed Dec 23, 2008
1 parent a2cdd69 commit f6cd51a
Show file tree
Hide file tree
Showing 13 changed files with 327 additions and 9 deletions.
8 changes: 6 additions & 2 deletions app/controllers/clients_controller.rb
Expand Up @@ -3,7 +3,7 @@ class ClientsController < ApplicationController
layout 'clients'

def index
@clients = Client.find(:all, :order => :updated_at, :limit => 100)
@clients = []

respond_to do |format|
format.html # index.html.erb
Expand All @@ -13,7 +13,11 @@ def index
end

def search
@clients = Client.search(params[:q], :limit => 100, :only => [:firstname, :lastname, :name])
if !params[:q].blank?
@clients = Client.search(params[:q], :limit => 100, :only => [:firstname, :lastname, :name])
else
@clients = []
end
respond_to do |format|
format.xml { render :xml => @clients }
format.json { render :json => @clients }
Expand Down
2 changes: 1 addition & 1 deletion app/controllers/devices_controller.rb
Expand Up @@ -13,7 +13,7 @@ def index
respond_to do |format|
format.html
format.xml { render :xml => @devices }
format.json { render :json => @devices }
format.json { render :json => @devices.to_json() }
end
end

Expand Down
22 changes: 21 additions & 1 deletion app/controllers/sentries_controller.rb
@@ -1,7 +1,17 @@
class SentriesController < ApplicationController
before_filter :login_required
before_filter :login_required, :except => 'index'
before_filter :http_basic_authenticate, :only => 'index'
layout nil

def index
if params[:device_id]
@sentries = Sentry.find(:all, :conditions => {:device_id => params[:device_id]})
respond_to do |format|
format.json { render :json => @sentries }
end
end
end

def edit
@sentry = Sentry.find(params[:id])
end
Expand Down Expand Up @@ -33,4 +43,14 @@ def destroy
@sentry.destroy
redirect_to :back
end

protected

def http_basic_authenticate
authenticate_or_request_with_http_basic do |username, password|
# after testing uncomment the following line and comment out the test line
# username == APP_CONFIG[:event_api_username] && password == APP_CONFIG[:event_api_password]
username == "test" && password == "test"
end
end
end
12 changes: 11 additions & 1 deletion app/models/event.rb
@@ -1,3 +1,13 @@
class Event < ActiveRecord::Base
belongs_to :recordable, :polymorphic => true
end

def has(word)
self.message =~ /#{word}/ ? true : false
end
alias :have :has

def message
self.data
end

end
9 changes: 9 additions & 0 deletions app/models/sentry.rb
Expand Up @@ -2,5 +2,14 @@ class Sentry < ActiveRecord::Base
has_many :events, :as => :recordable, :dependent => :destroy
belongs_to :device
belongs_to :schedule
belongs_to :goggle

def survey!
StatusLang.run(self.id, self.goggle.script) ? true : false
end

def notify!(message=nil)
NotificationQueue.create(:message => message, :schedule_id => self.schedule_id)
end

end
3 changes: 0 additions & 3 deletions app/views/layouts/clients.html.erb
Expand Up @@ -27,9 +27,6 @@
<div id="applesearch"><input type="search" id="srch_fld" placeholder="Search..." autosave="applestyle_srch" results="5" onkeyup="" /></div>
<h2>Clients</h2>
<ul class="itu" id="clients">
<% Client.find(:all, :limit => 100).each do |client| %>
<li alt="<%= client.id %>"><a href="/clients/<%= client.id %>" class="<%= client.type %>"><%= client.lastfirst %></a></li>
<% end %>
</ul>
</div>

Expand Down
3 changes: 3 additions & 0 deletions config/environment.rb
Expand Up @@ -10,6 +10,8 @@
# Bootstrap the Rails environment, frameworks, and default configuration
require File.join(File.dirname(__FILE__), 'boot')
require 'redcloth'
require 'days_and_times'
# require 'json/pure'

Rails::Initializer.run do |config|
# Settings in config/environments/* take precedence over those specified here.
Expand Down Expand Up @@ -70,4 +72,5 @@
config.gem 'json'
config.gem 'paperclip'
require 'lib/search.rb'
require 'lib/statuslang.rb'
end
2 changes: 1 addition & 1 deletion db/migrate/20081212174923_create_sentries.rb
Expand Up @@ -4,7 +4,7 @@ def self.up
t.boolean :state
t.text :message
t.integer :device_id
t.string :goggle_parameters
t.string :parameters
t.datetime :last_surveyed_at
t.integer :survey_interval
t.integer :notifications_to_send
Expand Down
128 changes: 128 additions & 0 deletions lib/daemon.rb
@@ -0,0 +1,128 @@
# Loop.
# Do any survey that either doesn't have a last_surveyed_at, or is past last_surveyed_at + survey_interval. And is not currently ignored.
# Record last_surveyed_at
# Notify if haven't notified too recently
# Record last_notified_at
#
# Later: Partition groups of surveys into separate threads to get it done faster.
require 'fileutils'

def loggit!(msg)
File.open("log/watcher.log", 'a') do |log|
log << "[#{Time.now.strftime("%D %T")}] #{msg}\n"
end
msg
end
def store_pid(pid)
File.open("log/watcher.pid", 'w'){|f| f.write("#{pid}\n")}
end
def delete_pidfile
FileUtils.rm("log/watcher.pid")
end

def watch!
# This will naturally migrate different surveys to need run slightly apart from one another.
while(sleep(1))
Sentry.find(:all).each do |sentry|
begin
if (sentry.last_surveyed_at.nil? || Time.now > sentry.last_surveyed_at.to_time + sentry.survey_interval)
begin
success = sentry.survey!
if success
loggit! "SUCCESS #{sentry.device.name} / #{sentry.goggle.name}"
sentry.last_notified_at = nil
else !success
loggit! "FAILURE #{sentry.device.name} / #{sentry.goggle.name}"
if sentry.last_notified_at.nil? || (sentry.notifications_to_send > 1 && Time.now > sentry.last_notified_at.to_time + sentry.maximum_notify_frequency)
if sentry.notify!
sentry.last_notified_at = Time.now
loggit! " -> NOTIFIED"
else
loggit! " -> Could Not Notify"
end
end
end
rescue => e
STDERR << loggit!("ERROR - (#{sentry.device.name} / #{sentry.goggle.name}): #{e}")
if sentry.last_notified_at.nil? || (sentry.notifications_to_send > 1 && Time.now > sentry.last_notified_at.to_time + sentry.maximum_notify_frequency)
loggit! " -> NOTIFIED of ERROR"
sentry.last_notified_at = Time.now if sentry.notify!("Sentry #{sentry.device.name} / #{sentry.goggle.name} errored: #{e}")
end
ensure
sentry.last_surveyed_at = Time.now
sentry.save
end
end
rescue => e
STDERR << loggit!("ERROR - Was going to cause exit on Sentry #{sentry.device.name}! Error: #{e}")
sentry.notify!("Sentry #{sentry.device.name} Exit-causing error: #{e}", 'dcparker@gmail.com')
end
end
end
end

watch!

#
# def start_daemon!
# if File.exists?("log/watcher.pid")
# pid = IO.read("log/watcher.pid").chomp.to_i
# puts "Already running on process #{pid}!"
# else
# fork do
# Process.setsid
# exit if fork
# at_exit {
# delete_pidfile
# loggit!('Daemon Stopped')
# }
# File.umask 0000
# STDIN.reopen "/dev/null"
# trap("TERM") { exit }
# trap("INT") { exit }
# store_pid(Process.pid)
# puts "Started Watcher Daemon on process #{Process.pid}."
# STDOUT.reopen "/dev/null", "a"
# STDERR.reopen "/dev/null"
# loggit!('Daemon Starting...')
# STDERR.reopen(File.open("log/watcher_errors.log", 'a'))
# Merb::Config[:environment] = 'production' unless ARGV[1] == 'development' || Merb::Config[:environment]
# ::Merb::BootLoader.initialize_merb
# loggit!(' -> Started')
# begin
# watch!
# rescue => e
# STDERR << loggit!("Program error OUTSIDE loop! > #{e}")
# email = Merb::Mailer.new(:to => 'dcparker@gmail.com',
# :from => "imonit@sabretechllc.com",
# :subject => "iMonit Daemon Stopped on ERROR",
# :text => "Program error OUTSIDE loop! > #{e}")
# email.deliver!
# end
# end
# end
# end
#
# def stop_daemon!
# begin
# pid = IO.read("log/watcher.pid").chomp.to_i
# Process.kill("INT", pid)
# loggit! "Daemon Stopped Manually :)"
# puts "Stopped Watcher Daemon on process #{pid}"
# rescue => k
# puts "Failed to kill! #{k}"
# ensure
# exit
# end
# end
#
# case ARGV[0]
# when 'start'
# start_daemon!
# when 'stop'
# stop_daemon!
# when 'log'
# puts IO.read("log/watcher.log")
# else
# puts "Usage: ruby lib/daemon.rb start|stop|log"
# end
6 changes: 6 additions & 0 deletions lib/statuslang.rb
@@ -0,0 +1,6 @@
require 'statuslang/lang'
module StatusLang
def self.run(sentry_id,code)
Lang.new(sentry_id).instance_eval(code)
end
end
43 changes: 43 additions & 0 deletions lib/statuslang/lang.rb
@@ -0,0 +1,43 @@
require 'statuslang/ruby_lang_extensions'
module StatusLang
class Lang
def initialize(sentry_id)
@sentry_id = sentry_id
end

def last(amount=nil)
if amount
if amount.is_a?(Duration)
options[:after] = amount.ago
elsif amount.is_a?(Integer)
options[:limit] = amount
else
raise ArgumentError, "amount must be a Duration or an Integer!"
end
end
self
end

def posts
if @options[:after]
Event.find(:all, :conditions => {:recordable_type => "Sentry", :recordable_id => @sentry_id, :created_at.gte => @options[:after]})
elsif @options[:limit]
Event.find(:all, :conditions => {:recordable_type => "Sentry", :recordable_id => @sentry_id}, :limit => @options[:limit])
end
end
def post
Event.find(:last, :conditions => {:recordable_type => "Sentry", :recordable_id => @sentry_id})
end
def all_posts
posts.all
end
def any_post
posts.any
end

private
def options
@options ||= {}
end
end
end
60 changes: 60 additions & 0 deletions lib/statuslang/message.rb
@@ -0,0 +1,60 @@
require 'simple_mapper'
require 'net/http'
require 'lib/nethttp_magic'

module StatusLang
# Post is a String class that is also a SimpleMapper persistence class reading from StatusPing.com.
# The reason it is also a string is so that it can be treated as a simple string of the Message content itself if desired.
#
# To use, first set the options (which will be set per-feed)
# StatusLang::Post.options(:feed_key => feed_key, :feed_secret => feed_secret)
# then call just like any SimpleMapper model: Post.get(..query-params..).
class Message < String
include SimpleMapper::Persistence
set_format :json
set_entity_name 'message'
add_connection_adapter(:http) do
set_base_url 'http://statusping.com/messages'
end
has :properties
property :feed
property :created_by
property :content
property :created_at
uses :callbacks
add_callback('initialize_request') do |request,cboptions|
# Add HTTP Basic Auth to the request
# the nonce can technically be anything, but to be best secure we want it pretty randomly generated.
nonce = Digest::SHA1.hexdigest("--#{rand(1234)}--#{Time.now.to_s}--#{rand(12345)}--")
sequence = Time.now.to_f
request.add_query_param(:nonce => nonce)
request.basic_auth options[:feed_key], Digest::SHA1.hexdigest("#{nonce}#{options[:feed_secret]}")
[request,cboptions]
end
def self.options(options=nil)
Thread.current['statusping_options'] = options if options
Thread.current['statusping_options'] ||= {}
end

# Sets the data into the object. This is provided as a default method, but your model can overwrite it any
# way you want. For example, you could set the data to some other object type, or to a Marshalled storage.
# The type of data you receive will depend on the format and parser you use. Of course you could make up
# your own spin-off of one of those, too.
def data=(data)
raise TypeError, "data must be a hash" unless data.is_a?(Hash)
data.each {|k,v| instance_variable_set("@#{k}", v)}
self.replace(@content)
end
alias :update_data :data=


# The following is for StatusLang.

attr_accessor :any_all

def has(word)
self =~ /#{word}/ ? true : false
end
alias :have :has
end
end

0 comments on commit f6cd51a

Please sign in to comment.