ManageIQ/manageiq-gems-pending

View on GitHub
lib/gems/pending/util/mount/miq_generic_mount_session.rb

Summary

Maintainability
D
2 days
Test Coverage
F
46%
File `miq_generic_mount_session.rb` has 397 lines of code (exceeds 250 allowed). Consider refactoring.
require 'active_support/core_ext/object/blank'
require 'fileutils'
require 'logger'
require 'sys-uname'
require 'uri'
require 'awesome_spawn'
 
Class `MiqGenericMountSession` has 45 methods (exceeds 20 allowed). Consider refactoring.
class MiqGenericMountSession < MiqFileStorage::Interface
class NoSuchFileOrDirectory < RuntimeError; end
 
class << self
def run_command(command, args)
AwesomeSpawn.run!(command, :params => args, :combined_output => true).output
end
end
 
attr_accessor :settings, :mnt_point
attr_writer :logger
 
Call `super` to initialize state of the parent class.
def initialize(log_settings)
raise "URI missing" unless log_settings.key?(:uri)
@settings = log_settings.dup
@mnt_point = nil
end
 
def logger
@logger ||= $log.nil? ? :: Logger.new(STDOUT) : $log
end
 
def mount(args)
self.class.run_command("mount", args)
rescue AwesomeSpawn::CommandResultError => err
raise if err.result.exit_status != 1
 
sudo_mount(args)
end
 
def sudo_mount(args)
self.class.run_command("sudo mount", args)
end
 
def self.umount(mount_point)
run_command("umount", [mount_point])
rescue AwesomeSpawn::CommandResultError => err
raise if err.result.exit_status != 1
 
sudo_umount(mount_point)
end
 
def self.sudo_umount(mount_point)
run_command("sudo umount", [mount_point])
end
 
def self.in_depot_session(opts, &_block)
Check block argument explicitly instead of using `block_given?`.
raise "No block provided!" unless block_given?
session = new_session(opts)
yield session
ensure
session.disconnect if session
end
 
def self.new_session(opts)
klass = uri_scheme_to_class(opts[:uri])
session = klass.new_with_opts(opts)
session.connect
session
end
 
def self.new_with_opts(opts)
new(opts.slice(:uri, :username, :password))
end
 
def self.uri_scheme_to_class(uri)
require 'uri'
Useless assignment to variable - `userinfo`. Use `_` or `_userinfo` as a variable name to indicate that it won't be used.
Useless assignment to variable - `share`. Use `_` or `_share` as a variable name to indicate that it won't be used.
Useless assignment to variable - `opaque`. Use `_` or `_opaque` as a variable name to indicate that it won't be used.
Useless assignment to variable - `registry`. Use `_` or `_registry` as a variable name to indicate that it won't be used.
Useless assignment to variable - `query`. Use `_` or `_query` as a variable name to indicate that it won't be used.
Useless assignment to variable - `fragment`. Use `_` or `_fragment` as a variable name to indicate that it won't be used.
Useless assignment to variable - `host`. Use `_` or `_host` as a variable name to indicate that it won't be used.
Useless assignment to variable - `port`. Use `_` or `_port` as a variable name to indicate that it won't be used.
scheme, userinfo, host, port, registry, share, opaque, query, fragment = URI.split(URI::DEFAULT_PARSER.escape(uri))
case scheme
when 'smb'
MiqSmbSession
when 'nfs'
MiqNfsSession
when 'glusterfs'
MiqGlusterfsSession
else
raise "unsupported scheme #{scheme} from uri: #{uri}"
end
end
 
def mount_share
require 'tmpdir'
@mnt_point = settings_mount_point || Dir.mktmpdir("miq_")
end
 
def get_ping_depot_options
@@ping_depot_options ||= begin
Useless assignment to variable - `opts`.
opts = ::VMDB::Config.new("vmdb").config[:log][:collection] if defined?(::VMDB) && defined?(::VMDB::CONFIG)
opts = {:ping_depot => false}
opts
end
end
 
def ping_timeout
get_ping_depot_options
@@ping_timeout ||= (@@ping_depot_options[:ping_depot_timeout] || 20)
end
 
def do_ping?
get_ping_depot_options
@@do_ping ||= @@ping_depot_options[:ping_depot] == true
end
 
Method `pingable?` has a Cognitive Complexity of 7 (exceeds 5 allowed). Consider refactoring.
def pingable?
log_header = "MIQ(#{self.class.name}-pingable?)"
return true unless self.do_ping?
return true unless @settings[:ports].kind_of?(Array)
 
res = false
require 'net/ping'
begin
# To prevent "no route to host" type issues, assume refused connection indicates the host is reachable
before = Net::Ping::TCP.econnrefused
Net::Ping::TCP.econnrefused = true
 
@settings[:ports].each do |port|
logger.info("#{log_header} pinging: #{@host} on #{port} with timeout: #{ping_timeout}")
tcp1 = Net::Ping::TCP.new(@host, port, ping_timeout)
res = tcp1.ping
logger.info("#{log_header} pinging: #{@host} on #{port} with timeout: #{ping_timeout}...result: #{res}")
break if res == true
end
ensure
Net::Ping::TCP.econnrefused = before
end
 
res == true
end
 
Method `connect` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
def connect
log_header = "MIQ(#{self.class.name}-connect)"
 
# Replace any encoded spaces back into spaces since the mount commands accepts quoted spaces
@mount_path = @mount_path.to_s.gsub('%20', ' ')
 
# # Grab only the share part of a path such as: /temp/default_1/evm_1/current_default_1_evm_1_20091120_192429_20091120_225653.zip
# @mount_path = @mount_path.split("/")[0..1].join("/")
 
begin
raise "Connect: Cannot communicate with: #{@host} - verify the URI host value and your DNS settings" unless self.pingable?
 
mount_share
rescue => err
Use `String#include?` instead of a regex match with literal-only pattern.
if err.kind_of?(RuntimeError) && err.message =~ /No such file or directory/
msg = "No such file or directory when connecting to host: [#{@host}] share: [#{@mount_path}]"
raise NoSuchFileOrDirectory, msg
end
msg = "Connecting to host: [#{@host}], share: [#{@mount_path}] encountered error: [#{err.class.name}] [#{err.message}]"
logger.error("#{log_header} #{msg}...#{err.backtrace.join("\n")}")
disconnect
raise
end
end
 
def disconnect
self.class.disconnect(@mnt_point, logger)
@mnt_point = nil
end
 
Method `disconnect` has a Cognitive Complexity of 11 (exceeds 5 allowed). Consider refactoring.
def self.disconnect(mnt_point, logger = $log)
return if mnt_point.nil?
log_header = "MIQ(#{self.class.name}-disconnect)"
logger.info("#{log_header} Disconnecting mount point: #{mnt_point}") if logger
begin
raw_disconnect(mnt_point)
rescue => err
# Ignore mount point not found/mounted messages
Use `match?` instead of `=~` when `MatchData` is not used.
unless err.message =~ /not found|mounted/
msg = "[#{err.class.name}] [#{err.message}], disconnecting mount point: #{@mnt_point}"
logger.error("#{log_header} #{msg}") if logger
raise
end
end
Remove unnecessary existence check `File.exist?`.
Use atomic file operation method `FileUtils.rm_f`.
FileUtils.rmdir(mnt_point) if File.exist?(mnt_point)
 
logger.info("#{log_header} Disconnecting mount point: #{mnt_point}...Complete") if logger
end
 
def active?
!@mnt_point.nil?
end
 
def reconnect!
disconnect
connect
end
 
def with_test_file(&_block)
Check block argument explicitly instead of using `block_given?`.
raise "requires a block" unless block_given?
file = '/tmp/miq_verify_test_file'
begin
`echo "testing" > #{file}`
yield file
ensure
FileUtils.rm(file, :force => true)
end
end
 
Method `verify` has 38 lines of code (exceeds 25 allowed). Consider refactoring.
def verify
log_header = "MIQ(#{self.class.name}-verify)"
logger.info("#{log_header} [#{@settings[:uri]}]...")
res = true
 
begin
connect
relpath = File.join(@mnt_point, relative_to_mount(@settings[:uri]))
 
test_path = 'miqverify/test'
to = File.join(test_path, 'test_file')
fq_file_path = File.join(relpath, to)
 
current_test = "create nested directories"
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...")
FileUtils.mkdir_p(File.dirname(fq_file_path))
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...complete")
 
with_test_file do |from|
current_test = "copy file"
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...")
FileUtils.cp(from, fq_file_path)
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...complete")
end
 
current_test = "delete file"
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...")
FileUtils.rm(fq_file_path, :force => true)
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...complete")
 
current_test = "remove nested directories"
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...")
FileUtils.rmdir(File.dirname(fq_file_path))
FileUtils.rmdir(File.dirname(File.dirname(fq_file_path)))
logger.info("#{log_header} [#{@settings[:uri]}] Testing #{current_test}...complete")
 
rescue => err
logger.error("#{log_header} Verify [#{current_test}] failed with error [#{err.class.name}] [#{err}], [#{err.backtrace[0]}]")
res = false, err.to_s
else
res = true, ""
ensure
disconnect
end
logger.info("#{log_header} [#{@settings[:uri]}]...result: [#{res.first}]")
res
end
 
def add(*upload_args)
dest_uri = nil
log_header = "MIQ(#{self.class.name}-add)"
 
# Don't think this log line is possible when using MiqFileStorage::Interface
#
# logger.info("#{log_header} Source: [#{source}], Destination: [#{dest_uri}]...")
 
begin
reconnect!
 
dest_uri = super
rescue => err
msg = "Adding [#{source_for_log}] to [#{remote_file_path}], failed due to error: '#{err.message}'"
logger.error("#{log_header} #{msg}")
raise
ensure
disconnect
end
 
logger.info("#{log_header} File URI added: [#{remote_file_path}] complete")
dest_uri
end
 
def upload_single(dest_uri)
log_header = "MIQ(#{self.class.name}-upload_single)"
relpath = uri_to_local_path(dest_uri)
if File.exist?(relpath)
logger.info("#{log_header} Skipping add since URI: [#{dest_uri}] already exists")
return dest_uri
end
 
logger.info("#{log_header} Copying file [#{source_for_log}] to [#{dest_uri}]...")
IO.copy_stream(source_input, relpath, byte_count)
logger.info("#{log_header} Copying file [#{source_for_log}] to [#{dest_uri}] complete")
dest_uri
end
 
def download_single(remote_file, local_file)
log_header = "MIQ(#{self.class.name}-download)"
 
logger.info("#{log_header} Target: [#{local_file}], Remote file: [#{remote_file}]...")
 
begin
reconnect!
relpath = uri_to_local_path(remote_file)
unless File.exist?(relpath)
logger.warn("#{log_header} Remote file: [#{remote_file}] does not exist!")
return
end
 
logger.info("#{log_header} Copying file [#{relpath}] to [#{local_file}]...")
IO.copy_stream(relpath, local_file, byte_count)
logger.info("#{log_header} Copying file [#{relpath}] to [#{local_file}] complete")
Similar blocks of code found in 2 locations. Consider refactoring.
rescue => err
msg = "Downloading [#{remote_file}] to [#{local_file}], failed due to error: '#{err.message}'"
logger.error("#{log_header} #{msg}")
raise
ensure
disconnect
end
 
logger.info("#{log_header} Download File: [#{remote_file}] complete")
local_file
end
 
Method `log_uri_still_configured?` has a Cognitive Complexity of 6 (exceeds 5 allowed). Consider refactoring.
def log_uri_still_configured?(log_uri)
# Only remove the log file if the current depot @settings are based on the same base URI as the log_uri to be removed
return false if log_uri.nil? || @settings[:uri].nil?
 
Useless assignment to variable - `port`. Use `_` or `_port` as a variable name to indicate that it won't be used.
Useless assignment to variable - `fragment`. Use `_` or `_fragment` as a variable name to indicate that it won't be used.
Useless assignment to variable - `query`. Use `_` or `_query` as a variable name to indicate that it won't be used.
Useless assignment to variable - `userinfo`. Use `_` or `_userinfo` as a variable name to indicate that it won't be used.
Useless assignment to variable - `registry`. Use `_` or `_registry` as a variable name to indicate that it won't be used.
Useless assignment to variable - `opaque`. Use `_` or `_opaque` as a variable name to indicate that it won't be used.
scheme, userinfo, host, port, registry, share, opaque, query, fragment = URI.split(URI::DEFAULT_PARSER.escape(@settings[:uri]))
Useless assignment to variable - `opaque_log`. Use `_` or `_opaque_log` as a variable name to indicate that it won't be used.
Useless assignment to variable - `registry_log`. Use `_` or `_registry_log` as a variable name to indicate that it won't be used.
Useless assignment to variable - `port_log`. Use `_` or `_port_log` as a variable name to indicate that it won't be used.
Useless assignment to variable - `userinfo_log`. Use `_` or `_userinfo_log` as a variable name to indicate that it won't be used.
Useless assignment to variable - `fragment_log`. Use `_` or `_fragment_log` as a variable name to indicate that it won't be used.
Useless assignment to variable - `query_log`. Use `_` or `_query_log` as a variable name to indicate that it won't be used.
scheme_log, userinfo_log, host_log, port_log, registry_log, share_log, opaque_log, query_log, fragment_log = URI.split(URI::DEFAULT_PARSER.escape(log_uri))
 
return false if scheme != scheme_log
return false if host != host_log
 
# Since the depot URI is a base URI, remove all the directories in the log_uri from the base URI and check for empty?
return false unless (share.split("/") - share_log.split("/")).empty?
true
end
 
Method `remove` has 29 lines of code (exceeds 25 allowed). Consider refactoring.
def remove(log_uri)
log_header = "MIQ(#{self.class.name}-remove)"
 
unless self.log_uri_still_configured?(log_uri)
logger.info("#{log_header} Skipping remove because log URI: [#{log_uri}] does not originate from the currently configured base URI: [#{@settings[:uri]}]")
return
end
 
relpath = nil
begin
# Samba has issues mount directly in the directory of the file so mount on the parent directory
Use `@settings[:uri] = File.dirname(File.dirname(log_uri))` instead of `@settings.merge!(:uri => File.dirname(File.dirname(log_uri)))`.
@settings.merge!(:uri => File.dirname(File.dirname(log_uri)))
reconnect!
 
relpath = File.join(@mnt_point, relative_to_mount(log_uri))
# path is now /temp/default_1/EVM_1/Archive_default_1_EVM_1_20091016_193633_20091016_204855.zip, trim the share and join with the mount point
# /mnt/miq_1258754934/default_1/EVM_1/Archive_default_1_EVM_1_20091016_193633_20091016_204855.zip
# relpath = File.join(@mnt_point, path.split('/')[2..-1] )
 
logger.info("#{log_header} URI: [#{log_uri}] using relative path: [#{relpath}] and mount path: [#{@mount_path}]...")
 
unless File.exist?(relpath)
logger.info("#{log_header} Skipping since URI: [#{log_uri}] with relative path: [#{relpath}] does not exist")
return log_uri
end
 
logger.info("#{log_header} Deleting [#{relpath}] on [#{log_uri}]...")
FileUtils.rm_rf(relpath)
logger.info("#{log_header} Deleting [#{relpath}] on [#{log_uri}]...complete")
Useless assignment to variable - `err`.
rescue NoSuchFileOrDirectory => err
logger.warn("#{log_header} No such file or directory to delete: [#{log_uri}]")
Similar blocks of code found in 2 locations. Consider refactoring.
rescue => err
msg = "Deleting [#{relpath}] on [#{log_uri}], failed due to err '#{err.message}'"
logger.error("#{log_header} #{msg}")
raise
ensure
disconnect
end
 
logger.info("#{log_header} URI: [#{log_uri}]...complete")
log_uri
end
 
def uri_to_local_path(remote_file)
File.join(@mnt_point, relative_to_mount(remote_file))
end
 
def local_path_to_uri(local_path)
relative_path = Pathname.new(local_path).relative_path_from(Pathname.new(@mnt_point)).to_s
File.join(@settings[:uri], relative_path)
end
 
#
# These methods require an existing connection
#
 
def glob(pattern)
with_mounted_exception_handling do
Dir.glob("#{mount_root}/#{pattern}").collect { |path| (path.split("/") - mount_root.split("/")).join("/") }
end
end
 
def mkdir(path)
with_mounted_exception_handling do
log_header = "MIQ(#{self.class.name}-mkdir)"
new_path = uri_to_local_path(path)
logger.info("#{log_header} Building relative path: [#{new_path}]...")
FileUtils.mkdir_p(new_path)
logger.info("#{log_header} Building relative path: [#{new_path}]...complete")
end
end
 
def stat(file)
with_mounted_exception_handling do
File.stat("#{mount_root}/#{file}")
end
end
 
def read(file)
with_mounted_exception_handling do
File.read("#{mount_root}/#{file}")
end
end
 
def write(file, contents)
with_mounted_exception_handling do
mkdir(File.dirname(file))
The use of `Kernel#open` is a serious security risk.
open(file, "w") { |fd| fd.write(contents) }
end
end
 
def delete(file_or_directory)
with_mounted_exception_handling do
FileUtils.rm_rf("#{mount_root}/#{file_or_directory}")
end
end
 
def open(*args, &block)
with_mounted_exception_handling do
args[0] = "#{mount_root}/#{args[0]}"
File.open(*args, &block)
end
end
 
def file?(file)
with_mounted_exception_handling do
File.file?("#{mount_root}/#{file}")
end
end
 
protected
 
def mount_root
@mnt_point
end
 
def relative_to_mount(uri)
log_header = "MIQ(#{self.class.name}-relative_to_mount)"
logger.info("#{log_header} mount point [#{@mount_path}], uri: [#{uri}]...")
Useless assignment to variable - `opaque`. Use `_` or `_opaque` as a variable name to indicate that it won't be used.
Useless assignment to variable - `userinfo`. Use `_` or `_userinfo` as a variable name to indicate that it won't be used.
Useless assignment to variable - `port`. Use `_` or `_port` as a variable name to indicate that it won't be used.
Useless assignment to variable - `registry`. Use `_` or `_registry` as a variable name to indicate that it won't be used.
Useless assignment to variable - `scheme`. Use `_` or `_scheme` as a variable name to indicate that it won't be used.
Useless assignment to variable - `fragment`. Use `_` or `_fragment` as a variable name to indicate that it won't be used.
Useless assignment to variable - `query`. Use `_` or `_query` as a variable name to indicate that it won't be used.
Useless assignment to variable - `host`. Use `_` or `_host` as a variable name to indicate that it won't be used.
scheme, userinfo, host, port, registry, path, opaque, query, fragment = URI.split(URI::DEFAULT_PARSER.escape(uri))
 
# Replace any encoded spaces back into spaces since the mount commands accepts quoted spaces
path.gsub!('%20', ' ')
 
raise "path: #{path} or mount_path #{@mount_path} is blank" if path.nil? || @mount_path.nil? || path.empty? || @mount_path.empty?
res = (path.split("/") - @mount_path.split("/")).join("/")
logger.info("#{log_header} mount point [#{@mount_path}], uri: [#{uri}]...relative: [#{res}]")
res
end
 
def with_mounted_exception_handling
yield
rescue => err
err.message.gsub!(@mnt_point, @settings[:uri])
raise
end
 
`protected` (on line 445) does not make singleton methods protected. Use `protected` inside a `class << self` block instead.
def self.raw_disconnect(mnt_point)
case Sys::Platform::IMPL
when :macosx
sudo_umount(mnt_point)
when :linux
umount(mnt_point)
else
raise "platform not supported"
end
end
 
private
 
def settings_read_only?
@settings[:read_only] == true
end
 
def settings_mount_point
return nil if @settings[:mount_point].blank? # Check if settings contains the mount_point to use
FileUtils.mkdir_p(@settings[:mount_point]).first
end
 
def source_for_log
if @input_writer
"<STREAMED_FROM_CMD>"
elsif @source_input
@source_input.path
else
"<NOT_READY>"
end
end
end