require 'pathname' require 'metadata/VMMount/VMMount' require 'util/miq-unicode' require 'util/miq-xml' require 'VMwareWebService/MiqVimInventory' require 'timeout' require 'util/miq-extensions' require 'VMwareWebService/MiqVimBroker' class VmConfig attr_reader :configFile def initialize(filename) @configFile = nil @configPath = nil @direct_file_access = true if filename.kind_of? Hash @direct_file_access = false @cfgHash = filename else @cfgHash = {} # If filename contains embedded new-line chars treat it as data. Otherwise, its a filename. if filename.index("\n") f = filename else set_vmconfig_path(filename) begin configType = File.extname(filename).delete(".").downcase require "metadata/VmConfig/#{configType}Config" rescue LoadError => e raise e, "Filetype unrecognized for file #{filename}" end extend Kernel.const_get(configType.capitalize + "Config") f = convert(filename) end process_file(f) f.close if f.class == File postProcessDisks end # Handle oddities configuration_fixup end def process_file(data_lines) data_lines.each_line do |line| line.AsciiToUtf8!.strip! next if line.length == 0 next if line =~ /^#.*$/ next unless line.include?("=") k, v = line.split(/\s*=\s*/) # Note: All key names are lower-cased for easy lookup k = k.downcase v = v.gsub(/^"/, "").gsub(/"$/, "") @cfgHash[k] = v end end def postProcessDisks # Convert absolute paths to relative paths for the disks if @direct_file_access getAllDiskKeys.each do |dk| next if @cfgHash[dk + ".devicetype"] && @cfgHash[dk + ".devicetype"].include?("cdrom") next if @cfgHash[dk + ".filename"] && @cfgHash[dk + ".filename"].downcase == "auto detect" begin dskPath = Pathname.new(@cfgHash[dk + ".filename"]) begin @cfgHash[dk + ".filename"] = dskPath.relative_path_from(Pathname.new(@configPath)).to_s.tr("\\", "/") if dskPath.absolute? rescue @cfgHash[dk + ".filename"] = dskPath.to_s.tr("\\", "/") end if self.respond_to?(:diskCreateType) createType = diskCreateType(@cfgHash[dk + ".filename"]) @cfgHash[dk + ".createType"] = createType if createType end if self.respond_to?(:diskControllerType) adapterType = diskControllerType(@cfgHash[dk + ".filename"]) @cfgHash[dk + ".adapterType"] = adapterType if adapterType end rescue => err $log.warn "VmConfig: Failed to convert path: #{@cfgHash[dk + ".filename"]}, #{err}" end end end end def getHash @cfgHash end def getDiskFileHash return @diskFileHash if @diskFileHash @diskFileHash = {} getAllDiskKeys.each do |dk| next if @cfgHash[dk + ".devicetype"] && @cfgHash[dk + ".devicetype"].include?("cdrom") next if @cfgHash[dk + ".filename"] && @cfgHash[dk + ".filename"].downcase == "auto detect" filename = @cfgHash[dk + ".filename"] @diskFileHash[dk] = filename if @direct_file_access ds, _dir, _name = split_filename(filename) if ds.nil? && !Pathname.new(filename).absolute? @diskFileHash[dk] = File.expand_path(File.join(@configPath, filename)) end end end @diskFileHash end # getDiskFileHash def getAllDiskKeys dskKeys = @cfgHash.keys.delete_if { |e| !diskKey?(e) }.collect! { |e| e.gsub(".filename", "") } dskKeys.delete_if { |dk| @cfgHash[dk + ".present"].to_s.downcase == "false" } dskKeys.sort end def getSnapshotDiskFileHash return @snapshotdiskFileHash if @snapshotdiskFileHash # For snapshots we need to keep the disks order by snapshot uid, then by device @snapshotdiskFileHash = Hash.new { |h, k| h[k] = {} } getSnapshotDiskKeys.each do |dk| next if @cfgHash[dk + ".devicetype"].to_s.include?("cdrom") next if @cfgHash[dk + ".filename"].to_s.downcase == "auto detect" dk =~ /^snapshot\d+/ sn_uid = @cfgHash["#{$&}.uid"] key = @cfgHash[dk + ".node"] # Independent disks do not par-take in snapshots. Check the mode of the parent disk. next if @cfgHash["#{key}.mode"].to_s.include?('independent') @snapshotdiskFileHash[sn_uid]['disks'] ||= {} filename = @cfgHash[dk + ".filename"] disk_path = filename if @direct_file_access ds, _dir, _name = split_filename(filename) if ds.nil? && !Pathname.new(@cfgHash[dk + ".filename"]).absolute? disk_path = File.join(@configPath, filename) end end @snapshotdiskFileHash[sn_uid]['disks'][key] = disk_path end @snapshotdiskFileHash end # getSnapshotDiskFileHash def getSnapshotDiskKeys dskKeys = @cfgHash.keys.delete_if { |e| !snapshotdiskKey?(e) }.collect! { |e| e.gsub(".filename", "") } dskKeys.delete_if { |dk| @cfgHash[dk + ".present"].to_s.downcase == "false" } dskKeys.sort end def set_vmconfig_path(config_path) @configFile = normalize_vmconfig_file(config_path) @configPath = File.dirname(@configFile) end def to_xml(diskStats = true, miqvm = nil) # If loaded directly from a hash object, try to add snapshot metadata before processing load_vim_snapshots(miqvm) set_vmconfig_path(miqvm.vmConfigFile) unless miqvm.nil? normalize_file_paths xml = MiqXml.createDoc("", "vendor" => vendor) # Sort the Hash into an array to group like keys and add each array to the XML doc @cfgHash.sort { |a, b| a <=> b }.each do |(k, v)| next if k[0..0] == '.' insert_XML(k, v, xml) end # Pre-load data unless miqvm.nil? # Make sure we have the volume manager and loaded begin miqvm.rootTrees[0] @vol_mgr_loaded = true rescue LoadError $log.warn "add_disk_stats [#{$!.class}]-[#{$!}]" end files(miqvm) end # Add virtual disk sizes add_disk_stats(xml, miqvm) if diskStats # Here we list all files in the directory and get their size on disk. add_file_sizes(xml, miqvm) # Add partition and volume information add_volumes(xml, miqvm) # Add date and size info for snapshots add_snapshot_size(xml, miqvm) # TODO: Re-enable # normalize_path_names(xml) xml end alias_method :toXML, :to_xml def getDriveImageList vmDisks = [] # Convert config file to XML xml = toXML(false) # Find the disk section of the XML xml.root.each_recursive do |e| # Only process drives that are present and have a filename if (!e.attributes['present'].nil? && e.attributes['present'].downcase == 'true') && !e.attributes['filename'].nil? && (!e.attributes['type'].nil? && e.attributes['type'].downcase == 'disk') && (!e.attributes['id'].nil? && e.attributes['id'].include?(":")) # Make sure the disk we are looking at is not a CD-ROM if e.attributes['devicetype'].nil? || (!e.attributes['devicetype'].nil? && e.attributes['devicetype'].downcase.index("cd").nil?) diskName = e.attributes['filename'].tr("\"", "").strip.tr("\\", "/") diskName = File.join(@configPath, diskName) unless diskName[0..0] == "/" $log.debug "Adding Disk name to list: [#{diskName}]" begin vmDisks.push(File.expand_path(diskName)) rescue vmDisks.push(diskName) end $log.debug "Adding Disk name to list: [#{diskName}]" # $log.info "vmDisk Array = #{vmDisks.to_s()}" end end end vmDisks end def dump_config_to_log(log_level = :warn) $log.send(log_level, "VmConfig: Start of configuration info for: #{@configFile}") @cfgHash.each { |k, v| $log.send(log_level, "#{k} = #{v}") } $log.send(log_level, "VmConfig: End of configuration info for: #{@configFile}") end def vendor "unknown" end def find_file(filename, file_list = files) # basename = File.basename(filename) # return file_list.detect {|f| f[:name] == basename} file_list.detect { |f| f[:path] == filename } end private def normalize_vmconfig_file(config_file) ds, _dir, _name = split_filename(config_file) return config_file unless ds.nil? File.expand_path(config_file.tr('\\', '/')) end def diskKey?(key) key =~ /^ide\d+:\d+\.filename$/ || key =~ /^scsi\d+:\d+\.filename$/ end def snapshotdiskKey?(key) key =~ /^snapshot\d+.disk\d+.filename$/ end def insert_XML(key, value, xml) maj, min, other = key.split(".") # Any elements that cannot be split are put into a misc class min, maj = maj, maj if min.nil? if maj.index(/\d/) maj_name = $`.chomp(':') else maj_name = maj end if min[0...3] == "mem" || min[0...5] == "nvram" path = [["hardware"], ["memory"]] elsif maj_name == "uuid" path = [["hardware"], ["bios"]] # Drives elsif maj_name == "scsi" || maj_name == "ide" || maj_name == "floppy" path = getDevicePath(maj, "disk") # Ports elsif maj_name == "usb" || maj_name == "parallel" || maj_name == "serial" || maj_name == "sound" || maj_name == "ethernet" path = getDevicePath(maj, maj_name) elsif maj_name == "snapshot" other, min = min, other if other path = getSnapShotPath(maj, maj_name, other) else path = [["vm"], [maj]] end parent = xml.root path.each do |p| if p[1].nil? ele = MIQRexml.findElement(p[0], parent) if parent.has_elements? else ele = find_with_attributes(p[0], p[1], parent) if parent.has_elements? end # Create new node if needed. if ele.nil? # Remove any nil keys from the hash p[1].delete_if { |_k, v| v.nil? } if p[1].kind_of?(Hash) parent = parent.add_element(p[0], p[1]) else parent, ele = ele, nil end end parent.add_attribute(min.tr(':', '_'), value) if parent end def add_disk_stats(xml, miqvm) # Now loop over the xml and find disk files xml.find_first("/*/hardware").each_recursive do |e| # Find elements that have a filename attribute # Loop through the "whole disks" and get the size for this disk if e.attributes['filename'] getDiskFileHash.each_pair do |device_id, filename| if is_same_disk?(device_id, e) if (fstat = find_file(e.attributes['filename'], disk_files(miqvm))).nil? $log.warn "add_disk_stats - Disk file not found - Details: device_id: [#{device_id}] [#{filename}]" next end $log.info "add_disk_stats - device_id: [#{device_id}] [#{filename}]" whole_disk = nil whole_disk = miqvm.wholeDisks.detect { |wd| wd.hwId.include?(device_id) } if @vol_mgr_loaded size = whole_disk.nil? ? fstat[:provision_size] : whole_disk.size # Report a disk's size_on_disk as the size of all the files that make up the disk. # This includes the base disk and snapshots. collective_disk_size = collective_size_on_disk(device_id, disk_files(miqvm)) size_on_disk = collective_disk_size.blank? ? fstat[:size] : collective_disk_size e.add_attributes({'size' => size, 'size_on_disk' => size_on_disk, 'disk_type' => fstat[:disk_type]}) end end end end rescue Exception $log.error "VmConfig.add_disk_stats [#{$!.class}]-[#{$!}]\n#{$!.backtrace.join("\n")}" end # Match filenames to loaded disks using the hardware id. IE scsi0:0 or ide0:0 def is_same_disk?(hardware_id, xml_element) diskId = xml_element.parent.attributes["type"] + xml_element.attributes["id"].to_s hardware_id.include?(diskId) end def normalize_path_names(xml) # Now loop over the xml and find disk files xml.root.each_recursive { |e| e.add_attribute('filename', resolve_ds_path(e.attributes['filename'])) if e.attributes['filename'] } xml.find_first("/*/files").each_recursive { |e| e.add_attribute('name', resolve_ds_path(e.attributes['name'])) if e.attributes['name'] } xml.find_first("/*/volumes").each_recursive { |e| e.add_attribute('virtual_disk_file', resolve_ds_path(e.attributes['virtual_disk_file'])) if e.attributes['virtual_disk_file'] } end def resolve_ds_path(filename) # TODO: Determine if we need to do any work here. ds, dir, name = split_filename(filename) return filename unless ds.nil? @ds_replace ||= {} @ds_replace.each_pair do |path, ds| return filename.sub(path, ds) if filename.include?(path) end ds_filename = resolve_ds_path_with_vim(filename) ds, dir, name = split_filename(ds_filename) unless ds.nil? idx = filename.index(dir) replaced_str = filename[0, idx] @ds_replace[replaced_str] = "[#{ds}] " end ds_filename end def host_vim_credentials creds = nil if $miqHostCfg if $miqHostCfg.emsLocal creds = $miqHostCfg.ems[$miqHostCfg.emsLocal] elsif $miqHostCfg.vimHost c = $miqHostCfg.vimHost if c[:username].nil? $log.warn "Host credentials are missing: skipping snapshot information." return nil end creds = {'host' => (c[:hostname] || c[:ipaddress]), 'user' => c[:username], 'password' => c[:password], 'use_vim_broker' => c[:use_vim_broker]} end end creds end def connect_to_host_vim(conn_reason, vmCfgFile) ems_host = host_vim_credentials return nil if ems_host.nil? begin st = Time.now ems_display_text = "host(#{ems_host['use_vim_broker'] ? 'via broker' : 'directly'}):#{ems_host['host']}" $log.info "#{conn_reason}: Connecting to [#{ems_display_text}] for VM:[#{vmCfgFile}]" require 'VMwareWebService/miq_fault_tolerant_vim' password_decrypt = ManageIQ::Password.decrypt(ems_host['password']) hostVim = MiqFaultTolerantVim.new(:ip => ems_host['host'], :user => ems_host['user'], :pass => password_decrypt, :use_broker => ems_host['use_vim_broker'], :vim_broker_drb_uri => ems_host['vim_broker_drb_uri']) $log.info "#{conn_reason}: Connection to [#{ems_display_text}] completed for VM:[#{vmCfgFile}] in [#{Time.now - st}] seconds" return hostVim rescue Timeout::Error => err $log.error "#{conn_reason}: Connection to [#{ems_display_text}] timed out for VM:[#{vmCfgFile}] with error [#{err}] after [#{Time.now - st}] seconds" rescue Exception => err $log.error "#{conn_reason}: Connection to [#{ems_display_text}] failed for VM:[#{vmCfgFile}] with error [#{err}] after [#{Time.now - st}] seconds" end nil end def resolve_ds_path_with_vim(filename) vi = nil ems = host_vim_credentials return (filename) if ems.nil? password_decrypt = ManageIQ::Password.decrypt(ems['password']) $log.debug "resolve_path_names: emsHost = #{ems['host']}, emsUser = #{ems['user']}" if $log vi = MiqVimInventory.new(ems['host'], ems['user'], password_decrypt) return getDsName(filename, vi) rescue $log.error "VmConfig.resolve_ds_path_with_vim #{$!}\n#{$!.backtrace.join("\n")}" ensure vi.disconnect if vi end def getDsName(filename, vi) filename = File.join(File.dirname(@configFile), filename) if File.dirname(filename) == "." dsName = vi.datastorePath(filename) dsName.sub!("] /", "] ") if dsName.index("] /") return dsName rescue return filename end def files(miqvm = nil) return @files if @files log_header = "VmConfig.files" @files = [] if @direct_file_access Dir.glob(File.join(@configPath, "/*.*")) do |f| s = File.size(f) rescue 0 @files << {:path => f, :name => File.basename(f), :size => s, :mtime => File.mtime(f)} end else begin if miqvm.vim filePattern = nil pathOnly = false vimDs = nil each_datastore(miqvm) do |ds, dirs| begin vimDs = miqvm.vim.getVimDataStore(ds) dirs.each do |path| vimDs.dsFileSearch(filePattern, path, pathOnly).each do |f| @files << {:path => f['fullPath'], :name => f['path'], :size => f['fileSize'].to_i, :mtime => Time.parse(f['modification'])} end end ensure vimDs.release if vimDs rescue nil end end elsif miqvm.rhevm # First add the VM's active disk files. disks = miqvm.rhevm.collect_vm_disks(miqvm.rhevmVm) disks.each { |disk| @files << rhevm_disk_file_entry(disk) } # Then add the files associtaed with inactive snapshots. miqvm.rhevm.collect_snapshots(miqvm.rhevmVm).each do |snap| next if snap.snapshot_type == 'active' miqvm.rhevm.collect_disks_of_snapshot(snap).each do |disk| @files << rhevm_disk_file_entry(disk) end end end rescue => err $log.error "#{log_header} #{err}\n#{err.backtrace.join("\n")}" end end disk_files(miqvm) @files end def rhevm_disk_file_entry(disk) storage_id = disk.storage_domains&.first&.id disk_id = disk.image_id || disk.id full_path = storage_id && File.join('/dev', storage_id, disk_id) {:path => full_path, :name => disk_id, :size => disk.actual_size.to_i} end private :rhevm_disk_file_entry def disk_files(miqvm = nil) return @disk_files if @disk_files @disk_files = [] if @direct_file_access @disk_files = files(miqvm) else begin vim_disks = {} if miqvm.vimVm miqvm.vimVm.devices.each do |d| next if d['capacityInKB'].nil? next if (filename = d.fetch_path('backing', 'fileName')).nil? disk = vim_disks[filename.to_s] = {} # check if it's raw disk type, using same logic as in vmware refresher_parser. mode = d.fetch_path('backing', 'compatibilityMode') disk_type = "rdm-#{mode[0...-4]}" if mode disk[:disk_type] = mode ? disk_type : ((d.fetch_path('backing', 'thinProvisioned') == 'true') ? 'thin' : 'thick') disk[:provision_size] = d['capacityInKB'].to_i * 1024 disk[:display_name] = d.fetch_path('deviceInfo', 'label').to_s disk[:vim_index] = d['key'].to_i disk[:disk_mode] = d['diskMode'] end filePattern = nil pathOnly = false each_datastore(miqvm) do |ds, dirs| begin vimDs = miqvm.vim.getVimDataStore(ds) dirs.each do |path| vimDs.dsVmDiskFileSearch(filePattern, path, pathOnly).each do |f| d = {:path => f['fullPath'], :name => f['path'].to_s, :size => f['fileSize'].to_i, :mtime => Time.parse(f['modification'])} dh = vim_disks[d[:path]] d.merge!(dh) unless dh.nil? @disk_files << d end end ensure vimDs.release if vimDs rescue nil end end elsif miqvm.rhevmVm disks = miqvm.rhevm.collect_vm_disks(miqvm.rhevmVm) disks.each do |disk| storage_id = disk.storage_domains&.first&.id disk_id = disk.image_id || disk.id full_path = storage_id && File.join('/dev', storage_id, disk_id) d = {:path => full_path, :name => disk.name.to_s, :size => disk.actual_size.to_i} @disk_files << d end end rescue Exception $log.error "VmConfig.disk_files #{$!}\n#{$!.backtrace.join("\n")}" end end @disk_files end def add_file_sizes(xml, miqvm) return if miqvm.nil? begin node = xml.root.add_element("files") total_size = 0 files(miqvm).each do |fh| total_size += fh[:size] node.add_element('file', {"name" => fh[:name], "size_on_disk" => fh[:size]}) end # Add the total size to the "files" node node.add_attribute("size_on_disk", total_size) free_space = 0; disk_capacity = 0 if miqvm && @vol_mgr_loaded # Make sure we have the volume manager loaded vmRoot = miqvm.rootTrees[0] if vmRoot miqvm.wholeDisks.each { |d| disk_capacity += d.size } rootAdded = false vmRoot.fileSystems.each do |fsd| $log.info "FileSystem: #{fsd.fsSpec}, Mounted on: #{fsd.mountPoint}, Type: #{fsd.fs.fsType}, Free bytes: #{fsd.fs.freeBytes}" # The root volume can appear more than once, so only add it one time. if fsd.mountPoint == "/" if rootAdded == false free_space += fsd.fs.freeBytes rootAdded = true end else free_space += fsd.fs.freeBytes end end end # Now calculate free/used percent against size on disk if !free_space.zero? && !disk_capacity.zero? # Calculate the percentage of free space percent_free = free_space.to_f / disk_capacity.to_f * 100 # Populate formated text fields node.add_attributes("pct_free_wrt_size_on_disk" => percent_free, "pct_used_wrt_size_on_disk" => (100 - percent_free)) node.add_attributes("disk_free_space" => free_space, "disk_capacity" => disk_capacity) end end node.add_attributes("disk_free_space" => free_space, "disk_capacity" => disk_capacity) rescue end end def load_vim_snapshots(miqvm) if @direct_file_access == false && miqvm && miqvm.vim # If the VM does not have snapshots we can stop here return if miqvm.snapshots.nil? ds, dir, name = split_filename(miqvm.vmConfigFile) # We need to connect to the host to get the snapshot file content hostVim = nil if miqvm.vim.isVirtualCenter? hostVim = connect_to_host_vim('snapshot_metadata', miqvm.vmConfigFile) if hostVim.nil? $log.warn "Snapshots information will be skipped due to EMS host missing credentials." return end vimDs = hostVim.getVimDataStore(ds) else vimDs = miqvm.vim.getVimDataStore(ds) end require "metadata/VmConfig/vmxConfig" extend Kernel.const_get("VmxConfig") snapshot_file = File.join(dir, File.basename(name, ".*") + ".vmsd") Timeout.timeout(60) do process_file(convert_vmsd(vimDs.get_file_content(snapshot_file))) end @cfgHash end rescue Timeout::Error => err $log.warn "Timeout reached transferring snapshot metadata for config file [#{miqvm.vmConfigFile}]. Message:[#{err}]" rescue => err $log.warn "Unable to process snapshot metadata for config file [#{miqvm.vmConfigFile}]. Message:[#{err}]" ensure vimDs.release if vimDs rescue nil hostVim.release if hostVim rescue nil end def each_datastore(_miqvm) files = [@configFile] each_disks { |_device_id, filename| files << filename } ds = Hash.new { |h, k| h[k] = {} } files.each do |f| dsName, dir, _name = split_filename(f) ds[dsName][dir] = true # unless dsName.nil? end ds.each_pair { |ds, h| yield(ds, h.to_a.transpose.first) } end def getDevicePath(name, endType) # Initialize variables full_pos, maj_pos, device = nil, "0", name # If the name contains a number, use it as the id if name.index(/\d/) device = $`.chomp(':') maj_pos = $& full_pos = "#{$&}#{$'}" end # These devices only have 1 position number, and should be grouped under a controller maj_pos = nil if ["ethernet", "floppy", "parallel", "serial"].include?(device) ret = [["hardware"], ["bus", {"type" => "pci"}], ["controller", {"type" => device, "id" => maj_pos}]] # Add a device type if the major position and full position differ, otherwise # we are processing a controller element. ret << ["device", {"type" => endType, "id" => full_pos}] if maj_pos != full_pos ret end def getSnapShotPath(name, endType, disk) # Initialize variables full_pos, _maj_pos, _device, pos_idx = split_data(name) return [["vm"], ["snapshots"]] if pos_idx.nil? ret = [["vm"], ["snapshots"], [endType, {"id" => full_pos}]] if disk subDevice = split_data(disk)[2] diskPath = getSnapShotPath(disk, subDevice, nil) ret << diskPath.pop end ret end def add_snapshot_size(xml, _miqvm) adjust_snapshot_aligment(xml) # First we need to loop through the snapshots and add flat files for any # disks that are just descriptor files xml.find_each("//*/snapshots/snapshot/disk") do |e| unless e.attributes['filename'].nil? find_additional_disk_files(e.attributes['filename']) do |f| e.parent.add_element("disk", "filename" => f[:path]) end end end # Now loop through the filename and collect stats (date/time/size) node = xml.find_first("//*/snapshots") node.each_recursive do |e| if e.attributes.include?("filename") begin filename = normalize_file_path(e.attributes['filename']) next if (fstat = find_file(filename)).nil? e.add_attribute("size_on_disk", fstat[:size]) e.add_attribute("mdate_on_disk", fstat[:mtime].getutc.iso8601(6)) rescue => err # Ignore errors here, we will try to load almost anything. $log.error "VmConfig.add_snapshot_size [#{err.class}]-[#{err}]\n#{err.backtrace.join("\n")}" end end end unless node.nil? end def adjust_snapshot_aligment(xml) sn = {} sn_root = xml.find_first("//*/snapshots") unless sn_root.nil? disks_by_snap = disks_by_snapshot_and_node xml.find_each("//*/snapshots/snapshot") { |s| sn[s.attributes[:uid]] = s } sn_list = sn.sort { |a, b| a[1].attributes[:uid].to_s <=> b[1].attributes[:uid].to_s } clear_snapshot_disks(sn_list) sn_list.each do |(id, snapshot)| parent = snapshot.attributes[:parent] unless parent.nil? || sn[parent].nil? disks_by_snap[id.to_s]['disks'].each do |device_id, filename| sn[parent].add_element(:disk, :node => device_id, :filename => filename) end end filename = normalize_file_path(snapshot.attributes[:filename]) snapshot.add_element("vmem", "filename" => filename) if find_file(filename) end current = sn[sn_root.attributes[:current]] unless current.nil? disks_by_snap[:current]['disks'].each do |device_id, filename| current.add_element(:disk, :node => device_id, :filename => filename) end end end end def clear_snapshot_disks(sn_list) sn_list.each do |(_id, snapshot)| delete_items = [] snapshot.each { |d| delete_items << d if d.name == 'disk' } delete_items.each(&:remove!) end end def split_data(name) # Initialize variables full_pos, maj_pos, device = nil, "0", name # If the name contains a number, use it as the id pos_idx = name.index(/\d/) if pos_idx full_pos = name[pos_idx..-1] maj_pos, device = full_pos[0..0] device = name[0...pos_idx] end return full_pos, maj_pos, device, pos_idx end def find_with_attributes(findKey, findAttr, xmlNode) xmlNode.each_element do |e| if e.name == findKey found = true findAttr.each_pair do |k, v| found = false if e.attributes[k] != v end return e if found end end nil end def add_volumes(xml, miqvm) return unless @vol_mgr_loaded return if miqvm.nil? xml.root << miqvm.volumeManager.toXml.root end def configuration_fixup fixup_keys = [] @cfgHash.each_pair do |k, v| # If the cdrom does not have a filename force the 'startconnected' to false. # This is to handle "Client Device" settings in VC that does not maintain the startconnected value. if k.include?('.devicetype') && v.include?('cdrom') dskKey = k.gsub('.devicetype', '') fixup_keys << dskKey if @cfgHash[dskKey + ".filename"].to_s.delete('"').blank? end end fixup_keys.each { |k| @cfgHash["#{k}.startconnected"] = 'false' } end def collective_size_on_disk(device_id, files = []) total_size = 0 disks_by_node[device_id].each do |disk_name| file = files.detect { |f| f[:path] == disk_name } total_size += file[:size] unless file.nil? find_additional_disk_files(disk_name) { |f| total_size += f[:size] } if @direct_file_access end total_size end def split_filename(filename) file = filename.dup ds = nil if file =~ /^\[.*\] / ds = $&.strip[1..-2] file = $' end return ds, File.dirname(file), File.basename(file) end def base_disk_name(filename) ds, dir, name = split_filename(filename) if name =~ /-\d{6}[-|\.]/ # puts "#{$`}<<#{$&}>>#{$'}" return nil if $`.nil? basename = $` else basename = File.basename(name, '.*') basename.gsub!('-flat', '') end key = File.join(dir, basename) key = "[#{ds}] " + key unless ds.nil? key end def self.to_h(filename) dataHash = {} data = filename data = File.read(filename) unless filename.include?("\n") data.each_line do |l| l.strip! next if l[0..0] == '.' || l[0..0] == '#' || l.empty? parent = l.split('=')[0].split('.').inject(:current_level => dataHash, :hash => nil, :key => nil) do |h, k| a1 = h[:current_level][k.strip.to_sym] ||= {} a1 = h[:current_level][k.strip.to_sym] = {:_default => a1} unless a1.kind_of?(Hash) {:current_level => a1, :hash => h, :key => k.strip.to_sym} end uh, key = parent[:hash][:current_level], parent[:key] kkey = key.to_s.strip.to_sym value = eval_config(l.split('=')[1], kkey) uh[key] = value end dataHash end def self.eval_config(value, _key) value.strip! value = value[1..-2] if value[0, 1] == '"' && value[-1, 1] == '"' [true, false].each { |b| return b if value.downcase == b.to_s } return value.to_i if value =~ /^\d/ return nil if value.blank? value end def normalize_file_paths(config_file = @configFile) @cfgHash.each_pair do |k, v| @cfgHash[k] = normalize_file_path(v, config_file) if k.include?('.filename') end end def normalize_file_path(filename, config_file = @configFile) cfg_ds, cfg_dir, _cfg_name = split_filename(config_file) ds, dir, name = split_filename(filename) if dir.blank? || dir == '.' name = File.join(cfg_dir, name) name = "[#{cfg_ds}] " + name unless cfg_ds.nil? return name else # Check for files that do not have a ds when the config file does return resolve_ds_path(filename) if cfg_ds && ds.nil? end filename end def disks_by_node result = Hash.new { |h, k| h[k] = [] } # Keep track of the disk we process so they do not get counted twice. # This handles independent-persistent disk that do not take part in snapshots but will # show up in each snapshot's disk list. processed_disks = [] normalize_file_paths getDiskFileHash.each_pair do |disk_id, path| unless processed_disks.include?(path) result[disk_id] << path processed_disks << path end end getSnapshotDiskFileHash.each_pair do |_id, h| h['disks'].each_pair do |disk_id, path| unless processed_disks.include?(path) result[disk_id] << path processed_disks << path end end end result end def each_disks # Call disks_by_node since it will remove duplicate disks disks_by_node.each_pair { |device_id, disks| disks.each { |d| yield(device_id, d) } } end def disks_by_snapshot_and_node result = Hash.new { |h, k| h[k] = {} } getSnapshotDiskFileHash.each_pair { |id, h| result[id] = h } result[:current] = {'disks' => {}} current_disks = result[:current]['disks'] getDiskFileHash.each_pair do |device_id, d| # Independent disks do not par-take in snapshots. Check the mode of the parent disk. next if @cfgHash["#{device_id}.mode"].to_s.include?('independent') current_disks[device_id] = d end result end def find_additional_disk_files(filename) ds, dir, name = split_filename(normalize_file_path(filename)) dfBase, ext = File.basename(name, ".*"), File.extname(name) %w(-flat -delta).each do |disk_type| search_filename = file_join(dir, dfBase + disk_type + ext) search_filename = "[#{ds}] " + search_filename unless ds.nil? f = find_file(search_filename) yield(f) unless f.nil? end end # Ensures that windows paths use forward slashes to full path lookups work. def file_join(*args) filename = File.join(*args) filename.tr!('\\', '/') if filename[1, 1] == ':' filename end end