app/models/rearview/job.rb
module Rearview
class Job < ActiveRecord::Base
include Rearview::ConstantsModuleMaker
include Rearview::Ext::StateMachine
self.table_name = "jobs"
make_constants_module :status,
:constants => [:success,:failed,:error,:graphite_error,:graphite_metric_error,:security_error]
attr_writer :deep_validation
attr_accessible :created_at, :updated_at, :name, :active, :last_run,
:cron_expr, :status, :user_id, :alert_keys, :deleted_at, :error_timeout,
:next_run, :description, :app_id, :metrics, :monitor_expr, :minutes,
:to_date
belongs_to :dashboard, :foreign_key => :app_id
belongs_to :user
has_one :job_data, :dependent => :destroy
has_many :job_errors, :dependent => :destroy
serialize :metrics, JSON
serialize :alert_keys, JSON
before_save :set_defaults
before_update :set_defaults
before_destroy :unschedule
validates :app_id, :cron_expr, :name, :metrics, :presence => true
validates :cron_expr, :'rearview/cron_expression' => true, :if => :deep_validation?
validates :metrics, :'rearview/metrics' => true, :if => :deep_validation?
validate :valid_alert_keys, :if => :deep_validation?
scope :schedulable, -> { where(:active=>true) }
state_machine :status, :initial => nil do
error_group = [:failed,:error,:graphite_metric_error,:graphite_error,:security_error]
after_transition nil => any, :do => :create_associated_event
after_transition :success => error_group , :do => :create_associated_event
after_transition error_group => :success, :do => :create_associated_event
after_transition :success => :success, :do => :update_associated_event
after_transition error_group => error_group, :do => :update_associated_event
event :success do
transition all => :success
end
event :failed do
transition all => :failed
end
event :error do
transition all => :error
end
event :graphite_error do
transition all => :graphite_error
end
event :graphite_metric_error do
transition all => :graphite_metric_error
end
event :security_error do
transition all => :security_error
end
state :success
state :failed
state :error
state :graphite_error
state :graphite_metric_error
state :security_error
end
def schedule
Rearview.monitor_service.schedule(self)
end
def unschedule
Rearview.monitor_service.unschedule(self)
end
def reset
self.unschedule if active
self.job_data.destroy unless(self.job_data.nil?)
self.job_errors.clear
self.status = nil
self.save!
self.reload
self.schedule if active
end
# The number of seconds to delay before the next time this job should run
def delay
if cron_expr == "0 * * * * ?"
60.0
else
Rearview::CronHelper.next_valid_time_after(cron_expr)
end
end
# This doesn't fit nicely as a callback -- the monitor itself needs to update
# the job which triggers a fun cycle of events =)
def sync_monitor_service
if active
schedule
else
unschedule
end
end
def valid_alert_keys
if alert_keys.present?
schemes = Rearview::Alerts.registry.keys
alert_keys.each do |key|
begin
uri = URI(key)
scheme = uri.scheme
unless scheme.present? && schemes.include?(scheme)
errors.add(:alert_keys,"#{key} is not a supported alert type")
else
scheme_class = Rearview::Alerts.registry[scheme]
unless scheme_class.key?(key)
errors.add(:alert_keys,"#{key} is invalid for supported alert type")
end
end
rescue URI::InvalidURIError, URI::InvalidComponentError
errors.add(:alert_keys,"#{key} is an invalid URI")
end
end
end
end
def create_associated_event(transition)
report_transition(transition)
job_error_attrs = {}
job_error_attrs.merge!(event_data[:job_error]) if event_data.try(:[],:job_error)
job_error = job_errors.create(job_error_attrs)
job_error.fire_event(translate_associated_event(transition),event_data)
end
def update_associated_event(transition)
report_transition(transition)
job_error = Rearview::JobError.latest_entry(self)
if job_error.present?
job_error.fire_event(translate_associated_event(transition),event_data)
end
end
def deep_validation?
@deep_validation
end
protected
def report_transition(transition)
Rearview::Statsd.report do |stats|
metric = ( transition.event.to_s == 'success' ? 'success' : 'failure' )
stats.increment("monitor.#{metric}")
end
rescue
logger.error "#{self} report_transition failed: #{$!}\n#{$@.join("\n")}"
end
def translate_associated_event(transition)
if transition.event.to_s == Status::SECURITY_ERROR
:error
else
transition.event
end
end
def set_defaults
unless self.alert_keys.present?
self.alert_keys = []
end
end
end
end