Skip to content

Commit

Permalink
Merge 7f186ad into 577b95f
Browse files Browse the repository at this point in the history
  • Loading branch information
viable-hartman committed Jun 3, 2014
2 parents 577b95f + 7f186ad commit ab61f07
Show file tree
Hide file tree
Showing 4 changed files with 209 additions and 0 deletions.
3 changes: 3 additions & 0 deletions Gemfile
Expand Up @@ -64,6 +64,9 @@ gem 'wunderground', '~> 1.2.0'
gem 'forecast_io', '~> 2.0.0'
gem 'rturk', '~> 2.12.1'

# Required for the pingdom agent
gem 'rest-client', '~> 1.6.7', require: false

gem 'twitter', '~> 5.8.0'
gem 'twitter-stream', github: 'cantino/twitter-stream', branch: 'master'
gem 'em-http-request', '~> 1.1.2'
Expand Down
131 changes: 131 additions & 0 deletions app/models/agents/pingdom_agent.rb
@@ -0,0 +1,131 @@
require "rest-client"
require "json"

module Agents
class PingdomAgent < Agent
cannot_receive_events!

description <<-MD
The Pingdom Agent creates an event for grabbing Pingdom alerts from an account specified by the given `pingdom_credref` field. This field is a key for the Pingdom password stored under [credentials](/user_credentials).
An API key set in `pingdom_apikey` is also necessary. You can generate a key under your Pingdom account by [following instructions found here](https://my.pingdom.com/account/appkeys).
An event will be created for each check that has changed state since the previous check. `memory['last_run']` is utilized to keep track of the last Pingdom check.
MD

event_description <<-MD
Events look like this:
{
"name": "Build.com",
"state": "up",
"lastresponsetime": "796ms"
}
MD

default_schedule "every_1m"

def working?
event_created_within?(options['expected_update_period_in_days']) && !recent_error_logs?
end

def pingdom_setup?
options['pingdom_url'].present? && pingdom_credref.present? && options['pingdom_apikey'].present?
end

def default_options
{
'pingdom_url' => 'https://api.pingdom.com/api/2.0',
'pingdom_credref' => '-email-',
'pingdom_apikey' => '',
'expected_update_period_in_days' => '14',
}
end

def pingdom_credref
(options['pingdom_credref'].presence && (options['pingdom_credref'] != '-email-')) ? options['pingdom_credref'] : nil
end

def pingdom_credentials
if pingdom_credref.present?
{
'pingdom_user' => options['pingdom_credref'],
'pingdom_pass' => credential(options['pingdom_credref'])
}
end
end

def pingdom_url(path)
"#{options["pingdom_url"]}/#{path}"
end

def validate_options
errors.add(:base, "Pingdom URL and credential reference are required") unless pingdom_setup?
errors.add(:base, "You need to specify the expected update period") unless options['expected_update_period_in_days'].present?
end

def get(url, options)
response = RestClient::Request.new(
:method => :get,
:url => url,
:user => pingdom_credentials['pingdom_user'],
:password => pingdom_credentials['pingdom_pass'],
:headers => options).execute

if response.code == 400
raise RuntimeError.new("Pingdom error: #{response['errorMessages']}")
elsif response.code == 403
raise RuntimeError.new("Authentication failed: Forbidden (403)")
elsif response.code != 200
raise RuntimeError.new("Request failed: #{response}")
end

response.body
end

def get_checks()
checks = {}
if pingdom_setup?
response = JSON.parse(
get(pingdom_url('checks'), {"App-Key" => options['pingdom_apikey'], :content_type => :json}),
:symbolize_names => true)
if response[:checks]
response[:checks].zip(response[:checks]) { |a,b|
checks[a[:name].to_sym] = {
:state => (b[:status] == 'up') ? 'up' : 'down',
:lastresponsetime => (b[:status] == 'up') ? b[:lastresponsetime] : "DOWN"
}
}
else
checks["pingdom"] = {:state => "down", :lastresponsetime => "-"}
end
end
return checks
end

def check
if pingdom_setup?
if memory.has_key?("last_run")
last_run = JSON.parse(memory["last_run"])
else
last_run = {}
end
checks = get_checks()
# Only create event if the state has changed.
checks.each do |check, chkinfo|
checkkey = check.to_s
if last_run.has_key?(checkkey)
if last_run[checkkey]["state"] != chkinfo[:state]
log "#{check} transitioning from #{last_run[checkkey]["state"]} to #{chkinfo[:state]}"
create_event :payload => chkinfo.merge(:name => check)
end
else
log "#{check} initializing to #{chkinfo[:state]}"
create_event :payload => chkinfo.merge(:name => check)
end
end
memory["last_run"] = JSON.generate(checks)
end
end
end
end
1 change: 1 addition & 0 deletions spec/data_fixtures/pingdom_checks.json
@@ -0,0 +1 @@
{"checks":[{"id":94638,"created":1245088418,"name":"FaucetDirect.com","hostname":"www.faucetdirect.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401698723,"lasttesttime":1401771767,"lastresponsetime":794,"status":"up","tags":[]},{"id":114126,"created":1252561766,"name":"FaucetDirect Search","hostname":"www.faucetdirect.com","use_legacy_notifications":true,"resolution":5,"type":"http","lasterrortime":1401257527,"lasttesttime":1401771722,"lastresponsetime":713,"status":"up","tags":[]},{"id":94639,"created":1245088532,"name":"LightingDirect.com","hostname":"www.lightingdirect.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257745,"lasttesttime":1401771765,"lastresponsetime":287,"status":"up","tags":[]},{"id":241921,"created":1289945581,"name":"LightingDirect Search","hostname":"www.lightingdirect.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401755432,"lasttesttime":1401771752,"lastresponsetime":534,"status":"up","tags":[]},{"id":94642,"created":1245092107,"name":"OMC","hostname":"www.omconsole.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1400401419,"lasttesttime":1401771760,"lastresponsetime":560,"status":"up","tags":[]},{"id":94674,"created":1245102658,"name":"VentingDirect.com","hostname":"www.VentingDirect.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257593,"lasttesttime":1401771733,"lastresponsetime":285,"status":"up","tags":[]},{"id":94678,"created":1245103071,"name":"FTP Server","hostname":"ftp.improvementdirect.com","use_legacy_notifications":true,"resolution":5,"type":"tcp","lasterrortime":1396901076,"lasttesttime":1401771576,"lastresponsetime":112,"status":"up","tags":[]},{"id":190374,"created":1276623657,"name":"Faucet.com","hostname":"www.faucet.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1400400467,"lasttesttime":1401771767,"lastresponsetime":256,"status":"up","tags":[]},{"id":155191,"created":1266302703,"name":"PullsDirect Search","hostname":"www.pullsdirect.com","use_legacy_notifications":true,"resolution":5,"type":"http","lasterrortime":1401257523,"lasttesttime":1401771715,"lastresponsetime":517,"status":"up","tags":[]},{"id":620780,"created":1344272572,"name":"PullsDirect.com","hostname":"www.pullsdirect.com","use_legacy_notifications":false,"resolution":1,"type":"http","lasterrortime":1401257947,"lasttesttime":1401771727,"lastresponsetime":725,"status":"up","tags":[]},{"id":198729,"created":1279216694,"name":"Build.com","hostname":"www.build.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257688,"lasttesttime":1401771768,"lastresponsetime":336,"status":"up","tags":[]},{"id":1182720,"created":1397071551,"name":"Test Check","hostname":"www.build.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257798,"lasttesttime":1401771758,"lastresponsetime":229,"status":"up","tags":[]},{"id":200917,"created":1279817292,"name":"Build Corp","hostname":"corp.build.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257588,"lasttesttime":1401771728,"lastresponsetime":474,"status":"up","tags":[]},{"id":387083,"created":1312176274,"name":"LightingShowplace.com","hostname":"www.lightingshowplace.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1400400446,"lasttesttime":1401771746,"lastresponsetime":582,"status":"up","tags":[]},{"id":553866,"created":1335906463,"name":"Floormall.com","hostname":"www.floormall.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257914,"lasttesttime":1401771770,"lastresponsetime":425,"status":"up","tags":[]},{"id":554890,"created":1335999944,"name":"Image Farm 1","hostname":"s1.img-b.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasttesttime":1401771734,"lastresponsetime":164,"status":"up","tags":[]},{"id":554891,"created":1336000033,"name":"Image Farm 2","hostname":"s2.img-b.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasttesttime":1401771755,"lastresponsetime":32,"status":"up","tags":[]},{"id":554895,"created":1336000625,"name":"Build Mobile","hostname":"m.build.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1400402093,"lasttesttime":1401771732,"lastresponsetime":118,"status":"up","tags":[]},{"id":770979,"created":1361765567,"name":"Chico Office - Otterson","hostname":"50.203.70.193","use_legacy_notifications":true,"resolution":1,"type":"ping","lasterrortime":1397736227,"lasttesttime":1401771751,"lastresponsetime":160,"status":"up","tags":[]},{"id":1015443,"created":1383536804,"name":"build.ca","hostname":"www.build.ca","use_legacy_notifications":true,"resolution":5,"type":"http","lasterrortime":1397062022,"lasttesttime":1401771562,"lastresponsetime":622,"status":"up","tags":[]},{"id":94672,"created":1245102550,"name":"Handlesets.com","hostname":"www.handlesets.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257647,"lasttesttime":1401771727,"lastresponsetime":354,"status":"up","tags":[]},{"id":94676,"created":1245102840,"name":"VentingPipe.com","hostname":"www.ventingpipe.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257786,"lasttesttime":1401771746,"lastresponsetime":356,"status":"up","tags":[]},{"id":126426,"created":1257461901,"name":"Handlesets Search","hostname":"www.handlesets.com","use_legacy_notifications":true,"resolution":1,"type":"http","lasterrortime":1401257786,"lasttesttime":1401771746,"lastresponsetime":486,"status":"up","tags":[]}]}
74 changes: 74 additions & 0 deletions spec/models/agents/pingdom_agent_spec.rb
@@ -0,0 +1,74 @@
require 'spec_helper'

describe Agents::PingdomAgent do
before(:each) do
stub_request(:get, /api.pingdom.com/).to_return(:body => File.read(Rails.root.join("spec/data_fixtures/pingdom_checks.json")), :status => 200, :headers => {"Content-Type" => "text/json"})

@valid_params = {
:pingdom_url => 'https://api.pingdom.com/api/2.0',
:pingdom_credref => 'user@somewhere.com',
:pingdom_apikey => 'key4you...',
:expected_update_period_in_days => '14',
}

@checker = Agents::PingdomAgent.new(:name => "pingdom-agent", :options => @valid_params)
@checker.user = users(:jane)
@checker.save!
end

describe "validating" do
before do
@checker.should be_valid
end

it "should require the pingdom_credref" do
@checker.options['pingdom_credref'] = nil
@checker.should_not be_valid
end

it "should require the pingdom url" do
@checker.options['pingdom_url'] = nil
@checker.should_not be_valid
end

it "should require the pingdom apikey" do
@checker.options['pingdom_apikey'] = nil
@checker.should_not be_valid
end

it "should require the expected_update_period_in_days" do
@checker.options['expected_update_period_in_days'] = nil
@checker.should_not be_valid
end
end

describe "helpers" do
it "should generate a valid credential reference" do
@checker.send(:pingdom_credref).should == "user@somewhere.com"
end

it "should generate a correct request url" do
@checker.send(:pingdom_url, 'checks').should == "https://api.pingdom.com/api/2.0/checks"
end
end

describe "#check" do
it "should be able to retrieve issues" do
reply = File.read(Rails.root.join("spec/data_fixtures/pingdom_checks.json"))
mock(@checker).get("https://api.pingdom.com/api/2.0/checks", {"App-Key"=>"key4you...", :content_type => :json}).returns(reply)

expect { @checker.check }.to change { Event.count }.by(23)
end
end

describe '#working?' do
it 'checks if events have been received within the expected receive period' do
@checker.should_not be_working # No events received
@checker.check
@checker.reload.should be_working # Just received events
fourteen_days_from_now = 14.days.from_now
stub(Time).now { fourteen_days_from_now }
@checker.reload.should_not be_working # More time has passed than the expected receive period without any new events
end
end
end

0 comments on commit ab61f07

Please sign in to comment.