ManageIQ/manageiq-smartstate

View on GitHub
lib/VolumeManager/MiqVolumeManager.rb

Summary

Maintainability
C
1 day
Test Coverage
F
29%
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