require 'ostruct' require 'sys-uname' require 'binary_struct' require 'VolumeManager/MiqLvm' require 'VolumeManager/MiqLdm' require 'VolumeManager/VolMgrPlatformSupport' require 'disk/modules/RawDisk' class MiqVolumeManager attr_accessor :rootTrees attr_reader :logicalVolumes, :physicalVolumes, :visibleVolumes, :hiddenVolumes, :allPhysicalVolumes, :vgHash, :lvHash, :diskFileNames def self.fromNativePvs return nil unless Sys::Platform::IMPL == :linux msg_pfx = "MiqVolumeManager.fromNativePvs" bdevs = `pvdisplay -c | cut -d: -f1`.tr(" \t", "").split("\n") ldevs = `lvdisplay -c | cut -d: -f1`.tr(" \t", "").split("\n") bdevs -= ldevs bda = [] bdevs.each do |bd| next if bd == "unknowndevice" $log.debug "#{msg_pfx}: Opening PV = #{bd}" diskInfo = OpenStruct.new diskInfo.rawDisk = true diskInfo.fileName = bd begin disk = MiqDisk.new(RawDisk, diskInfo, 0) rescue StandardError, NoMemoryError, SignalException => err $log.warn "#{msg_pfx}: Could not open PV: #{bd}" $log.warn err.to_s $log.debug err.backtrace.join("\n") next end raise "#{msg_pfx}: Failed to open disk: #{diskInfo.fileName}" unless disk bda << disk if $log.debug? $log.debug "#{msg_pfx}: Block device: #{bd}" $log.debug "#{msg_pfx}: \tDisk type: #{disk.diskType}" $log.debug "#{msg_pfx}: \tDisk partition type: #{disk.partType}" $log.debug "#{msg_pfx}: \tDisk block size: #{disk.blockSize}" $log.debug "#{msg_pfx}: \tDisk start LBA: #{disk.lbaStart}" $log.debug "#{msg_pfx}: \tDisk end LBA: #{disk.lbaEnd}" $log.debug "#{msg_pfx}: \tDisk start byte: #{disk.startByteAddr}" $log.debug "#{msg_pfx}: \tDisk end byte: #{disk.endByteAddr}" end end new(bda) end def initialize(pVols) @logicalVolumes = [] # visible logical volumes @physicalVolumes = [] # visible physical volumes @hiddenVolumes = [] # hidded volumes (in volume groups) @allPhysicalVolumes = [] # all physical volumes @vgHash = nil # volume groups hashed by name @rootTrees = nil # the MiqMountManager objects using this MiqVolumeManager object @vdlConnection = nil # connection for remote vmdk access. lvmPvHdrHash = {} # physical volume header list, for lvm pVols.each do |pv| pvh = LdmScanner.scan(pv) || Lvm2Scanner.labelScan(pv) if pvh pvh.diskObj = pv # add reference to the pv's open disk object to the pv header lvmPvHdrHash[pvh.pv_uuid] = pvh # this physical volume in an LVM volume group @hiddenVolumes << pv # so it's a hidden volume $log.info "MiqVolumeManager: #{pvh.lvm_type} metadata detected on PV: #{pv.dInfo.fileName}, partition: #{pv.partNum}" else @physicalVolumes << pv # this physical volume is not in an LVM volume group $log.debug "MiqVolumeManager: No LVM metadata detected on PV: #{pv.dInfo.fileName}, partition: #{pv.partNum}" end @allPhysicalVolumes << pv end @vgHash = {} parseLvmMetadata(lvmPvHdrHash) parseLvmThinMetadata @vgHash.each_value { |vg| @logicalVolumes.concat(vg.getLvs) } @lvHash = {} @logicalVolumes.each do |lvdObj| lv = lvdObj.dInfo.lvObj lvName = lv.lvName vgName = lv.vgObj.vgName @lvHash["/dev/#{vgName}/#{lvName}"] = lvdObj end @visibleVolumes = @logicalVolumes + @physicalVolumes end def close $log.info "MiqVolumeManager.close called" @logicalVolumes = @logicalVolumes.clear @physicalVolumes = @physicalVolumes.clear @hiddenVolumes = @hiddenVolumes.clear @vgHash = nil end # # Physical volumes are opened by the client, so the client should be responsible # for closing them. These methods are provided as a convienience when the volume # manager is instantiated through fromNativePvs(). # def closePvols @allPhysicalVolumes.each(&:close) @allPhysicalVolumes.clear end def closeAll $log.info "MiqVolumeManager.closeAll called" closePvols close end def parseLvmMetadata(pvHdrs) pvHdrs.each_value do |pvh| if pvh.lvm_type == "LVM2" $log.debug "MiqVolumeManager.parseLvmMetadata: parsing LVM2 metadata" pvh.mdList.each do |md| Lvm2MdParser.dumpMetadata(md) if $log.debug? parser = Lvm2MdParser.new(md, pvHdrs) next if @vgHash[parser.vgName] @vgHash[parser.vgName] = parser.parse # @vgHash[parser.vgName].dump end elsif pvh.lvm_type == "LDM" $log.debug "MiqVolumeManager.parseLvmMetadata: parsing LDM metadata" parser = LdmMdParser.new(pvh, pvHdrs) next if @vgHash[parser.vgName] @vgHash[parser.vgName] = parser.parse else $log.debug "MiqVolumeManager.parseLvmMetadata: unknown metadata type #{pvh.lvm_type}" next end end end def parseLvmThinMetadata @vgHash.each do |vgname, vg| $log.debug "MiqVolumeManager.parseLvmThinMetadata: setting LVM2 thin metadata" vg.thin_volumes.each do |tv| tv.thin_segments.each do |seg| seg.set_thin_pool_volume vg.logicalVolumes.values end end vg.thin_pool_volumes.each do |tpv| tpv.thin_pool_segments.each do |seg| seg.set_metadata_volume vg.logicalVolumes.values seg.set_data_volume vg.logicalVolumes.values end end end end def toXml(doc = nil) doc = MiqXml.createDoc(nil) unless doc vi = doc.add_element 'volumes' pvToXml(vi, false) pvToXml(vi, true) lvToXml(vi) vgToXml(vi) doc end def pvToXml(doc = nil, hidden = false) doc = MiqXml.createDoc(nil) unless doc if hidden vols = @hiddenVolumes volType = 'hidden' else vols = @physicalVolumes volType = 'physical' end pvs = doc.add_element volType vols.each do |dobj| pv = pvs.add_element('volume', "controller" => dobj.hwId, "disk_type" => dobj.diskType, "location" => dobj.partNum, "partition_type" => dobj.partType, "size" => dobj.size, "virtual_disk_file" => dobj.dInfo.fileName, "start_address" => dobj.startByteAddr,) if @rootTrees && @rootTrees.length > 0 pv.add_attribute("name", @rootTrees[0].osNames[dobj.hwId].to_s) if @rootTrees[0].osNames fs = @rootTrees[0].fileSystems.find { |f| f.fs.dobj.hwId == dobj.hwId } unless fs.nil? pv.add_attributes("filesystem" => fs.fs.fsType, "free_space" => fs.fs.freeBytes, "used_space" => dobj.size - fs.fs.freeBytes) end end if dobj.pvObj pv.add_attributes("volume_group" => dobj.pvObj.vgObj.vgName, "uid" => dobj.pvObj.pvId) end end doc end def lvToXml(doc = nil) doc = MiqXml.createDoc(nil) unless doc lvs = doc.add_element 'logical' @logicalVolumes.each do |dobj| lvObj = dobj.dInfo.lvObj name = lvObj.driveHint.blank? ? lvObj.lvName : lvObj.driveHint lv = lvs.add_element('volume', "name" => name, "type" => dobj.diskType, "size" => dobj.size, "uid" => lvObj.lvId, "volume_group" => lvObj.vgObj.vgName, "drive_hint" => lvObj.driveHint, "volume_name" => lvObj.lvName,) if @rootTrees && @rootTrees.length > 0 fs = @rootTrees[0].fileSystems.find { |f| f.fs.dobj.dInfo.lvObj && f.fs.dobj.dInfo.lvObj.lvName == lvObj.lvName } unless fs.nil? lv.add_attributes("filesystem" => fs.fs.fsType, "free_space" => fs.fs.freeBytes, "used_space" => dobj.size - fs.fs.freeBytes) end end end doc end def vgToXml(doc = nil) doc = MiqXml.createDoc(nil) unless doc vgs = doc.add_element 'volume_groups' @vgHash.each do |vgn, vgo| pext = 0 lext = 0 vg = vgs.add_element('volume_group', {"name" => vgn}) pvs = vg.add_element 'physical' vgo.physicalVolumes.each do |pvn, pvo| pvs.add_element('volume', "name" => pvn, "uid" => pvo.pvId, "controller" => pvo.diskObj.hwId, "os_name" => pvo.device, "physical_extents" => pvo.peCount, "virtual_disk_file" => pvo.diskObj.dInfo.fileName) pext += pvo.peCount end lvs = vg.add_element 'logical' vgo.logicalVolumes.each do |lvn, lvo| lvs.add_element('volume', "name" => lvn, "uid" => lvo.lvId) lvo.segments.each { |s| lext += s.extentCount } end vg.add_attributes("extent_size" => vgo.extentSize, "physical_extents" => pext, "logical_extents" => lext, "free_extents" => pext - lext) end if @vgHash doc end end # class MiqVolumeManager