module ForemanSalt class ReportImporter delegate :logger, :to => :Rails attr_reader :report def self.import(raw, proxy_id = nil) raise ::Foreman::Exception.new(_('Invalid report')) unless raw.is_a?(Hash) raw.map do |host, report| importer = ForemanSalt::ReportImporter.new(host, report, proxy_id) importer.import importer.report end end def initialize(host, raw, proxy_id = nil) @host = find_or_create_host(host) @raw = raw @proxy_id = proxy_id end def import logger.info "processing report for #{@host}" logger.debug { "Report: #{@raw.inspect}" } if @host.new_record? && !Setting[:create_new_host_when_report_is_uploaded] logger.info("skipping report for #{@host} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled") return Report.new end @host.salt_proxy_id ||= @proxy_id @host.last_report = start_time if @raw.is_a? Array process_failures # If Salt sends us only an array, it's a list of fatal failures else process_normal end @host.save(:validate => false) logger.info("Imported report for #{@host} in #{(Time.now - start_time).round(2)} seconds") end private def find_or_create_host(host) @host ||= Host::Base.find_by_name(host) unless @host new = Host::Managed.new(:name => host) new.save(:validate => false) @host = new end @host end def import_log_messages @raw.each do |resource, result| level = if result['changes'].blank? && result['result'] :info elsif !result['result'] :err else :notice end source = Source.find_or_create(resource) message = if result['changes']['diff'] result['changes']['diff'] elsif !result['comment'].blank? result['comment'] else 'No message available' end message = Message.find_or_create(message) Log.create(:message_id => message.id, :source_id => source.id, :report => @report, :level => level) end end def calculate_metrics success = 0 failed = 0 changed = 0 restarted = 0 restarted_failed = 0 time = {} @raw.each do |resource, result| next unless result.is_a? Hash if result['result'] success += 1 if resource.match(/^service_/) && result['comment'].include?('restarted') restarted += 1 elsif !result['changes'].blank? changed += 1 end elsif !result['result'] if resource.match(/^service_/) && result['comment'].include?('restarted') restarted_failed += 1 else failed += 1 end end time[resource] = if result['duration'].is_a? String Float(result['duration'].delete(' ms')) rescue nil else result['duration'] end end time[:total] = time.values.inject(&:+) events = { :total => changed + failed + restarted + restarted_failed, :success => success + restarted, :failure => failed + restarted_failed } changes = { :total => changed + restarted } resources = { 'total' => @raw.size, 'applied' => changed, 'restarted' => restarted, 'failed' => failed, 'failed_restarts' => restarted_failed, 'skipped' => 0, 'scheduled' => 0 } { :events => events, :resources => resources, :changes => changes, :time => time } end def process_normal metrics = calculate_metrics status = ReportStatusCalculator.new(:counters => metrics[:resources].slice(*::Report::METRIC)).calculate @host.puppet_status = status @report = Report.new(:host => @host, :reported_at => start_time, :status => status, :metrics => metrics) return @report unless @report.save import_log_messages end def process_failures status = ReportStatusCalculator.new(:counters => { 'failed' => @raw.size }).calculate @report = Report.create(:host => @host, :reported_at => Time.now, :status => status, :metrics => {}) @host.puppet_status = status source = Source.find_or_create('Salt') @raw.each do |failure| message = Message.find_or_create(failure) Log.create(:message_id => message.id, :source_id => source.id, :report => @report, :level => :err) end end def start_time @start_time ||= Time.now end end end