ManageIQ/manageiq-providers-vmware

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

Summary

Maintainability
A
2 hrs
Test Coverage
A
90%
class ManageIQ::Providers::Vmware::InfraManager::Inventory::Parser
  module HostSystem
    def datacenter_ref_by_host(host)
      @datacenter_ref_by_host ||= {}
      @datacenter_ref_by_host[host._ref] ||= find_parent_datacenter(host)
    end

    def network_ref_by_datacenter_and_name
      @network_ref_by_datacenter_and_name ||= cache["Network"].each_with_object({}) do |(ref, data), result|
        result[find_parent_datacenter(data[:parent])._ref] ||= {}
        result[find_parent_datacenter(data[:parent])._ref][data[:name]] = ref
      end
    end

    def validate_host_system_props(object, props)
      # We use summary and summary.config in order to set some required properties for hosts,
      # if these are missing we won't have enough information to build a useful record.
      if props.fetch_path(:summary, :config, :product).nil?
        [true, "Missing summary for Host [#{object._ref}]"]
      else
        false
      end
    end

    def parse_host_system_summary(host_hash, props)
      summary = props[:summary]
      return if summary.nil?

      host_hash[:uid_ems] = summary.fetch_path(:config, :name)
    end

    def parse_host_system_config(host_hash, props)
      config = props[:config]
      return if config.nil?

      host_hash[:admin_disabled] = config[:adminDisabled].to_s.downcase == "true"
      host_hash[:hyperthreading] = config.fetch_path(:hyperThread, :active).to_s.downcase == "true"
    end

    def parse_host_system_network(host_hash, props)
      network = props.fetch_path(:config, :network)
      return if network.nil?

      dns_config = network[:dnsConfig]
      if dns_config
        hostname    = dns_config[:hostName]
        domain_name = dns_config[:domainName]

        hostname = "#{hostname}.#{domain_name}" if domain_name.present?

        host_hash[:name]     = hostname
        host_hash[:hostname] = hostname
      end

      if network.fetch_path(:ipRouteConfig, :defaultGateway)
        require 'ipaddr'
        default_gw = IPAddr.new(network.fetch_path(:ipRouteConfig, :defaultGateway))
      end

      unless default_gw.nil?
        vnics = []

        console_vnic = network[:consoleVnic]
        vnics.concat(console_vnic) if console_vnic.present?

        network_vnic = network[:vnic]
        vnics.concat(network_vnic) if network_vnic.present?

        vnics.each do |vnic|
          ip = vnic.spec.ip.ipAddress
          subnet_mask = vnic.spec.ip.subnetMask

          next if ip.blank? || subnet_mask.blank? || !default_gw.mask(subnet_mask).include?(ip)

          host_hash[:ipaddress] = ip
          break
        end
      end
    end

    def parse_host_system_host_networks(_host, hardware, props)
      network = props.fetch_path(:config, :network)
      return if network.nil?

      vnics = Array(network[:consoleVnic]) + Array(network[:vnic])
      vnics.each do |vnic|
        port_key = vnic.port
        portgroup = Array(network[:portgroup]).find { |pg| pg.port.find { |p| p.key == port_key } }
        next if portgroup.nil?

        vswitch_key = portgroup[:vswitch]
        vswitch = network[:vswitch].find { |v| v.key == vswitch_key }
        next if vswitch.nil?

        pnic_key = vswitch.pnic.first
        pnic = network[:pnic].find { |p| p.key == pnic_key }
        next if pnic.nil?

        ip = vnic.spec&.ip
        next if ip.nil?

        persister.host_networks.build(
          :hardware     => hardware,
          :description  => pnic.device,
          :dhcp_enabled => ip.dhcp,
          :ipaddress    => ip.ipAddress,
          :subnet_mask  => ip.subnetMask
        )
      end
    end

    def parse_host_system_product(host_hash, props)
      product = props.fetch_path(:summary, :config, :product)
      return if product.nil?

      host_hash[:vmm_vendor] = host_system_vendor(product[:vendor])

      product_name = product[:name]
      host_hash[:vmm_product]     = product_name.nil? ? nil : product_name.to_s.gsub(/^VMware\s*/i, "")
      host_hash[:vmm_version]     = product[:version]
      host_hash[:vmm_buildnumber] = product[:build]
    end

    def host_system_vendor(vendor)
      return "unknown" if vendor.nil?

      vendor = vendor.split(",").first.to_s.downcase
      vendor = "unknown" unless Host::VENDOR_TYPES.include?(vendor)

      vendor
    end

    def parse_host_system_runtime(host_hash, props)
      runtime = props.fetch_path(:summary, :runtime)
      return if runtime.nil?

      host_hash[:connection_state] = runtime[:connectionState]

      host_hash[:maintenance] = runtime[:inMaintenanceMode].to_s.downcase == "true"
      host_hash[:power_state] = if host_hash[:connection_state] != "connected"
                                  "off"
                                elsif host_hash[:maintenance]
                                  "maintenance"
                                else
                                  "on"
                                end
    end

    def parse_host_system_system_info(host_hash, props)
      other_identifying_info = props.fetch_path(:hardware, :systemInfo, :otherIdentifyingInfo)
      return if other_identifying_info.nil?

      asset_tag = service_tag = nil
      other_identifying_info.each do |info|
        value = info.identifierValue.to_s.strip
        value = nil if value.blank?

        case info.identifierType.key
        when "AssetTag"   then asset_tag   = value
        when "ServiceTag" then service_tag = value
        end
      end

      host_hash[:asset_tag] = asset_tag
      host_hash[:service_tag] = service_tag
    end

    def parse_host_system_operating_system(host, props)
      persister.host_operating_systems.build(
        :host         => host,
        :name         => host.data[:hostname],
        :product_name => host.data[:vmm_product],
        :version      => host.data[:vmm_version],
        :build_number => host.data[:vmm_buildnumber],
        :product_type => props.fetch_path(:summary, :config, :product, :osType)
      )
    end

    def parse_host_system_system_services(host, props)
      host_service_info = props.fetch_path(:config, :service, :service)
      return if host_service_info.nil?

      host_service_info.each do |service|
        persister.host_system_services.build(
          :host         => host,
          :name         => service[:key],
          :display_name => service[:label],
          :running      => service[:running]
        )
      end
    end

    def parse_host_system_hardware(host, props)
      hardware_hash = {:host => host}

      hardware = props.fetch_path(:summary, :hardware)
      if hardware
        hardware_hash[:cpu_speed] = hardware[:cpuMhz]
        hardware_hash[:cpu_type] = hardware[:cpuModel]
        hardware_hash[:manufacturer] = hardware[:vendor]
        hardware_hash[:model] = hardware[:model]
        hardware_hash[:number_of_nics] = hardware[:numNics]

        # Value provided by VC is in bytes, need to convert to MB
        memory_size = hardware[:memorySize]
        hardware_hash[:memory_mb] = is_numeric?(memory_size) ? (memory_size.to_f / 1.megabyte).round : nil

        memory_console = props.fetch_path(:console, :consoleReservation, :serviceConsoleReserved)
        hardware_hash[:memory_console] = is_numeric?(memory_console) ? (memory_console.to_f / 1.megabyte).round : nil
        hardware_hash[:cpu_sockets] = hardware[:numCpuPkgs]
        hardware_hash[:cpu_total_cores] = hardware[:numCpuCores]
        hardware_hash[:cpu_cores_per_socket] = (hardware_hash[:cpu_total_cores].to_f / hardware_hash[:cpu_sockets]).to_i
      end

      summary_config = props.fetch_path(:summary, :config)
      if summary_config
        guest_os = summary_config.fetch_path(:product, :name).to_s.gsub(/^VMware\s*/i, "")
        hardware_hash[:guest_os] = guest_os
        hardware_hash[:guest_os_full_name] = guest_os

        vmotion_enabled = summary_config[:vmotionEnabled]
        hardware_hash[:vmotion_enabled] = vmotion_enabled.to_s.downcase == "true"
      end

      quick_stats = props.fetch_path(:summary, :quickStats)
      if quick_stats
        hardware_hash[:cpu_usage] = quick_stats[:overallCpuUsage]
        hardware_hash[:memory_usage] = quick_stats[:overallMemoryUsage]
      end

      persister_hardware = persister.host_hardwares.build(hardware_hash)

      parse_host_system_network_adapters(persister_hardware, props)
      parse_host_system_storage_adapters(persister_hardware, props)

      persister_hardware
    end

    def parse_host_system_network_adapters(hardware, props)
      pnics = props.fetch_path(:config, :network, :pnic)
      pnics.to_a.each do |pnic|
        name = uid = pnic.device

        link_speed  = pnic.linkSpeed&.speedMb
        link_speed *= 1000 if link_speed

        persister.host_guest_devices.build(
          :hardware        => hardware,
          :uid_ems         => uid,
          :device_name     => name,
          :device_type     => 'ethernet',
          :location        => pnic.pci,
          :present         => true,
          :controller_type => 'ethernet',
          :address         => pnic.mac,
          :speed           => link_speed
        )
      end
    end

    def parse_host_system_scsi_luns(scsi_luns)
      scsi_luns.to_a.each_with_object({}) do |lun, result|
        if lun.kind_of?(RbVmomi::VIM::HostScsiDisk)
          n_blocks   = lun.capacity.block
          block_size = lun.capacity.blockSize
          capacity   = (n_blocks * block_size) / 1024
        end

        lun_hash = {
          :uid_ems        => lun.uuid,
          :lun_type       => lun.lunType,
          :canonical_name => lun.canonicalName,
          :device_name    => lun.deviceName,
          :device_type    => lun.deviceType,
          :block          => n_blocks,
          :block_size     => block_size,
          :capacity       => capacity,
        }

        result[lun.key] = lun_hash
      end
    end

    def parse_host_system_scsi_targets(scsi_adapters, scsi_lun_uids)
      scsi_adapters.to_a.each_with_object({}) do |adapter, result|
        result[adapter.adapter] = adapter.target.to_a.map do |target|
          uid = target.target.to_s

          transport = target.transport
          if transport && transport.kind_of?(RbVmomi::VIM::HostInternetScsiTargetTransport)
            iscsi_name  = target.transport.iScsiName
            iscsi_alias = target.transport.iScsiAlias
            address     = target.transport.address
          end

          scsi_luns = target.lun.to_a.map do |lun|
            scsi_lun_uids[lun.scsiLun]&.merge(:lun => lun.lun.to_s)
          end

          {
            :uid_ems       => uid,
            :target        => uid,
            :iscsi_name    => iscsi_name,
            :iscsi_alias   => iscsi_alias,
            :address       => address,
            :miq_scsi_luns => scsi_luns,
          }
        end
      end
    end

    def parse_host_system_storage_adapters(hardware, props)
      storage_devices = props.dig(:config, :storageDevice)
      return if storage_devices.blank?

      scsi_lun_uids = parse_host_system_scsi_luns(storage_devices[:scsiLun])

      scsi_adapters = storage_devices.dig(:scsiTopology, :adapter)
      scsi_targets_by_adapter = parse_host_system_scsi_targets(scsi_adapters, scsi_lun_uids)

      hbas = storage_devices[:hostBusAdapter]
      hbas.to_a.each do |hba|
        name = uid = hba.device
        location = hba.pci
        model = hba.model

        if hba.kind_of?(RbVmomi::VIM::HostInternetScsiHba)
          iscsi_name = hba.iScsiName
          iscsi_alias = hba.iScsiAlias
          chap_auth_enabled = hba.authenticationProperties.chapAuthEnabled
        end

        controller_type = case hba
                          when RbVmomi::VIM::HostBlockHba
                            "Block"
                          when RbVmomi::VIM::HostFibreChannelHba
                            "Fibre"
                          when RbVmomi::VIM::HostInternetScsiHba
                            "iSCSI"
                          when RbVmomi::VIM::HostParallelScsiHba
                            "SCSI"
                          else
                            "HBA"
                          end

        persister_guest_device = persister.host_guest_devices.build(
          :hardware          => hardware,
          :uid_ems           => uid,
          :device_name       => name,
          :device_type       => 'storage',
          :present           => true,
          :iscsi_name        => iscsi_name,
          :iscsi_alias       => iscsi_alias,
          :location          => location,
          :model             => model,
          :chap_auth_enabled => chap_auth_enabled,
          :controller_type   => controller_type
        )

        scsi_targets_by_adapter[hba.key].to_a.each do |scsi_target|
          miq_scsi_luns = Array.wrap(scsi_target.delete(:miq_scsi_luns))

          persister_scsi_target = persister.miq_scsi_targets.build(
            scsi_target.merge(:guest_device => persister_guest_device)
          )

          miq_scsi_luns.each do |miq_scsi_lun|
            persister.miq_scsi_luns.build(
              miq_scsi_lun.merge(:miq_scsi_target => persister_scsi_target)
            )
          end
        end
      end
    end

    def parse_host_system_switches(host, props)
      network = props.fetch_path(:config, :network)
      return if network.blank?

      type = ManageIQ::Providers::Vmware::InfraManager::HostVirtualSwitch.name

      switches = network[:vswitch]
      persister_switches = switches.to_a.map do |switch|
        security_policy = switch.spec&.policy&.security
        if security_policy
          allow_promiscuous = security_policy[:allowPromiscuous]
          forged_transmits  = security_policy[:forgedTransmits]
          mac_changes       = security_policy[:macChanges]
        end

        persister_switch = persister.host_virtual_switches.build(
          :host              => host,
          :uid_ems           => switch[:name],
          :mtu               => switch[:mtu],
          :name              => switch[:name],
          :type              => type,
          :ports             => switch[:numPorts],
          :allow_promiscuous => allow_promiscuous,
          :forged_transmits  => forged_transmits,
          :mac_changes       => mac_changes
        )

        switch.pnic.to_a.each do |pnic|
          pnic_uid_ems = pnic.split("-").last
          next if pnic_uid_ems.nil?

          hardware = persister.host_hardwares.find(host)
          persister_guest_device = persister.host_guest_devices.find_or_build_by(:hardware => hardware, :uid_ems => pnic_uid_ems)
          persister_guest_device.assign_attributes(
            :switch => persister_switch
          )
        end

        persister_switch
      end

      type = ManageIQ::Providers::Vmware::InfraManager::OpaqueSwitch.name
      opaque_persister_switches = network[:opaqueSwitch].to_a.map do |switch|
        uid = switch.key
        persister.host_virtual_switches.build(
          :host    => host,
          :uid_ems => uid,
          :name    => switch.name,
          :type    => type
        )
      end

      persister_switches.concat(opaque_persister_switches)
    end

    def parse_host_system_distributed_switches(host)
      dvs_mors = %w[VmwareDistributedVirtualSwitch DistributedVirtualSwitch].flat_map do |dvs_klass|
        dvs = cache[dvs_klass].select do |_mor, props|
          props.fetch_path(:summary, :hostMember).any? { |h| h._ref == host.ems_ref }
        end

        dvs.collect { |mor, _| mor }
      end
      dvs_mors.collect { |mor| persister.distributed_virtual_switches.lazy_find(mor) }
    end

    def parse_host_system_host_switches(host, switches)
      switches.each do |switch|
        persister.host_switches.build(:host => host, :switch => switch)
      end
    end

    def parse_host_system_lans(object, host, switches, props)
      network = props.fetch_path(:config, :network)
      return if network.blank?

      switch_uids = switches.index_by(&:name)
      datacenter = datacenter_ref_by_host(object)

      network[:portgroup].to_a.each do |portgroup|
        next if portgroup.spec.nil?

        name    = portgroup.spec.name
        ems_ref = network_ref_by_datacenter_and_name.dig(datacenter._ref, name) if datacenter

        lan_hash = {
          :uid_ems => name,
          :name    => name,
          :tag     => portgroup.spec.vlanId.to_s,
          :switch  => switch_uids[portgroup.spec.vswitchName],
          :ems_ref => ems_ref
        }

        security = portgroup.spec.policy&.security
        if security
          lan_hash[:allow_promiscuous] = security.allowPromiscuous
          lan_hash[:forged_transmits]  = security.forgedTransmits
          lan_hash[:mac_changes]       = security.macChanges
        end

        computed_security = portgroup.computedPolicy&.security
        if computed_security
          lan_hash[:computed_allow_promiscuous] = computed_security.allowPromiscuous
          lan_hash[:computed_forged_transmits]  = computed_security.forgedTransmits
          lan_hash[:computed_mac_changes]       = computed_security.macChanges
        end

        persister.host_virtual_lans.build(lan_hash)
      end

      network[:opaqueNetwork].to_a.each do |opaque_network|
        switch_key = cache["HostSystem"][host.ems_ref]&.dig(:config, :network, :opaqueSwitch)&.pluck(:key)&.sort&.first
        next if switch_key.nil?

        extra_config     = Hash[opaque_network.extraConfig.to_a.map { |ec| [ec.key, ec.value] }]
        nsx_network_uuid = extra_config["com.vmware.opaquenetwork.segment.path"]&.split("/")&.last

        name    = opaque_network.opaqueNetworkName
        ems_ref = nsx_network_uuid || name
        uid_ems = opaque_network.opaqueNetworkId
        switch  = persister.host_virtual_switches.lazy_find(:host => host, :uid_ems => switch_key)

        persister.host_virtual_lans.build(
          :switch  => switch,
          :ems_ref => ems_ref,
          :uid_ems => uid_ems,
          :name    => name
        )
      end
    end
  end
end