app/models/miq_alert.rb
class MiqAlert < ApplicationRecord
include UuidMixin
include ReadOnlyMixin
SEVERITIES = [nil, "info", "warning", "error"]
serialize :miq_expression
serialize :hash_expression
serialize :options
validates :description, :presence => true, :uniqueness_when_changed => true, :length => {:maximum => 255}
validate :validate_automate_expressions
validate :validate_single_expression
validates :severity, :inclusion => {:in => SEVERITIES}
has_many :miq_alert_statuses, :dependent => :destroy
before_save :set_responds_to_events
attr_accessor :reserved
BASE_TABLES = %w[
Vm
Host
Storage
EmsCluster
ExtManagementSystem
MiqServer
ContainerNode
ContainerProject
PhysicalServer
]
def self.base_tables
BASE_TABLES
end
acts_as_miq_set_member
ASSIGNMENT_PARENT_ASSOCIATIONS = %i[host ems_cluster ext_management_system my_enterprise physical_server].freeze
HOURLY_TIMER_EVENT = "_hourly_timer_"
cache_with_timeout(:alert_assignments) { {} }
virtual_column :based_on, :type => :string
virtual_column :evaluation_description, :type => :string
virtual_column :notify_automate, :type => :boolean
virtual_column :notify_email, :type => :boolean
virtual_column :notify_evm_event, :type => :boolean
virtual_column :notify_snmp, :type => :boolean
def based_on
Dictionary.gettext(db, :type => :model)
end
def expression=(exp)
if exp.kind_of?(MiqExpression)
self.miq_expression = exp
elsif exp.kind_of?(Hash)
self.hash_expression = exp
end
end
def expression
miq_expression || hash_expression
end
def miq_expression=(exp)
super(exp.nil? || exp.kind_of?(MiqExpression) ? exp : MiqExpression.new(exp))
end
def evaluation_description
return "Expression (Custom)" if miq_expression
return "None" unless hash_expression && hash_expression.key?(:eval_method)
exp = self.class.expression_by_name(hash_expression[:eval_method])
exp ? exp[:description] : "Unknown"
end
# Define methods for notify_* virtual columns
[:automate, :email, :evm_event, :snmp].each do |n|
define_method(:"notify_#{n}") do
(options || {}).has_key_path?(:notifications, n)
end
end
def miq_actions
[]
end
alias_method :actions, :miq_actions
alias_method :owning_miq_actions, :miq_actions
def set_responds_to_events
events = responds_to_events_from_expression
self.responds_to_events = events unless events.nil?
end
def validate_automate_expressions
# if always_evaluate = true, delay_next_evaluation must be 0
valid = true
automate_expression = if hash_expression && self.class.expression_by_name(hash_expression[:eval_method])
self.class.expression_by_name(hash_expression[:eval_method])
else
{}
end
next_frequency = (options || {}).fetch_path(:notifications, :delay_next_evaluation)
if automate_expression[:always_evaluate] && next_frequency != 0
valid = false
errors.add(:notifications, "Datawarehouse alerts must have a 0 notification frequency")
end
valid
end
def validate_single_expression
if miq_expression && hash_expression
errors.add("Alert", "must not have both miq_expression and hash_expression set")
end
end
def self.assigned_to_target(target, event = nil)
# Get all assigned, enabled alerts based on target class and event
log_header = "[#{event}]"
log_target = "Target: #{target.class.name} Name: [#{target.name}], Id: [#{target.id}]"
# event can be nil, so the compact removes event if it is nil
key = [target.class.base_model.name, target.id, event].compact.join("_")
alert_assignments[key] ||= begin
profiles = MiqAlertSet.assigned_to_target(target)
alert_ids = profiles.flat_map { |p| p.members.pluck(:id) }.uniq
if alert_ids.empty?
none
else
msg = "Filtering for enabled alerts"
scope = where(:id => alert_ids, :enabled => true, :db => target.class.base_model.name)
if event
scope = scope.where("responds_to_events like ?", "%#{event}%")
msg << " with responds_to_events LIKE: #{event}"
end
_log.info("#{log_header} #{log_target} #{msg}")
scope
end
end
end
def self.target_needs_realtime_capture?(target)
!assigned_to_target(target, "#{target.class.base_model.name.underscore}_perf_complete").empty?
end
def self.normalize_target(target)
if target.kind_of?(Array)
klass, id = target
klass = Object.const_get(klass)
target = klass.find_by(:id => id)
raise "Unable to find object with class: [#{klass}], Id: [#{id}]" unless target
end
target
end
def self.evaluate_alerts(target, event, inputs = {})
target = normalize_target(target)
log_header = "[#{event}]"
log_target = "Target: #{target.class.name} Name: [#{target.name}], Id: [#{target.id}]"
_log.info("#{log_header} #{log_target}")
enabled_assigned_alerts = assigned_to_target(target, event)
_log.warn("#{log_header} #{log_target} Result: No enabled alerts are assigned to target! Nothing to do.") if enabled_assigned_alerts.empty?
enabled_assigned_alerts.each do |a|
next if a.postpone_evaluation?(target)
_log.info("#{log_header} #{log_target} Queuing evaluation of Alert: [#{a.description}]")
a.evaluate_queue(target, inputs)
end
end
def self.evaluate_hourly_timer
_log.info("Starting")
# Find all active alerts that respond to _hourly_timer_ that have assignments
# assignments = MiqAlert.assignments(:conditions => ["enabled = ? and responds_to_events like ?", true, "%#{HOURLY_TIMER_EVENT}%"])
# TODO: Optimize to filter out sets that don't have any enabled alerts that respond to HOURLY_TIMER_EVENT
assignments = MiqAlertSet.assignments
zone = MiqServer.my_server.zone
# Get list of targets from assigned profiles
targets = []
assignments.values.flatten.uniq.each do |prof|
prof.miq_alerts.each do |a|
next unless a.enabled? && a.responds_to_events && a.responds_to_events.include?(HOURLY_TIMER_EVENT)
# Targets may come from tables such as:
# ems_clusters, storages, hosts, ext_management_systems, miq_servers, vms
table_name = a.db.constantize.table_name
targets += zone.public_send(table_name)
targets += Zone.public_send(:"#{table_name}_without_a_zone") if Zone.respond_to?(:"#{table_name}_without_a_zone")
end
end
# Call evaluate_queue for each alert/target
targets.uniq.each { |t| evaluate_alerts(t, HOURLY_TIMER_EVENT) }
_log.info("Complete")
end
def evaluate_queue(targets, inputs = {})
Array.wrap(targets).each do |target|
zone = target.respond_to?(:my_zone) ? target.my_zone : MiqServer.my_zone
MiqQueue.put_unless_exists(
:class_name => self.class.name,
:instance_id => id,
:method_name => "evaluate",
:args => [[target.class.name, target.id], inputs],
:zone => zone
)
end
end
def postpone_evaluation?(target)
# TODO: Are there some alerts that we always want to evaluate?
# If a miq alert status exists for our resource and alert, and it has not been delay_next_evaluation seconds since
# it was evaluated, return true so we can skip evaluation
delay_next_evaluation = (options || {}).fetch_path(:notifications, :delay_next_evaluation)
start_skipping_at = Time.now.utc - (delay_next_evaluation || 10.minutes).to_i
statuses_not_expired = miq_alert_statuses.where(:resource => target, :result => true)
.where(miq_alert_statuses.arel_table[:evaluated_on].gt(start_skipping_at))
if statuses_not_expired.count > 0
_log.info("Skipping re-evaluation of Alert [#{description}] for target: [#{target.name}] with delay_next_evaluation [#{delay_next_evaluation}]")
true
else
false
end
end
def evaluate(target, inputs = {})
target = self.class.normalize_target(target)
return if postpone_evaluation?(target)
_log.info("Evaluating Alert [#{description}] for target: [#{target.name}]...")
result = eval_expression(target, inputs)
_log.info("Evaluating Alert [#{description}] for target: [#{target.name}]... Result: [#{result}]")
# If we are alerting, invoke the alert actions, then add a status so we can limit how often to alert
# Otherwise, destroy this alert's statuses for our target
invoke_actions(target, inputs) if result
add_status_post_evaluate(target, result, inputs[:ems_event])
result
end
def add_status_post_evaluate(target, result, event)
status_description, event_severity, url, resolved = event.try(:parse_event_metadata)
ems_ref = event.try(:ems_ref)
status = miq_alert_statuses.find_or_initialize_by(:resource => target, :event_ems_ref => ems_ref)
status.result = result
status.ems_id = target.try(:ems_id)
status.ems_id ||= target.id if target.is_a?(ExtManagementSystem)
status.description = status_description || description
status.severity = severity
status.severity = event_severity if event_severity.present?
status.url = url if url.present?
status.event_ems_ref = ems_ref if ems_ref.present?
status.resolved = resolved
status.evaluated_on = Time.now.utc
status.save!
miq_alert_statuses << status
end
def invoke_actions(target, inputs = {})
build_actions.each do |a|
if a.kind_of?(MiqAction)
inputs = inputs.merge(:policy => self, :event => MiqEventDefinition.new(:name => "AlertEvent", :description => "Alert condition met"))
a.invoke(target, inputs.merge(:result => true, :sequence => a.sequence, :synchronous => false))
else
next if a == :delay_next_evaluation
method = "invoke_#{a}"
unless respond_to?(method)
_log.warn("Unknown notification type: [#{a}], skipping invocation")
next
end
send(method, target, inputs)
end
end
rescue MiqException::StopAction => err
_log.error("Stopping action invocation [#{err.message}]")
nil
rescue MiqException::UnknownActionRc => err
_log.error("Aborting action invocation [#{err.message}]")
raise
rescue MiqException::PolicyPreventAction => err
_log.info("[#{err}]")
raise
end
def invoke_automate(target, inputs)
event = options.fetch_path(:notifications, :automate, :event_name)
event_obj = CustomEvent.create(
:event_type => event,
:target => target,
:source => 'Alert'
)
inputs = {
:miq_alert_description => description,
:miq_alert_id => id,
:alert_guid => guid,
'EventStream::event_stream' => event_obj.id,
:event_stream_id => event_obj.id
}
MiqQueue.put(
:class_name => "MiqAeEvent",
:method_name => "raise_evm_event",
:args => [event, [target.class.name, target.id], inputs],
:role => 'automate',
:priority => MiqQueue::HIGH_PRIORITY,
:zone => target.respond_to?(:my_zone) ? target.my_zone : MiqServer.my_zone
)
end
def build_actions
actions = []
notifications = (self.options ||= {})[:notifications]
notifications.each_key do |k|
if k == :email
Array.wrap(notifications[k]).each do |n|
n[:to].each do |to|
description = "#{k.to_s.titleize} Action To: [#{to}] for Alert: #{self.description}"
actions << MiqAction.new(
:action_type => k.to_s,
:options => {:from => n[:from], :to => to},
:name => description,
:description => description
)
end
end
elsif k == :snmp
Array.wrap(notifications[k]).each do |n|
description = "#{k.to_s.titleize} Action for Alert: #{self.description}"
action_type = "snmp_trap"
actions << MiqAction.new(
:action_type => action_type,
:options => n,
:name => description,
:description => description
)
end
elsif k == :evm_event
description = "#{k.to_s.titleize} Action for Alert: #{self.description}"
action_type = "evm_event"
actions << MiqAction.new(
:action_type => action_type,
:name => description,
:description => description
)
else
actions << k
end
end
actions
end
def eval_expression(target, inputs = {})
return Condition.evaluate(self, target, inputs) if miq_expression
return true if hash_expression && hash_expression[:eval_method] == "nothing"
raise "unable to evaluate expression: [#{miq_expression.inspect}], unknown format" unless hash_expression
case hash_expression[:mode]
when "internal" then evaluate_internal(target, inputs)
when "automate" then evaluate_in_automate(target, inputs)
when "script" then evaluate_script
else raise "unable to evaluate expression: [#{hash_expression.inspect}], unknown mode"
end
end
def self.rt_perf_model_details(dbs)
dbs.each_with_object({}) do |db, h|
h[db] = Metric::Rollup.const_get("#{db.underscore.upcase}_REALTIME_COLS").each_with_object({}) do |c, hh|
db_column = "#{db}Performance.#{c}" # this is to prevent the string from being collected during string extraction
hh[c.to_s] = Dictionary.gettext(db_column)
end
end
end
def self.operating_range_perf_model_details(dbs)
dbs.each_with_object({}) do |db, h|
h[db] = Metric::LongTermAverages::AVG_COLS.each_with_object({}) do |c, hh|
db_column = "#{db}Performance.#{c}" # this is to prevent the string from being collected during string extraction
hh[c.to_s] = Dictionary.gettext(db_column)
end
end
end
def self.hourly_perf_model_details(dbs)
dbs.each_with_object({}) do |db, h|
perf_model = "#{db}Performance"
h[db] = MiqExpression.model_details(perf_model, :include_model => false, :interval => "hourly").each_with_object({}) do |a, hh|
d, c = a
model, col = c.split("-")
next(hh) unless model == perf_model
next(hh) if ["timestamp", "v_date", "v_time", "resource_name"].include?(col)
next(hh) if col.starts_with?("abs_") && col.ends_with?("_timestamp")
hh[col] = d
end
end
end
def self.automate_expressions
@automate_expressions ||= [
{:name => "nothing", :description => N_(" Nothing"), :db => BASE_TABLES, :options => []},
{:name => "ems_alarm", :description => N_("VMware Alarm"), :db => ["Vm", "Host", "EmsCluster"], :responds_to_events => 'AlarmStatusChangedEvent_#{hash_expression[:options][:ems_id]}_#{hash_expression[:options][:ems_alarm_mor]}',
:options => [
{:name => :ems_id, :description => N_("Management System")},
{:name => :ems_alarm_mor, :description => N_("Alarm")}
]},
{:name => "event_threshold", :description => N_("Event Threshold"), :db => ["Vm"], :responds_to_events => '#{hash_expression[:options][:event_types]}',
:options => [
{:name => :event_types, :description => N_("Event to Check"), :values => ["CloneVM_Task", "CloneVM_Task_Complete", "DrsVmPoweredOnEvent", "MarkAsTemplate_Complete", "MigrateVM_Task", "PowerOnVM_Task_Complete", "ReconfigVM_Task_Complete", "ResetVM_Task_Complete", "ShutdownGuest_Complete", "SuspendVM_Task_Complete", "UnregisterVM_Complete", "VmPoweredOffEvent", "RelocateVM_Task_Complete"]},
{:name => :time_threshold, :description => N_("How Far Back to Check"), :required => true},
{:name => :freq_threshold, :description => N_("Event Count Threshold"), :required => true, :numeric => true}
]},
{:name => "event_log_threshold", :description => N_("Event Log Threshold"), :db => ["Vm"], :responds_to_events => "vm_scan_complete",
:options => [
{:name => :event_log_message_filter_type, :description => N_("Message Filter Type"), :values => ["STARTS WITH", "ENDS WITH", "INCLUDES", "REGULAR EXPRESSION"], :required => true},
{:name => :event_log_message_filter_value, :description => N_("Message Filter"), :required => true},
{:name => :event_log_name, :description => N_("Event Log Name")},
{:name => :event_log_level, :description => N_("Event Level")},
{:name => :event_log_event_id, :description => N_("Event Id")},
{:name => :event_log_source, :description => N_("Event Source")},
{:name => :time_threshold, :description => N_("How Far Back to Check"), :required => true},
{:name => :freq_threshold, :description => N_("Event Count Threshold"), :required => true, :numeric => true}
]},
{:name => "hostd_log_threshold", :description => N_("Hostd Log Threshold"), :db => ["Host"], :responds_to_events => "host_scan_complete",
:options => [
{:name => :event_log_message_filter_type, :description => N_("Message Filter Type"), :values => ["STARTS WITH", "ENDS WITH", "INCLUDES", "REGULAR EXPRESSION"], :required => true},
{:name => :event_log_message_filter_value, :description => N_("Message Filter"), :required => true},
{:name => :event_log_level, :description => N_("Message Level")},
{:name => :event_log_source, :description => N_("Message Source")},
{:name => :time_threshold, :description => N_("How Far Back to Check"), :required => true},
{:name => :freq_threshold, :description => N_("Event Count Threshold"), :required => true, :numeric => true}
]},
{:name => "realtime_performance", :description => N_("Real Time Performance"), :db => (dbs = ["Vm", "Host", "EmsCluster"]), :responds_to_events => '#{db.underscore}_perf_complete',
:options => [
{:name => :perf_column, :description => N_("Performance Field"), :values => rt_perf_model_details(dbs)},
{:name => :operator, :description => N_("Operator"), :values => [">", ">=", "<", "<=", "="]},
{:name => :value_threshold, :description => N_("Value Threshold"), :required => true},
{:name => :trend_direction, :description => N_("And is Trending"), :required => true, :values => {"none" => " Don't Care", "up" => "Up", "up_more_than" => "Up More Than", "down" => "Down", "down_more_than" => "Down More Than", "not_up" => "Not Up", "not_down" => "Not Down"}},
{:name => :trend_steepness, :description => N_("Per Minute"), :required => false},
{:name => :rt_time_threshold, :description => N_("Field Meets Criteria for"), :required => true},
{:name => :debug_trace, :description => N_("Debug Tracing"), :required => true, :values => ["false", "true"]},
]},
{:name => "operating_range_exceptions", :description => N_("Normal Operating Range"), :db => (dbs = ["Vm"]), :responds_to_events => "vm_perf_complete",
:options => [
{:name => :perf_column, :description => N_("Performance Field"), :values => operating_range_perf_model_details(dbs)},
{:name => :operator, :description => N_("Operator"), :values => ["Exceeded", "Fell Below"]},
{:name => :rt_time_threshold, :description => N_("Field Meets Criteria for"), :required => true}
]},
{:name => "hourly_performance", :description => N_("Hourly Performance"), :db => (dbs = ["EmsCluster"]), :responds_to_events => "_hourly_timer_",
:options => [
{:name => :perf_column, :description => N_("Performance Field"), :values => hourly_perf_model_details(dbs)},
{:name => :operator, :description => N_("Operator"), :values => [">", ">=", "<", "<=", "="]},
{:name => :value_threshold, :description => N_("Value Threshold"), :required => true},
{:name => :trend_direction, :description => N_("And is Trending"), :required => true, :values => {"none" => " Don't Care", "up" => "Up", "down" => "Down", "not_up" => "Not Up", "not_down" => "Not Down"}},
{:name => :hourly_time_threshold, :description => N_("Field Meets Criteria for"), :required => true},
{:name => :debug_trace, :description => N_("Debug Tracing"), :required => true, :values => ["false", "true"]},
]},
{:name => "reconfigured_hardware_value", :description => N_("Hardware Reconfigured"), :db => ["Vm"], :responds_to_events => "vm_reconfigure",
:options => [
{:name => :hdw_attr, :description => N_("Hardware Attribute"), :values => {:memory_mb => Dictionary.gettext("memory_mb", :type => "column"), :cpu_total_cores => Dictionary.gettext("cpu_total_cores", :type => "column")}},
{:name => :operator, :description => N_("Operator"), :values => ["Increased", "Decreased"]}
]},
{:name => "changed_vm_value", :description => N_("VM Value changed"), :db => ["Vm"], :responds_to_events => "vm_reconfigure",
:options => [
{:name => :hdw_attr, :description => N_("VM Attribute"), :values => {
:cpu_affinity => Dictionary.gettext("cpu_affinity", :type => "column")
}},
{:name => :operator, :description => N_("Operator"), :values => ["Changed"]}
]},
{:name => "dwh_generic", :description => N_("External Prometheus Alerts"), :db => ["ContainerNode", "ExtManagementSystem"], :responds_to_events => "datawarehouse_alert",
:options => [], :always_evaluate => true}
]
end
EVM_TYPE_TO_VIM_TYPE = {
"Vm" => "VirtualMachine",
"Host" => "HostSystem",
"EmsCluster" => "ClusterComputeResource",
"Storage" => "Datastore"
}
# TODO: vmware specific
def self.ems_alarms(db, ems = nil)
ems = ExtManagementSystem.extract_objects(ems)
raise "Unable to find Management System with id: [#{id}]" if ems.nil?
to = 30
alarms = []
begin
Timeout.timeout(to) { alarms = ems.get_alarms if ems.respond_to?(:get_alarms) }
rescue Timeout::Error
msg = "Request to retrieve alarms timed out after #{to} seconds"
$log.warn(msg)
raise msg
rescue => err
$log.warn("'#{err.message}', attempting to retrieve alarms")
raise
end
alarms.each_with_object({}) do |a, h|
exp = a.fetch_path("info", "expression", "expression")
next(h) unless exp
next(h) unless exp.detect { |e| e["type"] == EVM_TYPE_TO_VIM_TYPE[db] || e["objectType"] == EVM_TYPE_TO_VIM_TYPE[db] }
h[a["MOR"]] = a["info"]["name"]
end
end
def self.expression_types(db = nil)
automate_expressions.each_with_object({}) do |e, h|
next(h) unless db.nil? || e[:db].nil? || e[:db].include?(db)
h[e[:name]] = e[:description]
end
end
def self.expression_options(name)
exp = expression_by_name(name)
return nil unless exp
exp[:options]
end
def self.expression_by_name(name)
automate_expressions.find { |e| e[:name] == name }
end
def self.raw_events
@raw_events ||= expression_by_name("event_threshold")[:options].find { |h| h[:name] == :event_types }[:values] +
%w[datawarehouse_alert]
end
def self.event_alertable?(event)
raw_events.include?(event.to_s)
end
def self.alarm_has_alerts?(alarm_event)
where("responds_to_events LIKE ?", "%#{alarm_event}%").count > 0
end
def responds_to_events_from_expression
return nil if miq_expression || hash_expression.nil? || hash_expression[:eval_method] == "nothing"
options = self.class.expression_by_name(hash_expression[:eval_method])
options && substitute(options[:responds_to_events])
end
def substitute(str)
eval("result = \"#{str}\"")
end
def evaluate_in_automate(target, inputs = {})
target_key = target.class.name.singularize.downcase.to_sym
inputs[target_key] = target
[:vm, :host, :ext_management_system].each { |k| inputs[k] = target.send(target_key) if target.respond_to?(target_key) }
aevent = inputs
aevent[:eval_method] = hash_expression[:eval_method]
aevent[:alert_class] = self.class.name.downcase
aevent[:alert_id] = id
aevent[:target_class] = target.class.base_model.name.downcase
aevent[:target_id] = target.id
hash_expression[:options].each { |k, v| aevent[k] = v } if hash_expression[:options]
begin
result = MiqAeEvent.eval_alert_expression(target, aevent)
rescue => err
_log.error(err.message)
result = false
end
result
end
def evaluate_internal(target, _inputs = {})
method = "evaluate_method_#{hash_expression[:eval_method]}"
options = hash_expression[:options] || {}
raise "Evaluation method '#{hash_expression[:eval_method]}' does not exist" unless respond_to?(method)
send(method, target, options)
end
def evaluate_script
# TODO
true
end
def evaluate_method_dwh_generic(target, options)
target.evaluate_alert(id, options)
end
# Evaluation methods
#
def evaluate_method_changed_vm_value(target, options)
eval_options = {
:attr => options[:attr],
:operator => options[:operator]
}
eval_options[:attr] ||= options[:hdw_attr]
target.changed_vm_value?(eval_options)
end
def evaluate_method_event_threshold(target, options)
target.event_threshold?(options)
end
def evaluate_method_event_log_threshold(target, options)
eval_options = {
:message_filter_type => options[:event_log_message_filter_type],
:message_filter_value => options[:event_log_message_filter_value],
:name => options[:event_log_name],
:level => options[:event_log_level],
:event_id => options[:event_log_event_id],
:source => options[:event_log_source],
:time_threshold => options[:time_threshold],
:freq_threshold => options[:freq_threshold]
}
target.event_log_threshold?(eval_options)
end
def evaluate_method_hostd_log_threshold(target, options)
evaluate_method_event_log_threshold(target, options)
end
def evaluate_method_realtime_performance(target, options)
eval_options = {
:interval_name => "realtime",
:duration => options[:rt_time_threshold],
:column => options[:perf_column],
:value => options[:value_threshold],
:operator => options[:operator],
:debug_trace => options[:debug_trace],
:trend_direction => options[:trend_direction],
:slope_steepness => options[:trend_steepness]
}
evaluate_performance(target, eval_options)
end
def evaluate_method_operating_range_exceptions(target, options)
eval_options = {
:interval_name => "realtime",
:duration => options[:rt_time_threshold],
:debug_trace => options[:debug_trace]
}
# options[:perf_column] points to one of keys in vim_performance_operating_ranges's values column
# which stores the long term average data
# eval_options[:column] points to the column in metrics, where data are collected and to be compared
# with the average.
# A column name conversion is needed as follows
eval_options[:column] =
case options[:perf_column]
when "max_cpu_usage_rate_average"
"cpu_usage_rate_average"
when "max_mem_usage_absolute_average"
"mem_usage_absolute_average"
else
options[:perf_column]
end
case options[:operator].downcase
when "exceeded"
eval_options[:operator] = ">"
typ = "high"
when "fell below"
eval_options[:operator] = "<"
typ = "low"
else
raise "operator '#{eval_options[:operator]}' is not valid"
end
val_col_name = "#{options[:perf_column]}_#{typ}_over_time_period"
unless target.respond_to?(val_col_name)
_log.warn("Target class [#{target.class.name}] does not support operating range, skipping")
return false
end
eval_options[:value] = target.send(val_col_name)
if eval_options[:value].nil?
_log.info("Target class [#{target.class.name}] has no data for operating range column '#{val_col_name}', skipping")
return false
end
evaluate_performance(target, eval_options)
end
def evaluate_method_hourly_performance(target, options)
eval_options = {
:interval_name => "hourly",
:duration => options[:hourly_time_threshold],
:column => options[:perf_column],
:value => options[:value_threshold],
:operator => options[:operator],
:debug_trace => options[:debug_trace],
:trend_direction => options[:trend_direction]
}
evaluate_performance(target, eval_options)
end
def evaluate_performance(target, eval_options)
unless target.respond_to?(:performances_maintains_value_for_duration?)
_log.warn("Target class [#{target.class.name}] does not support duration based evaluation, skipping")
return false
end
status = target.miq_alert_statuses.first
if status
since_last_eval = (Time.now.utc - status.evaluated_on)
eval_options[:starting_on] = if since_last_eval >= eval_options[:duration]
(status.evaluated_on + 1)
else
(Time.now.utc - status.evaluated_on).seconds.ago.utc
end
end
target.performances_maintains_value_for_duration?(eval_options)
end
def evaluate_method_reconfigured_hardware_value(target, options)
target.reconfigured_hardware_value?(options)
end
def evaluate_method_ems_alarm(_target, _options)
true
end
def validate
if self.options.kind_of?(Hash) && self.options.fetch_path(:notifications, :automate)
event_name = self.options.fetch_path(:notifications, :automate, :event_name)
unless (event_name =~ /[^a-z0-9_]/i).nil?
errors.add("Event Name", "must be alphanumeric characters and underscores without spaces")
return
end
end
return if miq_expression
return if hash_expression && hash_expression[:eval_method] == "nothing"
if hash_expression[:options].blank?
errors.add("expression", "has no parameters")
return
end
exp_type = self.class.expression_options(hash_expression[:eval_method])
unless exp_type
errors.add("name", "#{hash_expression[:options][:eval_method]} is invalid")
return
end
exp_type.each do |fld|
next if fld[:required] != true
if hash_expression[:options][fld[:name]].blank?
errors.add("field", "'#{fld[:description]}' is required")
next
end
if fld[:numeric] == true && !is_numeric?(hash_expression[:options][fld[:name]])
errors.add("field", "'#{fld[:description]}' must be a numeric")
end
end
end
def name
description
end
def self.seed
action_fixture_file = File.join(FIXTURE_DIR, "miq_alert_default_action.yml")
if File.exist?(action_fixture_file)
action_hash = YAML.load_file(action_fixture_file)
action = MiqAction.new(action_hash)
else
action = nil
end
alert_fixture_file = File.join(FIXTURE_DIR, "miq_alerts.yml")
if File.exist?(alert_fixture_file)
alist = YAML.load_file(alert_fixture_file)
alist.each do |alert_hash|
guid = alert_hash["guid"] || alert_hash[:guid]
rec = find_by(:guid => guid)
if rec.nil?
alert_hash[:read_only] = true
alert = create(alert_hash)
_log.info("Added sample Alert: #{alert.description}")
if action
alert.options ||= {}
alert.options[:notifications] ||= {}
alert.options[:notifications][action.action_type.to_sym] = action.options
alert.save
end
end
end
end
end
def export_to_array
h = attributes
["id", "created_on", "updated_on"].each { |k| h.delete(k) }
[self.class.to_s => h]
end
def export_to_yaml
export_to_array.to_yaml
end
def self.import_from_hash(alert, options = {})
raise "No Alert to Import" if alert.nil?
status = {:class => name, :description => alert["description"], :children => []}
a = find_by(:guid => alert["guid"])
msg_pfx = "Importing Alert: guid=[#{alert["guid"]}] description=[#{alert["description"]}]"
if a.nil?
a = new(alert)
status[:status] = :add
else
status[:old_description] = a.description
a.attributes = alert
status[:status] = :update
end
unless a.valid?
status[:status] = :conflict
status[:messages] = a.errors.full_messages
end
msg = "#{msg_pfx}, Status: #{status[:status]}"
msg += ", Messages: #{status[:messages].join(",")}" if status[:messages]
if options[:preview] == true
MiqPolicy.logger.info("[PREVIEW] #{msg}")
else
MiqPolicy.logger.info(msg)
a.save!
end
return a, status
end
def self.import_from_yaml(fd)
input = YAML.load(fd)
input.collect do |e|
_a, stat = import_from_hash(e[name])
stat
end
end
def self.display_name(number = 1)
n_('Alert', 'Alerts', number)
end
end