app/models/miq_event.rb
class MiqEvent < EventStream
CHILD_EVENTS = {
:assigned_company_tag => {
:Host => [:vms_and_templates],
:EmsCluster => [:all_vms_and_templates],
:Storage => [:vms_and_templates],
:ResourcePool => [:vms_and_templates]
},
:unassigned_company_tag => {
:Host => [:vms_and_templates],
:EmsCluster => [:all_vms_and_templates],
:Storage => [:vms_and_templates],
:ResourcePool => [:vms_and_templates]
}
}
SUPPORTED_POLICY_AND_ALERT_CLASSES = [Host, VmOrTemplate, Storage,
EmsCluster, ResourcePool, MiqServer,
ExtManagementSystem,
ContainerReplicator, ContainerGroup, ContainerProject,
ContainerNode, ContainerImage, PhysicalServer].freeze
CLASS_GROUP_LEVELS = [MiqPolicy::CONDITION_SUCCESS, MiqPolicy::CONDITION_FAILURE].collect { |level| level.downcase.to_sym }
def self.class_group_levels
CLASS_GROUP_LEVELS
end
def self.description
_("Policy Events")
end
def self.group_name(group)
return if group.nil?
group_names_and_levels.dig(:group_names, group.to_sym) || DEFAULT_GROUP_NAME_STR
end
def self.group_and_level(event_type)
by_literal = partition_group_and_level_by_event_type
by_literal[event_type.to_s] || [DEFAULT_GROUP_NAME, DEFAULT_GROUP_LEVEL]
end
private_class_method def self.partition_group_and_level_by_event_type
return @literal_group_and_level_by_event_type if @literal_group_and_level_by_event_type
@literal_group_and_level_by_event_type = {}
# TODO: check if there's a more performant way to look up the miq_sets and miq_set_membership
@literal_group_and_level_by_event_type = MiqEventDefinition.all.each_with_object({}) do |ed, h|
group_name = ed.event_group_name || DEFAULT_GROUP_NAME
h[ed.name] = [group_name.to_sym, DEFAULT_GROUP_LEVEL]
end
end
def self.clear_event_groups_cache
@group_names_and_levels, @literal_group_and_level_by_event_type = nil
end
def self.group_names_and_levels
@group_names_and_levels ||= begin
hash = default_group_names_and_levels
hash[:group_names].merge!(MiqEventDefinitionSet.all.pluck(:name, :description).to_h.symbolize_keys)
group_levels.each do |level|
hash[:group_levels][level] ||= level.to_s.capitalize
end
hash
end
end
def self.raise_evm_event(target, raw_event, inputs = {}, options = {})
# Target may have been deleted if it's a worker
# Target, in that case will be the worker's server.
# The generic raw_event remains, but client can pass the :type of the worker spawning the event:
# ex: MiqEvent.raise_evm_event(w.miq_server, "evm_worker_not_responding", :type => "MiqGenericWorker", :event_details => "MiqGenericWorker with pid 1234 killed due to not responding")
# Policy, automate, and alerting could then consume this type field along with the details
if target.kind_of?(Array)
klass, id = target
target = klass.to_s.constantize.find_by(:id => id)
end
raise "Unable to find object for target: [#{target}]" unless target
event = normalize_event(raw_event.to_s)
if event == 'unknown' && target.class.base_class.in?(SUPPORTED_POLICY_AND_ALERT_CLASSES)
_log.warn("Event #{raw_event} for class [#{target.class.name}] id [#{target.id}] was not raised: #{raw_event} is not defined in MiqEventDefinition")
_log.info("Alert for Event [#{raw_event}]")
MiqAlert.evaluate_alerts(target, raw_event, inputs) if MiqAlert.event_alertable?(raw_event)
return
end
event_obj = build_evm_event(event, target, inputs[:full_data])
inputs.merge!('MiqEvent::miq_event' => event_obj.id, :miq_event_id => event_obj.id)
inputs.merge!('EventStream::event_stream' => event_obj.id, :event_stream_id => event_obj.id)
# save the EmsEvent that are required by some actions later in policy resolution
event_obj.update(:full_data => {:source_event_id => inputs[:ems_event].id}) if inputs[:ems_event]
MiqAeEvent.raise_evm_event(raw_event, target, inputs, options)
event_obj
end
def process_evm_event(inputs = {})
return if target.nil?
return unless target.class.base_class.in?(SUPPORTED_POLICY_AND_ALERT_CLASSES)
raise "Unable to find object with class: [#{target_class}], Id: [#{target_id}]" unless target
results = {}
inputs[:type] ||= target.class.name
inputs[:source_event] = source_event if source_event
inputs[:triggering_type] = event_type
inputs[:triggering_data] = full_data
_log.info("Event Raised [#{event_type}]")
begin
results[:policy] = MiqPolicy.enforce_policy(target, event_type, inputs)
update_with_policy_result(results)
update(:message => 'Policy resolved successfully!')
rescue MiqException::PolicyPreventAction => err
update(:full_data => {:policy => {:prevented => true}}, :message => err.message)
end
_log.info("Alert for Event [#{event_type}]")
results[:alert] = MiqAlert.evaluate_alerts(target, event_type, inputs)
results[:children_events] = self.class.raise_event_for_children(target, event_type, inputs)
results
end
def self.build_evm_event(event, target, full_data = nil)
options = {
:event_type => event,
:target => target,
:full_data => full_data,
:source => 'POLICY',
:timestamp => Time.now.utc
}
user = User.current_user
options.merge!(:user_id => user.id, :group_id => user.current_group.id, :tenant_id => user.current_tenant.id) if user
MiqEvent.create(options)
end
def update_with_policy_result(result = {})
if result.fetch_path(:policy, :actions, :assign_scan_profile)
update(
:full_data => {
:policy => {
:actions => {
:assign_scan_profile => result.fetch_path(:policy, :actions, :assign_scan_profile)
}
}
}
)
end
end
def self.normalize_event(event)
return event if MiqEventDefinition.find_by(:name => event)
"unknown"
end
def self.raise_evm_event_queue_in_region(target, raw_event, inputs = {})
MiqQueue.put(
:zone => nil,
:class_name => name,
:method_name => 'raise_evm_event',
:args => [[target.class.name, target.id], raw_event, inputs]
)
end
def self.raise_evm_event_queue(target, raw_event, inputs = {})
MiqQueue.put(
:class_name => name,
:method_name => 'raise_evm_event',
:args => [[target.class.name, target.id], raw_event, inputs]
)
end
def self.raise_evm_alert_event_queue(target, raw_event, inputs = {})
MiqQueue.put_unless_exists(
:class_name => "MiqAlert",
:method_name => 'evaluate_alerts',
:args => [[target.class.name, target.id], raw_event, inputs]
) if MiqAlert.alarm_has_alerts?(raw_event)
end
def self.raise_evm_job_event(target, options = {}, inputs = {}, q_options = {})
# Eg. options = {:type => "scan", ":prefix => "request, :suffix => "abort"}
options.reverse_merge!(
:type => "scan",
:prefix => nil,
:suffix => nil
)
target_model = target.class.base_model.name.downcase
target_model = "vm" if target_model.match?("template")
base_event = [target_model, options[:type]].join("_")
evm_event = [options[:prefix], base_event, options[:suffix]].compact.join("_")
raise_evm_event(target, evm_event, inputs, q_options)
end
def self.raise_event_for_children(target, raw_event, inputs = {})
child_assocs = CHILD_EVENTS.fetch_path(raw_event.to_sym, target.class.base_class.name.to_sym)
return if child_assocs.blank?
child_event = "#{raw_event}_parent_#{target.class.base_model.name.underscore}"
child_assocs.each do |assoc|
next unless target.respond_to?(assoc)
children = target.send(assoc)
children.each do |child|
_log.info("Raising Event [#{child_event}] for Child [(#{child.class}) #{child.name}] of Parent [(#{target.class}) #{target.name}]")
raise_evm_event_queue(child, child_event, inputs)
end
end
end
def self.event_name_for_target(target, event_suffix)
"#{target.class.base_model.name.underscore}_#{event_suffix}"
end
# return the event that triggered the policy event
def source_event
return @source_event if @source_event
return unless full_data
source_event_id = full_data.fetch_path(:source_event_id)
@source_event = EventStream.find_by(:id => source_event_id) if source_event_id
end
def self.display_name(number = 1)
n_('Event', 'Events', number)
end
end