app/models/log_file.rb
require 'uri'
require 'mount/miq_generic_mount_session'
class LogFile < ApplicationRecord
belongs_to :resource, :polymorphic => true
belongs_to :file_depot
belongs_to :miq_task
LOG_REQUEST_TIMEOUT = 30.minutes
cattr_reader :log_request_timeout
before_destroy :remove
def relative_path_for_upload(loc_file)
server = resource
zone = server.zone
path = "#{zone.name}_#{zone.id}", "#{server.name}_#{server.id}"
date_string = "#{format_log_time(logging_started_on)}_#{format_log_time(logging_ended_on)}"
fname = "#{File.basename(loc_file, ".*").capitalize}_"
fname += "region_#{MiqRegion.my_region.region rescue "unknown"}_#{zone.name}_#{zone.id}_#{server.name}_#{server.id}_#{date_string}#{File.extname(loc_file)}"
dest = File.join("/", path, fname)
_log.info("Built relative path: [#{dest}] from source: [#{loc_file}]")
dest
end
# Base is the URI defined by the user
# loc_file is the name of the original file
def build_log_uri(base_uri, loc_file)
scheme, userinfo, host, port, registry, path, opaque, query, fragment = URI.split(URI::DEFAULT_PARSER.escape(base_uri))
# Convert encoded spaces back to spaces
path.gsub!('%20', ' ')
relpath = relative_path_for_upload(loc_file)
new_path = File.join("/", path, relpath)
uri = URI::HTTP.new(scheme, userinfo, host, port, registry, new_path, opaque, query, fragment).to_s
_log.info("New URI: [#{uri}] from base: [#{base_uri}], and relative path: [#{relpath}]")
uri
end
def upload
# TODO: Currently the ftp code in the LogFile class has LogFile logic for the destination folders (evm_1/server_1) and builds these paths and copies the logs
# appropriately. To make all the various mechanisms work, we need to build a destination URI based on the input filename and pass this along
# so that the nfs, ftp, smb, etc. mechanism have very little LogFile logic and only need to know how decipher the URI and build the directories as appropraite.
raise _("LogFile local_file is nil") unless local_file
unless File.exist?(local_file)
raise _("LogFile local_file: [%{file_name}] does not exist!") % {:file_name => local_file}
end
raise _("Log Depot settings not configured") unless file_depot
method = get_post_method(file_depot.uri)
send(:"upload_log_file_#{method}")
end
def remove
method = get_post_method(log_uri)
return if method.nil?
return send(:"remove_log_file_#{method}") if respond_to?(:"remove_log_file_#{method}")
# At this point ftp should have returned
klass = Object.const_get("Miq#{method.capitalize}Session")
klass.new(legacy_depot_hash).remove(log_uri)
rescue Exception => err
_log.warn("#{err.message}, deleting #{log_uri} from FTP")
end
def file_exists?
return true if log_uri.nil?
method = get_post_method(log_uri)
return true if method.nil?
return send(:"file_exists_#{method}?") if respond_to?(:"file_exists_#{method}?")
# At this point ftp should have returned
klass = Object.const_get("Miq#{method.capitalize}Session")
klass.new(legacy_depot_hash).exist?(log_uri)
end
# main UI method to call to request logs from a server
def self.logs_from_server(*args)
options = args.extract_options!
userid = args[0] || "system"
# If no server provided, use the MiqServer receiving this request
server = args[1] || MiqServer.my_server
# All server types who provide logs must implement the following instance methods:
# - my_zone: which returns the zone in which they reside
# - who_am_i: which returns a log friendly string of the server's class and id
[:my_zone, :who_am_i].each { |meth| raise "#{meth} not implemented for #{server.class.name}" unless server.respond_to?(meth) }
zone = server.my_zone
resource = server.who_am_i
_log.info("Queueing the request by userid: [#{userid}] for logs from server: [#{resource}]")
begin
# Create the task for the UI to check
task = MiqTask.create(:name => "Zipped log retrieval for [#{resource}]", :userid => userid, :miq_server_id => server.id)
# callback only on exceptions.. ie, on errors... second level callback will set status to finished
cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
# Queue the async fetch of the logs from the server - specifying a timeout, the zone to process this request, and a callback
options = options.merge(:taskid => task.id, :klass => server.class.name, :id => server.id)
MiqQueue.put(
:class_name => name,
:method_name => "_request_logs",
:args => [options],
:zone => zone,
:miq_callback => cb,
:msg_timeout => LOG_REQUEST_TIMEOUT,
:priority => MiqQueue::HIGH_PRIORITY
)
rescue => err
task.queue_callback_on_exceptions('Finished', 'error', err.to_s, nil) if task
raise
else
# return task id to the UI
msg = "Queued the request for logs from server: [#{resource}]"
task.update_status("Queued", "Ok", msg)
_log.info("Task: [#{task.id}] #{msg}")
task.id
end
end
def self.historical_logfile
empty_logfile(true)
end
def self.current_logfile
empty_logfile(false)
end
def self.empty_logfile(historical)
LogFile.create(:state => "collecting",
:historical => historical,
:description => "Default logfile")
end
def self.ping_timeout
::Settings.log.collection.ping_depot_timeout
end
def self.do_ping?
::Settings.log.collection.ping_depot == true
end
def upload_log_file_ftp
file_depot.upload_file(self)
end
def upload_log_file_nfs
uri_to_add = build_log_uri(file_depot.uri, local_file)
uri = MiqNfsSession.new(legacy_depot_hash).add(local_file, uri_to_add)
update(
:state => "available",
:log_uri => uri
)
post_upload_tasks
end
def upload_log_file_smb
uri_to_add = build_log_uri(file_depot.uri, local_file)
uri = MiqSmbSession.new(legacy_depot_hash).add(local_file, uri_to_add)
update(
:state => "available",
:log_uri => uri
)
post_upload_tasks
end
def remove_log_file_ftp
file_depot.remove_file(self)
end
def destination_directory
File.join("#{resource.zone.name}_#{resource.zone.id}", "#{resource.name}_#{resource.id}")
end
def self.logfile_name(resource, category = "Current", date_string = nil)
region = MiqRegion.my_region.try(:region) || "unknown"
[category, "region", region, resource.zone.name, resource.zone.id, resource.name, resource.id, date_string].compact.join(" ")
end
def destination_file_name
name.gsub(/\s+/, "_").concat(File.extname(local_file))
end
def name
super || self.class.logfile_name(resource)
end
def post_upload_tasks
FileUtils.rm_f(local_file) if File.exist?(local_file)
end
def format_log_time(time)
time.respond_to?(:strftime) ? time.strftime("%Y%m%d_%H%M%S") : "unknown"
end
private
def get_post_method(uri)
return nil if uri.nil?
# Convert all backslashes in the URI to forward slashes
uri.tr!('\\', '/')
# Strip any leading and trailing whitespace
uri.strip!
URI.split(URI::DEFAULT_PARSER.escape(uri))[0]
end
def legacy_depot_hash
# TODO: Delete this and make FileDepotSmb and FileDepotNfs implement all of their upload/delete/etc. logic
{
:uri => file_depot.uri,
:username => file_depot.authentication_userid,
:password => file_depot.authentication_password,
}
end
def self._request_logs(options)
taskid = options[:taskid]
klass = options.delete(:klass).to_s
id = options.delete(:id)
log_header = "Task: [#{taskid}]"
server = Object.const_get(klass).find(id)
resource = server.who_am_i
# server must implement an instance method: started_on? which returns whether the server is started
unless server.respond_to?(:started?)
raise MiqException::Error, _("started? not implemented for %{server_name}") % {:server_name => server.class.name}
end
unless server.started?
if server.respond_to?(:name)
raise MiqException::Error,
_("Log request failed since [%{resource} %{server_name}] is not started") % {:resource => resource,
:server_name => server.name}
else
raise MiqException::Error,
_("Log request failed since [%{resource}] is not started") % {:resource => resource}
end
end
task = MiqTask.find(taskid)
msg = "Requesting logs from server: [#{resource}]"
_log.info("#{log_header} #{msg}")
task.update_status("Active", "Ok", msg)
cb = {:class_name => task.class.name, :instance_id => task.id, :method_name => :queue_callback_on_exceptions, :args => ['Finished']}
unless server.respond_to?(:_post_my_logs)
raise MiqException::Error,
_("_post_my_logs not implemented for %{server_name}") % {:server_name => server.class.name}
end
options = options.merge(:callback => cb, :timeout => LOG_REQUEST_TIMEOUT)
server._post_my_logs(options)
msg = "Requested logs from: [#{resource}]"
_log.info("#{log_header} #{msg}")
task.update_status("Queued", "Ok", msg)
end
private_class_method :_request_logs
end