ManageIQ/manageiq-providers-vmware

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

Summary

Maintainability
A
0 mins
Test Coverage
A
90%
require 'VMwareWebService/VimTypes'

class ManageIQ::Providers::Vmware::InfraManager::Inventory::Parser
  include Vmdb::Logging
  include ComputeResource
  include Datastore
  include DistributedVirtualSwitch
  include HostSystem
  include ResourcePool
  include VirtualMachine

  attr_reader :cache, :collector, :persister
  private     :cache, :collector, :persister

  def initialize(collector, persister)
    @cache     = collector.cache
    @collector = collector
    @persister = persister
  end

  def parse(object, kind, props)
    object_type = object.class.wsdl_name
    parse_method = "parse_#{object_type.underscore}"

    raise "Missing parser for #{object_type}" unless respond_to?(parse_method)

    send(parse_method, object, kind, props)
  end

  def parse_ext_management_system(object, about)
    api_version   = about.apiVersion
    instance_uuid = about.instanceUuid

    persister.ext_management_system.build(
      :guid                => object.guid,
      :api_version         => api_version,
      :uid_ems             => instance_uuid,
      :last_inventory_date => Time.now.utc
    )
  end

  def parse_compute_resource(object, kind, props)
    persister.clusters.targeted_scope << object._ref
    return if kind == "leave"

    # If a host isn't in a cluster VMware still puts it in a ComputeResource
    # parent object but this isn't shown on their UI
    hidden = object.class.wsdl_name == "ComputeResource"

    cluster_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :uid_ems      => object._ref,
      :name         => CGI.unescape(props[:name]),
      :parent       => lazy_find_managed_object(props[:parent]),
      :hidden       => hidden
    }

    parse_compute_resource_summary(cluster_hash, props)
    parse_compute_resource_das_config(cluster_hash, props)
    parse_compute_resource_drs_config(cluster_hash, props)

    persister.clusters.build(cluster_hash)
  end
  alias parse_cluster_compute_resource parse_compute_resource

  def parse_customization_spec_manager(_object, kind, props)
    return if kind == "leave"

    props[:info].to_a.each do |spec_info|
      persister.customization_specs.build(
        :name             => spec_info[:name],
        :typ              => spec_info[:type],
        :description      => spec_info[:description],
        :last_update_time => spec_info[:last_update_time].to_s,
        :spec             => rbvmomi_to_basic_types(spec_info[:spec]).deep_stringify_keys
      )
    end
  end

  def parse_datacenter(object, kind, props)
    persister.ems_folders.targeted_scope << object._ref
    return if kind == "leave"

    dc_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :uid_ems      => object._ref,
      :type         => "ManageIQ::Providers::Vmware::InfraManager::Datacenter",
      :name         => CGI.unescape(props[:name]),
      :parent       => lazy_find_managed_object(props[:parent]),
    }

    persister.ems_folders.build(dc_hash)
  end

  def parse_datastore(object, kind, props)
    persister.storages.targeted_scope << object._ref
    return if kind == "leave"

    storage_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :parent       => lazy_find_managed_object(props[:parent])
    }

    parse_datastore_summary(storage_hash, props)
    parse_datastore_capability(storage_hash, props)

    storage = persister.storages.build(storage_hash)

    parse_datastore_host_mount(storage, object._ref, props)
  end

  def parse_distributed_virtual_switch(object, kind, props)
    persister.distributed_virtual_switches.targeted_scope << object._ref
    return if kind == "leave"

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

    switch_hash = {
      :uid_ems => object._ref,
      :type    => type,
      :shared  => true,
    }

    parse_dvs_config(switch_hash, props[:config])
    parse_dvs_summary(switch_hash, props[:summary])

    persister.distributed_virtual_switches.build(switch_hash)

    # Since Lans aren't a top-level collection but belong_to a switch we have
    # to send all dvportgroups for a dvswitch when doing a targeted refresh of the switch
    dvpgs = cache["DistributedVirtualPortgroup"].select do |_mor, dvpg_props|
      dvpg_props.fetch_path(:config, :distributedVirtualSwitch)&._ref == object._ref
    end

    dvpgs.each do |mor, dvpg_props|
      portgroup = RbVmomi::VIM::DistributedVirtualPortgroup(object._connection, mor)
      parse_portgroups_internal(portgroup, dvpg_props)
    end
  end
  alias parse_vmware_distributed_virtual_switch parse_distributed_virtual_switch

  def parse_extension_manager(_object, kind, props)
    return if kind == "leave"

    props[:extensionList].each do |extension|
      persister.ems_extensions.build(
        :ems_ref => extension.key,
        :key     => extension.key,
        :company => extension.company,
        :label   => extension.description.label,
        :summary => extension.description.summary,
        :version => extension.version
      )
    end
  end

  def parse_folder(object, kind, props)
    persister.ems_folders.targeted_scope << object._ref
    return if kind == "leave"

    # "Hidden" folders are folders which exist in the VIM API but are not shown
    # on the vSphere UI.  These folders are the root folder above the datacenters
    # named "Datacenters", and the 4 child folders of each datacenter (datastore,
    # host, network, vm)
    hidden = props[:parent].nil? || props[:parent].kind_of?(RbVmomi::VIM::Datacenter)

    folder_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :type         => "ManageIQ::Providers::Vmware::InfraManager::Folder",
      :uid_ems      => object._ref,
      :name         => CGI.unescape(props[:name]),
      :parent       => lazy_find_managed_object(props[:parent]),
      :hidden       => hidden,
    }

    persister.ems_folders.build(folder_hash)
  end

  def parse_host_system(object, kind, props)
    persister.hosts.targeted_scope << object._ref
    return if kind == "leave"

    invalid, err = validate_host_system_props(object, props)
    if invalid
      _log.warn("#{err} Skipping.")

      return
    end

    cluster = lazy_find_managed_object(props[:parent])
    host_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :ems_cluster  => cluster,
      :type         => "ManageIQ::Providers::Vmware::InfraManager::HostEsx",
    }

    parse_host_system_summary(host_hash, props)
    parse_host_system_config(host_hash, props)
    parse_host_system_product(host_hash, props)
    parse_host_system_network(host_hash, props)
    parse_host_system_runtime(host_hash, props)
    parse_host_system_system_info(host_hash, props)

    host_hash[:type] = if host_hash.include?(:vmm_product) && !%w[esx esxi].include?(host_hash[:vmm_product].to_s.downcase)
                         "ManageIQ::Providers::Vmware::InfraManager::Host"
                       else
                         "ManageIQ::Providers::Vmware::InfraManager::HostEsx"
                       end

    host_hash[:name] ||= props[:name]

    host = persister.hosts.build(host_hash)

    parse_host_system_operating_system(host, props)
    parse_host_system_system_services(host, props)
    hardware = parse_host_system_hardware(host, props)
    parse_host_system_host_networks(host, hardware, props)

    switches     = parse_host_system_switches(host, props)
    dvs_switches = parse_host_system_distributed_switches(host)

    parse_host_system_host_switches(host, switches + dvs_switches)
    parse_host_system_lans(object, host, switches, props)
  end

  def parse_license_manager(_object, kind, props)
    return if kind == "leave"

    require "digest"
    props[:licenses].to_a.each do |license|
      uid = Digest::SHA1.hexdigest(license.licenseKey)

      persister.ems_licenses.build(
        :ems_ref         => uid,
        :name            => license.name,
        :license_edition => license.editionKey,
        :total_licenses  => license.total,
        :used_licenses   => license.used
      )
    end
  end

  def parse_network(object, kind, props)
  end
  alias parse_opaque_network parse_network

  def parse_distributed_virtual_portgroup(object, kind, props)
    dvs = props.fetch_path(:config, :distributedVirtualSwitch)

    # If the dvportgroup is deleted we don't want to pass kind="leave" to the
    # switch parser because it will think the switch is being deleted
    kind = "update" if kind == "leave"

    dvs_props = cache.find(dvs)
    if dvs_props.nil?
      _log.warn("#{object.class.wsdl_name}:#{object._ref} DVSwitch [#{dvs._ref}] not found in cache...Skipping")
    else
      parse_distributed_virtual_switch(dvs, kind, dvs_props)
    end
  end

  def parse_portgroups_internal(object, props)
    return if Array.wrap(props[:tag]).detect { |tag| tag.key == "SYSTEM/DVS.UPLINKPG" }

    ref  = object._ref
    uid  = props.fetch_path(:config, :key)
    name = props.fetch_path(:summary, :name) || props.fetch_path(:config, :name)
    name = CGI.unescape(name) unless name.nil?

    default_port_config = props.fetch_path(:config, :defaultPortConfig)
    security_policy = default_port_config&.securityPolicy
    vlan_spec = default_port_config&.vlan
    tag = vlan_spec.vlanId if vlan_spec.kind_of?(RbVmomi::VIM::VmwareDistributedVirtualSwitchVlanIdSpec)

    if security_policy
      allow_promiscuous = security_policy.allowPromiscuous&.value
      forged_transmits  = security_policy.forgedTransmits&.value
      mac_changes       = security_policy.macChanges&.value
    end

    dvs    = props.fetch_path(:config, :distributedVirtualSwitch)
    switch = persister.distributed_virtual_switches.lazy_find(dvs._ref) unless dvs.nil?

    lan_hash = {
      :ems_ref           => ref,
      :uid_ems           => uid,
      :name              => name,
      :switch            => switch,
      :allow_promiscuous => allow_promiscuous,
      :forged_transmits  => forged_transmits,
      :mac_changes       => mac_changes,
      :tag               => tag
    }

    persister.distributed_virtual_lans.find_or_build_by(lan_hash)
  end

  def parse_resource_pool(object, kind, props)
    persister.resource_pools.targeted_scope << object._ref
    return if kind == "leave"

    parent = props[:parent]

    # Default resource pools are ones whose parent is a Cluster or a Host,
    # resource pools which show up on the vSphere UI all have resource pools
    # as parents.
    is_default = parent && !parent.kind_of?(RbVmomi::VIM::ResourcePool)
    name       = if is_default
                   cached_parent = cache.find(parent) if parent
                   parent_model = persister.vim_class_to_collection(parent).base_class_name

                   _("Default for %{parent_type} %{parent_name}") % {:parent_type => Dictionary.gettext(parent_model, :type => :model, :notfound => :titleize),
                                                                     :parent_name => cached_parent[:name]}
                 else
                   CGI.unescape(props[:name])
                 end

    rp_hash = {
      :ems_ref      => object._ref,
      :ems_ref_type => object.class.wsdl_name,
      :uid_ems      => object._ref,
      :name         => name,
      :vapp         => object.kind_of?(RbVmomi::VIM::VirtualApp),
      :parent       => lazy_find_managed_object(parent),
      :is_default   => is_default,
    }

    parse_resource_pool_memory_allocation(rp_hash, props)
    parse_resource_pool_cpu_allocation(rp_hash, props)

    persister.resource_pools.build(rp_hash)
  end
  alias parse_virtual_app parse_resource_pool

  def parse_pbm_profile(object, _kind, props)
    persister.storage_profiles.build(
      :ems_ref      => object.profileId.uniqueId,
      :name         => props[:name],
      :profile_type => props[:profileCategory]
    )
  end
  alias parse_pbm_capability_profile         parse_pbm_profile
  alias parse_pbm_default_capability_profile parse_pbm_profile

  def parse_pbm_placement_hub(persister_storage_profile, _object, _kind, props)
    persister_storage = persister.storages.lazy_find(props[:hubId])
    persister.storage_profile_storages.build(
      :storage_profile => persister_storage_profile,
      :storage         => persister_storage
    )
  end

  def parse_storage_pod(object, kind, props)
    persister.ems_folders.targeted_scope << object._ref
    return if kind == "leave"

    name = CGI.unescape(props[:name]) if props[:name]

    pod_hash = {
      :ems_ref => object._ref,
      :uid_ems => object._ref,
      :type    => "StorageCluster",
      :name    => name,
      :parent  => lazy_find_managed_object(props[:parent]),
    }

    persister.ems_folders.build(pod_hash)
  end

  def parse_virtual_machine(object, kind, props)
    persister.vms_and_templates.targeted_scope << object._ref
    return if kind == "leave"

    invalid, err = validate_virtual_machine_props(object, props)
    if invalid
      _log.warn("#{err} Skipping.")

      return
    end

    name = CGI.unescape(props[:name]) if props[:name]

    vm_hash = {
      :ems_ref       => object._ref,
      :ems_ref_type  => object.class.wsdl_name,
      :vendor        => "vmware",
      :name          => name,
      :parent        => lazy_find_managed_object(props[:parent]),
      :resource_pool => lazy_find_managed_object(props[:resourcePool]),
    }

    parse_virtual_machine_config(vm_hash, props)
    parse_virtual_machine_datastore(vm_hash, props)
    parse_virtual_machine_resource_config(vm_hash, props)
    parse_virtual_machine_summary(vm_hash, props)
    parse_virtual_machine_storage(vm_hash, props)

    vm = persister.vms_and_templates.build(vm_hash)

    parse_virtual_machine_operating_system(vm, props)
    parse_virtual_machine_hardware(vm, props)
    parse_virtual_machine_custom_attributes(vm, props)
    parse_virtual_machine_labels(vm, props)
    parse_virtual_machine_snapshots(vm, props)
  end

  def parse_content_library_item(library_item)
    return unless library_item.type == 'ovf'

    props = {
      :ems_ref     => library_item.id,
      :name        => library_item.name,
      :description => library_item.description,
      :content     => library_item.type # TODO: currently 'ovf|iso|file'
    }
    persister.orchestration_templates.build(props)
  end

  def lazy_find_managed_object(managed_object)
    return if managed_object.nil?

    parent_collection = persister.vim_class_to_collection(managed_object)
    parent_collection.lazy_find(managed_object._ref)
  end

  def find_parent_datacenter(obj)
    find_parent_of_type(obj, "Datacenter")
  end

  def find_parent_of_type(obj, wsdl_name)
    parent = obj
    parent = cache.find(parent)&.dig(:parent) until parent.nil? || parent.class.wsdl_name == wsdl_name
    parent
  end

  def rbvmomi_to_basic_types(obj)
    case obj
    when RbVmomi::BasicTypes::DataObject
      obj.props.transform_values { |v| rbvmomi_to_basic_types(v) }
    when Array
      obj.map { |v| rbvmomi_to_basic_types(v) }
    else
      obj
    end
  end
end