|
class ReportImporter
|
|
include Foreman::TelemetryHelper
|
|
|
|
delegate :logger, :to => :Rails
|
|
attr_reader :report, :report_scanners
|
|
|
|
# When writing your own Report importer, provide feature(s) of authorized Smart Proxies
|
|
def self.authorized_smart_proxy_features
|
|
@authorized_smart_proxy_features ||= []
|
|
end
|
|
|
|
def self.register_smart_proxy_feature(feature)
|
|
@authorized_smart_proxy_features = (authorized_smart_proxy_features + [ feature ]).uniq
|
|
end
|
|
|
|
def self.unregister_smart_proxy_feature(feature)
|
|
@authorized_smart_proxy_features -= [ feature ]
|
|
end
|
|
|
|
def self.import(raw, proxy_id = nil)
|
|
importer = new(raw, proxy_id)
|
|
importer.import
|
|
importer.report
|
|
end
|
|
|
|
# to be overriden in children
|
|
def report_name_class
|
|
raise NotImplementedError, "#{__method__} not implemented for this report importer"
|
|
end
|
|
|
|
def initialize(raw, proxy_id = nil)
|
|
raise ::Foreman::Exception.new(_('Invalid report')) unless raw.is_a?(Hash) || raw.is_a?(ActionController::Parameters)
|
|
@raw = raw
|
|
@proxy_id = proxy_id
|
|
end
|
|
|
|
def import
|
|
logger.debug { "Processing report: #{raw.inspect}" }
|
|
telemetry = {}
|
|
telemetry_duration_histogram(:report_importer_create, :ms, {type: self.class.name}, telemetry) do
|
|
create_report_and_logs
|
|
end
|
|
if report.persisted?
|
|
telemetry_duration_histogram(:report_importer_refresh, :ms, {type: self.class.name}, telemetry) do
|
|
host.refresh_statuses(statuses_for_refresh)
|
|
end
|
|
create = telemetry[:report_importer_create].try(:round, 1)
|
|
refresh = telemetry[:report_importer_refresh].try(:round, 1)
|
|
logger.info("Imported report for #{name} in #{create} ms, status refreshed in #{refresh} ms")
|
|
end
|
|
end
|
|
|
|
def scan
|
|
logger.info "Scanning report with: #{report_scanners.join(', ')}"
|
|
report_scanners.each do |scanner|
|
|
break if scanner.scan(report, logs)
|
|
end
|
|
logger.debug { "Changes after scanning: #{report.changes.inspect}" }
|
|
end
|
|
|
|
private
|
|
|
|
attr_reader :raw, :proxy_id
|
|
|
|
def name
|
|
@name ||= raw['host']
|
|
end
|
|
|
|
def host
|
|
hostname = name.downcase
|
|
@host ||= Host::Base.find_by_certname(hostname) ||
|
|
Host::Base.find_by_name(hostname) ||
|
|
Host::Managed.new(:name => hostname)
|
|
end
|
|
|
|
def time
|
|
@time ||= Time.parse(raw['reported_at']).utc
|
|
end
|
|
|
|
def logs
|
|
raw['logs'] || []
|
|
end
|
|
|
|
def import_log_messages
|
|
logs.each do |log|
|
|
# Parse the API format
|
|
level = log['log']['level']
|
|
msg = log['log']['messages']['message']
|
|
src = log['log']['sources']['source']
|
|
|
|
message = Message.find_or_create msg
|
|
source = Source.find_or_create src
|
|
|
|
# Symbols get turned into strings via the JSON API, so convert back here if it matches
|
|
# and expected log level. Log objects can't be created without one, so raise if not
|
|
raise(::Foreman::Exception.new(N_("Invalid log level: %s", level))) unless Report::LOG_LEVELS.include?(level)
|
|
|
|
Log.create(:message_id => message.id, :source_id => source.id, :report => report, :level => level.to_sym)
|
|
end
|
|
end
|
|
|
|
def report_status
|
|
raise NotImplementedError
|
|
end
|
|
|
|
def statuses_for_refresh
|
|
HostStatus.status_registry
|
|
end
|
|
|
|
def notify_on_report_error(mail_error_state)
|
|
if report.error?
|
|
# found a report with errors
|
|
# notify via email IF enabled is set to true
|
|
|
|
if host.disabled?
|
|
logger.warn "#{name} is disabled - skipping alert"
|
|
return
|
|
end
|
|
|
|
owners = host.owner.present? ? host.owner.recipients_for(:config_error_state) : []
|
|
users = ConfigManagementError.all_hosts.flat_map(&:users)
|
|
users.select { |user| Host.authorized_as(user, :view_hosts).find(host.id).present? }
|
|
owners.concat users
|
|
if owners.present?
|
|
logger.debug { "sending alert to #{owners.map(&:login).join(',')}" }
|
|
MailNotification[mail_error_state].deliver(report, :users => owners.uniq)
|
|
else
|
|
logger.debug { "no owner or recipients for alert on #{name}" }
|
|
end
|
|
end
|
|
end
|
|
|
|
def create_report_and_logs
|
|
if host.new_record? && !Setting[:create_new_host_when_report_is_uploaded]
|
|
logger.info("skipping report for #{name} as its an unknown host and create_new_host_when_report_is_uploaded setting is disabled")
|
|
@report = report_name_class.new
|
|
return @report
|
|
end
|
|
|
|
# we save the host without validation for two reasons:
|
|
# 1. It might be auto imported, therefore might not be valid (e.g. missing partition table etc)
|
|
# 2. We want this to be fast and light on the db.
|
|
# at this point, the report is important, not the host
|
|
host.save(:validate => false)
|
|
|
|
status = report_status
|
|
# and save our report
|
|
@report = report_name_class.new(:host => host, :reported_at => time, :status => status, :metrics => raw['metrics'])
|
|
|
|
# Run report scanner
|
|
scan
|
|
|
|
@report.save
|
|
@report
|
|
end
|
|
|
|
def report_scanners
|
|
Foreman::Plugin.report_scanner_registry.report_scanners
|
|
end
|
|
end
|