Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add hash_expression to MiqAlert #15315

Merged
merged 1 commit into from
Aug 31, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 47 additions & 27 deletions app/models/miq_alert.rb
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
class MiqAlert < ApplicationRecord
include UuidMixin

serialize :expression
serialize :miq_expression
serialize :hash_expression
serialize :options

validates_presence_of :description, :guid
validates_uniqueness_of :description, :guid
validate :validate_automate_expressions
validate :validate_single_expression

has_many :miq_alert_statuses, :dependent => :destroy
before_save :set_responds_to_events
Expand Down Expand Up @@ -47,11 +49,23 @@ 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 evaluation_description
return "Expression (Custom)" if expression.kind_of?(MiqExpression)
return "None" unless expression && expression.kind_of?(Hash) && expression.key?(:eval_method)
return "Expression (Custom)" if miq_expression
return "None" unless hash_expression && hash_expression.key?(:eval_method)

exp = self.class.expression_by_name(expression[:eval_method])
exp = self.class.expression_by_name(hash_expression[:eval_method])
exp ? exp[:description] : "Unknown"
end

Expand All @@ -76,8 +90,8 @@ def set_responds_to_events
def validate_automate_expressions
# if always_evaluate = true, delay_next_evaluation must be 0
valid = true
automate_expression = if expression.kind_of?(Hash) && self.class.expression_by_name(expression[:eval_method])
self.class.expression_by_name(expression[:eval_method])
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
Expand All @@ -89,6 +103,12 @@ def validate_automate_expressions
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

Expand Down Expand Up @@ -325,16 +345,16 @@ def build_actions
end

def eval_expression(target, inputs = {})
return Condition.evaluate(self, target, inputs) if expression.kind_of?(MiqExpression)
return true if expression.kind_of?(Hash) && expression[:eval_method] == "nothing"
return Condition.evaluate(self, target, inputs) if miq_expression
return true if hash_expression && hash_expression[:eval_method] == "nothing"

raise "unable to evaluate expression: [#{expression.inspect}], unknown format" unless expression.kind_of?(Hash)
raise "unable to evaluate expression: [#{miq_expression.inspect}], unknown format" unless hash_expression

case expression[:mode]
case hash_expression[:mode]
when "internal" then return evaluate_internal(target, inputs)
when "automate" then return evaluate_in_automate(target, inputs)
when "script" then return evaluate_script
else raise "unable to evaluate expression: [#{expression.inspect}], unknown mode"
else raise "unable to evaluate expression: [#{hash_expression.inspect}], unknown mode"
Copy link
Member

@Fryguy Fryguy Aug 25, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not for this PR, but we are mixing evaluation of 2 different expression types into a single method, and that is causing confusion. I think this would be a LOT clearer if the two evaluation strategies were split into methods, with eval_expression branching between those

def eval_expression(target, inputs = {})
  case expression
  when MiqExpression then eval_miq_expression(target, inputs = {})
  when Hash          then eval_hash_expression(target, inputs = {})
  else raise "unable to evaluate expression: [#{expression.inspect}], unknown format"
  end
end

I think I'm also leaning towards using expression where possible in the places where it has been changed to individually check miq_expression, then hash_expression, then nil. Checking for each one in turn is sort of the purpose of the expression method itself.

However, in a method like my suggested eval_hash_expression, where you know you are working with hash_expression, then yes, you would access it directly.

Copy link
Contributor Author

@zakiva zakiva Aug 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Fryguy This is defiantly the direction we are aiming at. As @imtayadeway pointed in an earlier discussion, having both expression types in the same column was the root problem here, which we hope to handle in this PR (and its related schema change). The next step will be trying to better encapsulate these types and their evaluation, maybe with two new tables each with one column (it will probably require more discussion). However, since this PR blocks some other items we are working on we would like to move forward with the current implementation as an initial solution if you are OK with it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds great @zakiva

end
end

Expand Down Expand Up @@ -374,12 +394,12 @@ def self.hourly_perf_model_details(dbs)
def self.automate_expressions
@automate_expressions ||= [
{:name => "nothing", :description => _(" Nothing"), :db => BASE_TABLES, :options => []},
{:name => "ems_alarm", :description => _("VMware Alarm"), :db => ["Vm", "Host", "EmsCluster"], :responds_to_events => 'AlarmStatusChangedEvent_#{expression[:options][:ems_id]}_#{expression[:options][:ems_alarm_mor]}',
{:name => "ems_alarm", :description => _("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 => _("Management System")},
{:name => :ems_alarm_mor, :description => _("Alarm")}
]},
{:name => "event_threshold", :description => _("Event Threshold"), :db => ["Vm"], :responds_to_events => '#{expression[:options][:event_types]}',
{:name => "event_threshold", :description => _("Event Threshold"), :db => ["Vm"], :responds_to_events => '#{hash_expression[:options][:event_types]}',
:options => [
{:name => :event_types, :description => _("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 => _("How Far Back to Check"), :required => true},
Expand Down Expand Up @@ -532,9 +552,9 @@ def self.alarm_has_alerts?(alarm_event)
end

def responds_to_events_from_expression
return nil if expression.nil? || expression.kind_of?(MiqExpression) || expression[:eval_method] == "nothing"
return nil if miq_expression || hash_expression.nil? || hash_expression[:eval_method] == "nothing"

options = self.class.expression_by_name(expression[:eval_method])
options = self.class.expression_by_name(hash_expression[:eval_method])
options.nil? ? nil : substitute(options[:responds_to_events])
end

Expand All @@ -548,13 +568,13 @@ def evaluate_in_automate(target, inputs = {})
[:vm, :host, :ext_management_system].each { |k| inputs[k] = target.send(target_key) if target.respond_to?(target_key) }

aevent = inputs
aevent[:eval_method] = expression[:eval_method]
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

expression[:options].each { |k, v| aevent[k] = v } if expression[:options]
hash_expression[:options].each { |k, v| aevent[k] = v } if hash_expression[:options]

begin
result = MiqAeEvent.eval_alert_expression(target, aevent)
Expand All @@ -570,10 +590,10 @@ def evaluate_internal(target, _inputs = {})
method = "evaluate_middleware"
options = _inputs[:ems_event]
else
method = "evaluate_method_#{expression[:eval_method]}"
options = expression[:options] || {}
method = "evaluate_method_#{hash_expression[:eval_method]}"
options = hash_expression[:options] || {}
end
raise "Evaluation method '#{expression[:eval_method]}' does not exist" unless self.respond_to?(method)
raise "Evaluation method '#{hash_expression[:eval_method]}' does not exist" unless self.respond_to?(method)

send(method, target, options)
end
Expand Down Expand Up @@ -734,28 +754,28 @@ def validate
end
end

return if expression.kind_of?(MiqExpression)
return if expression.kind_of?(Hash) && expression[:eval_method] == "nothing"
return if miq_expression
return if hash_expression && hash_expression[:eval_method] == "nothing"

if expression[:options].blank?
if hash_expression[:options].blank?
errors.add("expression", "has no parameters")
return
end

exp_type = self.class.expression_options(expression[:eval_method])
exp_type = self.class.expression_options(hash_expression[:eval_method])
unless exp_type
errors.add("name", "#{expression[:options][:eval_method]} is invalid")
errors.add("name", "#{hash_expression[:options][:eval_method]} is invalid")
return
end

exp_type.each do |fld|
next if fld[:required] != true
if expression[:options][fld[:name]].blank?
if hash_expression[:options][fld[:name]].blank?
errors.add("field", "'#{fld[:description]}' is required")
next
end

if fld[:numeric] == true && !is_numeric?(expression[:options][fld[:name]])
if fld[:numeric] == true && !is_numeric?(hash_expression[:options][fld[:name]])
errors.add("field", "'#{fld[:description]}' must be a numeric")
end
end
Expand Down
Loading