ManageIQ/manageiq-providers-ibm_power_hmc

View on GitHub
app/models/manageiq/providers/ibm_power_hmc/inventory/parser/infra_manager.rb

Summary

Maintainability
A
0 mins
Test Coverage
A
93%
class ManageIQ::Providers::IbmPowerHmc::Inventory::Parser::InfraManager < ManageIQ::Providers::IbmPowerHmc::Inventory::Parser
  OS_MAP = {
    'phyp' => 'IBM PowerVM hypervisor (PHYP)'
  }.freeze

  def parse
    $ibm_power_hmc_log.info("#{self.class}##{__method__} start")
    collector.collect! do
      parse_cecs
      parse_cecs_unavailable
      parse_lpars
      parse_vioses
      parse_templates
      parse_ssps
      parse_resource_pools
    end
    $ibm_power_hmc_log.info("#{self.class}##{__method__} end")
  end

  def parse_cecs
    collector.cecs.each do |sys|
      host = persister.hosts.build(
        :uid_ems             => sys.uuid,
        :ems_ref             => sys.uuid,
        :name                => sys.name,
        :hypervisor_hostname => "#{sys.mtype}-#{sys.model}*#{sys.serial}",
        :hostname            => sys.hostname,
        :ipaddress           => sys.ipaddr,
        :power_state         => lookup_power_state(sys.state),
        :vmm_vendor          => "ibm_power_hmc"
      )

      parse_host_operating_system(host, sys)
      parse_host_hardware(host, sys)
      parse_host_advanced_settings(host, sys)
      parse_vswitches(host, sys)
      parse_vlans(host, sys)
    end
  end

  def parse_cecs_unavailable
    collector.cecs_unavailable.each do |sys|
      mtype_model, serial = sys["MTMS"].split("*")
      host = persister.hosts.build(
        :uid_ems             => sys["UUID"],
        :ems_ref             => sys["UUID"],
        :name                => sys["SystemName"],
        :hypervisor_hostname => sys["MTMS"],
        :ipaddress           => sys["IPAddress"],
        :power_state         => lookup_power_state(sys["State"]),
        :vmm_vendor          => "ibm_power_hmc"
      )
      persister.host_operating_systems.build(
        :host         => host,
        :product_name => OS_MAP['phyp'],
        :build_number => sys["SystemFirmware"]
      )
      persister.host_hardwares.build(
        :host                 => host,
        :cpu_type             => "ppc64",
        :bitness              => 64,
        :manufacturer         => "IBM",
        :model                => mtype_model,
        :memory_mb            => sys["InstalledSystemMemory"],
        :cpu_sockets          => sys["InstalledSystemProcessorUnits"],
        :cpu_total_cores      => sys["InstalledSystemProcessorUnits"],
        :cpu_cores_per_socket => 1,
        :serial_number        => serial
      )
    end
  end

  def self.storage_capacity(storage)
    case storage
    when IbmPowerHmc::VirtualMediaRepository, IbmPowerHmc::VirtualOpticalMedia
      storage.size.to_f.gigabytes.to_i
    when IbmPowerHmc::SharedStoragePool, IbmPowerHmc::LogicalUnit, IbmPowerHmc::VirtualDisk
      storage.capacity.to_f.gigabytes.to_i
    else
      storage.capacity.to_f.megabytes.to_i
    end
  end

  def self.storage_type(storage)
    case storage
    when IbmPowerHmc::PhysicalVolume
      "Physical Volume"
    when IbmPowerHmc::VirtualDisk
      "Logical Volume"
    when IbmPowerHmc::VirtualOpticalMedia
      "Optical Media"
    when IbmPowerHmc::LogicalUnit
      "Logical Unit"
    end
  end

  def parse_ssps
    collector.ssps.each do |ssp|
      persister.storages.build(
        :store_type  => "SSP",
        :name        => ssp.name,
        :total_space => self.class.storage_capacity(ssp),
        :ems_ref     => ssp.cluster_uuid,
        :free_space  => ssp.free_space.to_f.gigabytes.round
      )
    end
  end

  def parse_lpar_disks(lpar, hardware)
    return unless collector.lpar_disks.key?(lpar.uuid) # LPAR has no disk

    collector.lpar_disks[lpar.uuid].group_by { |d| d[:udid] }.each do |udid, paths|
      disk = paths.first
      size = disk[:storage] ? self.class.storage_capacity(disk[:storage]) : disk[:size]

      persister.disks.build(
        :hardware        => hardware,
        :location        => paths.pluck(:client_drc).sort.uniq.join(","),
        :device_name     => udid,
        :device_type     => disk[:type],
        :storage         => disk[:cluster_id] ? persister.storages.lazy_find(disk[:cluster_id]) : nil,
        :size            => size,
        :size_on_disk    => size,
        :mode            => disk[:mode],
        :disk_type       => disk[:storage] ? self.class.storage_type(disk[:storage]) : disk[:disk_type],
        :thin            => disk[:thin],
        :filename        => disk[:path],
        :controller_type => "SCSI"
      )
    end
  end

  def parse_vios_disks(vios, hardware)
    vios.pvs.each do |pv|
      size = self.class.storage_capacity(pv)

      persister.disks.build(
        :hardware        => hardware,
        :location        => pv.location,
        :device_name     => pv.name,
        :device_type     => "disk",
        :size            => size,
        :size_on_disk    => size,
        :mode            => "rw",
        :disk_type       => self.class.storage_type(pv),
        :controller_type => pv.is_fc == "true" ? "FC" : "SCSI"
      )
    end
  end

  def parse_host_operating_system(host, sys)
    persister.host_operating_systems.build(
      :host         => host,
      :product_name => OS_MAP['phyp'],
      :build_number => sys.fwversion
    )
  end

  def parse_host_hardware(host, sys)
    hardware = persister.host_hardwares.build(
      :host                 => host,
      :cpu_type             => "ppc64",
      :bitness              => 64,
      :manufacturer         => "IBM",
      :model                => "#{sys.mtype}#{sys.model}",
      :cpu_speed            => collector.cec_cpu_freqs[sys.uuid],
      :memory_mb            => sys.memory,
      :cpu_sockets          => sys.cpus,
      :cpu_total_cores      => sys.cpus,
      :cpu_cores_per_socket => 1,
      :serial_number        => sys.serial
    )
    parse_host_guest_devices(hardware, sys)
  end

  def parse_host_guest_devices(hardware, sys)
    sys.io_slots.each do |slot|
      io = slot.io_adapter
      next if io.nil? || io.udid.to_i == 65_535 # Skip empty slots

      child_devices = slot.ior_devices.map do |port|
        persister.host_guest_devices.build(
          :hardware        => hardware,
          :uid_ems         => port.location,
          :device_type     => "physical_port",
          :controller_type => "IO",
          :device_name     => "Port",
          :location        => port.location,
          :model           => port.description,
          :address         => port.macaddr.nil? ? port.wwpn : self.class.parse_macaddr(port.macaddr),
          :auto_detect     => true
        )
      end

      persister.host_guest_devices.build(
        :hardware        => hardware,
        :uid_ems         => io.dr_name,
        :device_type     => "physical_port",
        :controller_type => "IO",
        :device_name     => "Adapter",
        :location        => io.dr_name,
        :model           => io.description,
        :auto_detect     => true,
        :child_devices   => child_devices
      )
    end
  end

  def parse_host_advanced_settings(host, sys)
    if collector.pcm_enabled[sys.uuid]
      persister.hosts_advanced_settings.build(
        :resource     => host,
        :name         => "pcm_enabled",
        :display_name => _("PCM-enabled"),
        :description  => _("Performance and Capacity Monitoring data collection enabled"),
        :value        => collector.pcm_enabled[sys.uuid].aggregation,
        :read_only    => true
      )
    end
    persister.hosts_advanced_settings.build(
      :resource     => host,
      :name         => "hmc_managed",
      :display_name => _("HMC-managed"),
      :description  => _("The PowerVM management master of this host is a HMC."),
      :value        => sys.is_classic_hmc_mgmt.eql?("true") ? sys.is_classic_hmc_mgmt : sys.is_hmc_mgmt_master,
      :read_only    => true
    )
  end

  def parse_lpars
    collector.lpars.each do |lpar|
      _vm, hardware = parse_lpar_common(lpar, ManageIQ::Providers::IbmPowerHmc::InfraManager::Lpar.name)
      parse_lpar_disks(lpar, hardware)
      parse_lpar_guest_devices(lpar, hardware)
    end
  end

  def parse_vioses
    collector.vioses.each do |vios|
      _vm, hardware = parse_lpar_common(vios, ManageIQ::Providers::IbmPowerHmc::InfraManager::Vios.name)
      parse_vios_disks(vios, hardware)
      parse_vios_networks(vios, hardware)
      parse_vios_guest_devices(vios, hardware)
      parse_vios_media_repository(vios)
    end
  end

  def parse_lpar_common(lpar, type)
    # Common code for LPARs and VIOSes.
    host = persister.hosts.lazy_find(lpar.sys_uuid)
    resource_pool = lpar.shared_processor_pool_uuid ? persister.resource_pools.lazy_find("#{lpar.sys_uuid}_#{lpar.shared_processor_pool_uuid}") : nil
    vm = persister.vms.build(
      :type            => type,
      :uid_ems         => lpar.uuid,
      :ems_ref         => lpar.uuid,
      :name            => lpar.name,
      :location        => "unknown",
      :vendor          => "ibm_power_hmc",
      :description     => lpar.description.to_s,
      :raw_power_state => lpar.state,
      :host            => host,
      :resource_pool   => resource_pool
    )
    parse_vm_operating_system(vm, lpar)
    parse_vm_advanced_settings(vm, lpar)
    parse_vm_labels(vm, lpar)

    hardware = parse_vm_hardware(vm, lpar)
    parse_vm_networks(lpar, hardware)
    parse_vm_guest_devices(lpar, hardware)

    [vm, hardware]
  end

  def parse_vm_labels(vm, lpar)
    # Common code for LPARs and VIOSes.
    lpar.group_uuids.each do |uuid|
      next unless collector.groups.key?(uuid)

      group = collector.groups[uuid]
      persister.vm_and_template_labels.build(
        :resource    => vm,
        :name        => group.name,
        :section     => "labels",
        :source      => "ibm_power_hmc",
        :value       => "",
        :description => group.description
      )
    end
  end

  def parse_vm_hardware(vm, lpar)
    # Common code for LPARs, VIOSes and templates.
    num_cpus = lpar.dedicated.eql?("true") ? lpar.procs.to_i : lpar.vprocs.to_i
    persister.hardwares.build(
      :vm_or_template       => vm,
      :memory_mb            => lpar.memory,
      :cpu_type             => "ppc64",
      :cpu_speed            => lpar.respond_to?(:sys_uuid) ? collector.cec_cpu_freqs[lpar.sys_uuid] : nil,
      :cpu_sockets          => num_cpus,
      :cpu_total_cores      => num_cpus,
      :cpu_cores_per_socket => 1
    )
  end

  def parse_vm_networks(lpar, hardware)
    if lpar.rmc_ipaddr
      persister.networks.build(
        :hardware    => hardware,
        :ipaddress   => lpar.rmc_ipaddr,
        :ipv6address => nil
      )
    end
  end

  def parse_vios_networks(vios, hardware)
    vios.seas.collect(&:iface).compact.each do |iface|
      next if iface.ip.nil?

      # If the IP is the same as the RMC one, complete it with additional information.
      persister.networks.find_or_build_by(:hardware => hardware, :ipaddress => iface.ip, :ipv6address => nil).assign_attributes(
        :ipaddress       => iface.ip,
        :ipv6address     => nil,
        :subnet_mask     => iface.netmask,
        :hostname        => iface.hostname,
        :default_gateway => iface.gateway
      )
    end
  end

  def parse_vios_media_repository(vios)
    return if vios.rep.nil?

    storage = persister.storages.build(
      :ems_ref     => vios.uuid,
      :type        => ManageIQ::Providers::IbmPowerHmc::InfraManager::MediaRepository.name,
      :store_type  => "ISO",
      :name        => vios.rep.name,
      :total_space => self.class.storage_capacity(vios.rep)
    )
    # Populate with ISO images.
    vios.rep.vopts.each do |vopt|
      persister.iso_images.build(
        :storage => storage,
        :name    => vopt.name
      )
    end
  end

  def parse_vswitches(host, sys)
    if collector.vswitches.key?(sys.uuid)
      collector.vswitches[sys.uuid].each do |vswitch|
        switch = persister.host_virtual_switches.build(
          :uid_ems => vswitch.uuid,
          :name    => vswitch.name,
          :host    => host
        )
        persister.host_switches.build(:host => host, :switch => switch)
      end
    end
  end

  def parse_vlans(host, sys)
    if collector.vlans.key?(sys.uuid)
      collector.vlans[sys.uuid].each do |vlan|
        vswitch = persister.host_virtual_switches.lazy_find(:host => host, :uid_ems => vlan.vswitch_uuid)
        persister.lans.build(
          :switch  => vswitch,
          :uid_ems => vlan.uuid,
          :tag     => vlan.vlan_id,
          :name    => vlan.name,
          :ems_ref => sys.uuid
        )
      end
    end
  end

  def parse_vm_operating_system(vm, lpar)
    if lpar.os.nil? || lpar.os.downcase == "unknown"
      # RSCT is not running on the LPAR
      if lpar.respond_to?(:type) && lpar.type == "Virtual IO Server"
        os_info = ["VIOS"]
      end
    else
      # HMC provides the OS version as a flat string.
      # We do our best to extract the name, version and build numbers from this string.
      # Also, HMCs older than v10 have a 32 characters limit on the OS name part.
      # Examples of what we get from HMC/RSCT:
      # "VIOS 3.1.0.11"
      # "AIX 7.3 7300-00-00-0000"
      # "Linux/Debian 4.4.0-87-generic Unknown"
      # "Linux/Hardware Management Conso V10R2 1030"
      # "Linux/Red Hat Enterprise Linux  4.18.0-372.19.1.el8_6.ppc8.4 (Ootpa) 8.4 (Ootpa)"
      # "Linux/Red Hat Enterprise Linux (CoreOS) 4.18.0-372.19.1.el8_6.ppc8.4 (Ootpa) 8.4 (Ootpa)"
      os_info = lpar.os.split
      if os_info.length > 3
        # Split on the first word that contains a digit
        os_info = lpar.os.split(/\s+(?=\S*\d)/, 2)
      end
    end

    if os_info
      persister.operating_systems.build(
        :vm_or_template => vm,
        :product_name   => os_info[0],
        :version        => os_info[1],
        :build_number   => os_info[2]
      )
    end
  end

  def parse_vm_guest_devices(lpar, hardware)
    if collector.netadapters.key?(lpar.uuid)
      collector.netadapters[lpar.uuid].each do |ent|
        build_ethernet_dev(lpar, ent, hardware, "client network adapter")
      end
    end

    if collector.sriov_elps.key?(lpar.uuid)
      collector.sriov_elps[lpar.uuid].each do |ent|
        build_ethernet_dev(lpar, ent, hardware, "sr-iov")
      end
    end

    lpar.lhea_ports.each do |ent|
      build_ethernet_dev(lpar, ent, hardware, "host ethernet adapter")
    end

    # Physical adapters can be assigned to VIOSes and LPARs.
    lpar.io_slots.each do |slot|
      build_io_adapter(slot.io_adapter, hardware) unless slot.io_adapter.nil?
    end
  end

  def parse_lpar_guest_devices(lpar, hardware)
    if collector.vnics.key?(lpar.uuid)
      collector.vnics[lpar.uuid].each do |ent|
        build_ethernet_dev(lpar, ent, hardware, "vnic")
      end
    end

    if collector.vfc_client_adapters.key?(lpar.uuid)
      collector.vfc_client_adapters[lpar.uuid].each do |vfc|
        persister.guest_devices.build(
          :hardware        => hardware,
          :uid_ems         => vfc.uuid,
          :device_name     => vfc.dr_name,
          :device_type     => "physical_port",
          :controller_type => "client VFC adapter",
          :auto_detect     => true,
          :address         => vfc.wwpns.join(","),
          :location        => vfc.location
        )
      end
    end
  end

  def parse_vios_guest_devices(vios, hardware)
    vios.trunks.each do |trunk|
      vlan = vlan_by_tag(vios.sys_uuid, trunk.vswitch_uuid, trunk.vlan_id)

      persister.guest_devices.build(
        :hardware        => hardware,
        :uid_ems         => trunk.location,
        :device_name     => trunk.name,
        :device_type     => "ethernet",
        :controller_type => "trunk adapter",
        :auto_detect     => true,
        :address         => self.class.parse_macaddr(trunk.macaddr),
        :location        => trunk.location,
        :lan             => vlan
      )
    end
  end

  def parse_vm_advanced_settings(vm, lpar)
    if lpar.respond_to?("id")
      persister.vms_and_templates_advanced_settings.build(
        :resource     => vm,
        :name         => "partition_id",
        :display_name => _("Partition ID"),
        :description  => _("The logical partition number"),
        :value        => lpar.id.to_i,
        :read_only    => true
      )
    end

    if lpar.respond_to?("ref_code")
      persister.vms_and_templates_advanced_settings.build(
        :resource     => vm,
        :name         => "reference_code",
        :display_name => _("Reference Code"),
        :description  => _("The logical partition reference code"),
        :value        => lpar.ref_code,
        :read_only    => true
      )
    end

    unless lpar.proc_units.nil?
      persister.vms_and_templates_advanced_settings.build(
        :resource     => vm,
        :name         => 'entitled_processors',
        :display_name => _('Entitled Processors'),
        :description  => _('The number of entitled processors assigned to the VM'),
        :value        => lpar.proc_units,
        :read_only    => true
      )
    end

    proc_type = lpar.dedicated == "true" ? "dedicated" : lpar.sharing_mode
    persister.vms_and_templates_advanced_settings.build(
      :resource     => vm,
      :name         => 'processor_type',
      :display_name => _('Processor type'),
      :description  => _('dedicated: Dedicated, shared: Uncapped shared, capped: Capped shared'),
      :value        => proc_type,
      :read_only    => true
    )

    mem_type = lpar.shared_mem == "true" ? "shared" : "dedicated"
    persister.vms_and_templates_advanced_settings.build(
      :resource     => vm,
      :name         => 'memory_type',
      :display_name => _('Memory type'),
      :description  => _('Dedicated or shared'),
      :value        => mem_type,
      :read_only    => true
    )
  end

  def parse_templates
    collector.templates.each do |template|
      t = persister.miq_templates.build(
        :uid_ems         => template.uuid,
        :ems_ref         => template.uuid,
        :name            => template.name,
        :description     => template.description,
        :vendor          => "ibm_power_hmc",
        :template        => true,
        :location        => "unknown",
        :raw_power_state => "never"
      )
      parse_vm_hardware(t, template)
      parse_vm_operating_system(t, template)
      parse_vm_advanced_settings(t, template)
    end
  end

  def lookup_power_state(state)
    # See SystemState.Enum (/rest/api/web/schema/inc/Enumerations.xsd)
    case state.downcase
    when /error.*/                    then "off"
    when "failed authentication"      then "off"
    when "incomplete"                 then "off"
    when "initializing"               then "on"
    when "no connection"              then "unknown"
    when "on demand recovery"         then "off"
    when "operating"                  then "on"
    when /pending authentication.*/   then "off"
    when "power off"                  then "off"
    when "power off in progress"      then "off"
    when "recovery"                   then "off"
    when "standby"                    then "off"
    when "version mismatch"           then "on"
    when "service processor failover" then "off"
    when "unknown"                    then "unknown"
    else                                   "off"
    end
  end

  def vlan_by_tag(sys_uuid, vswitch_uuid, vlan_id)
    host = persister.hosts.lazy_find(sys_uuid)
    vswitch = persister.host_virtual_switches.lazy_find(:host => host, :uid_ems => vswitch_uuid)
    persister.lans.lazy_find({:switch => vswitch, :tag => vlan_id}, :ref => :by_tag)
  end

  def build_ethernet_dev(lpar, ent, hardware, controller_type)
    id = ent.respond_to?(:uuid) ? ent.uuid : ent.macaddr

    macaddr = self.class.parse_macaddr(ent.macaddr)
    vlan = vlan_by_tag(lpar.sys_uuid, ent.vswitch_uuid, ent.vlan_id) if ent.kind_of?(IbmPowerHmc::ClientNetworkAdapter)

    persister.guest_devices.build(
      :hardware        => hardware,
      :uid_ems         => id,
      :device_name     => macaddr,
      :device_type     => "ethernet",
      :controller_type => controller_type,
      :auto_detect     => true,
      :address         => macaddr,
      :location        => ent.location,
      :lan             => vlan
    )
  end

  def build_io_adapter(adapter, hardware)
    # Parse physical adapter ports, if any.
    child_devices =
      case adapter
      when IbmPowerHmc::PhysicalFibreChannelAdapter
        adapter.ports.map do |fcs|
          persister.guest_devices.build(
            :hardware        => hardware,
            :uid_ems         => fcs.location,
            :location        => fcs.location,
            :device_name     => fcs.name.nil? ? fcs.location : fcs.name,
            :device_type     => "physical_port",
            :controller_type => "Fibre channel port",
            :address         => fcs.wwpn,
            :model           => adapter.description,
            :auto_detect     => true
          )
        end
      end || []

    persister.guest_devices.build(
      :hardware        => hardware,
      :uid_ems         => adapter.dr_name,
      :location        => adapter.dr_name,
      :device_name     => "Adapter",
      :device_type     => "physical_port",
      :controller_type => "IO",
      :model           => adapter.description,
      :auto_detect     => true,
      :child_devices   => child_devices
    )
  end

  def self.parse_macaddr(macaddr)
    macaddr.downcase.scan(/\w{2}/).join(':') unless macaddr.nil?
  end

  def parse_resource_pools
    parse_cpu_resource_pools
    parse_mem_resource_pools
  end

  def parse_cpu_resource_pools
    collector.shared_processor_pools.each do |pool|
      next if pool.name =~ /^SharedPool\d\d$/ && pool.max == "0"

      ref = "#{pool.sys_uuid}_#{pool.uuid}"
      params = {
        :uid_ems => ref,
        :ems_ref => ref,
        :name    => pool.name,
        :parent  => persister.hosts.lazy_find(pool.sys_uuid),
        :type    => ManageIQ::Providers::IbmPowerHmc::InfraManager::ProcessorResourcePool.name
      }
      if pool.name == "DefaultPool"
        params[:cpu_cores_reserve]   = 0
        params[:cpu_reserve_expand]  = false
        params[:cpu_cores_limit]     = -1
        params[:cpu_cores_available] = nil
        params[:is_default]          = true
      else
        params[:cpu_cores_reserve]   = pool.reserved
        params[:cpu_reserve_expand]  = true
        params[:cpu_cores_limit]     = pool.max
        params[:cpu_cores_available] = pool.available
        params[:is_default]          = false
      end

      persister.resource_pools.build(params)
    end
  end

  def parse_mem_resource_pools
    collector.shared_memory_pools.each do |pool|
      ref = "#{pool.sys_uuid}_#{pool.uuid}"
      persister.resource_pools.build(
        :uid_ems               => ref,
        :ems_ref               => ref,
        :name                  => "DefaultMemPool",
        :parent                => persister.hosts.lazy_find(pool.sys_uuid),
        :type                  => ManageIQ::Providers::IbmPowerHmc::InfraManager::MemoryResourcePool.name,
        :memory_shares         => pool.max_mb.to_i - pool.available_mb.to_i,
        :memory_reserve        => pool.available_mb,
        :memory_reserve_expand => true,
        :memory_limit          => pool.max_mb,
        :is_default            => false
      )
    end
  end
end