ManageIQ/manageiq-providers-kubevirt

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

Summary

Maintainability
A
0 mins
Test Coverage
A
96%
#
# This is the base class for all the rest of the parsers. It contains the methods that will be
# shared by the full and targeted refresh parsers.
#
class ManageIQ::Providers::Kubevirt::Inventory::Parser < ManageIQ::Providers::Inventory::Parser
  include Vmdb::Logging

  protected

  #
  # The identifier of the built-in cluster:
  #
  CLUSTER_ID = '0'.freeze

  #
  # The identifier of the built-in storage:
  #
  STORAGE_ID = '0'.freeze

  attr_reader :cluster_collection
  attr_reader :host_collection
  attr_reader :host_storage_collection
  attr_reader :hw_collection
  attr_reader :network_collection
  attr_reader :os_collection
  attr_reader :storage_collection
  attr_reader :template_collection
  attr_reader :vm_collection
  attr_reader :vm_os_collection
  attr_reader :disk_collection

  def add_builtin_clusters
    cluster_object = cluster_collection.find_or_build(CLUSTER_ID)
    cluster_object.ems_ref = CLUSTER_ID
    cluster_object.name = collector.manager.name
    cluster_object.uid_ems = CLUSTER_ID
  end

  def add_builtin_storages
    storage_object = storage_collection.find_or_build(STORAGE_ID)
    storage_object.ems_ref = STORAGE_ID
    storage_object.name = collector.manager.name
    storage_object.store_type = 'UNKNOWN'
    storage_object.total_space = 0
    storage_object.free_space = 0
    storage_object.uncommitted = 0
  end

  def process_nodes(objects)
    objects.each do |object|
      process_node(object)
    end
  end

  def process_node(object)
    # Get the basic information:
    uid = object.uid
    name = object.name

    # Add the inventory object for the host:
    host_object = host_collection.find_or_build(uid)
    host_object.connection_state = 'connected'
    host_object.ems_cluster = cluster_collection.lazy_find(CLUSTER_ID)
    host_object.ems_ref = uid
    host_object.hostname = object.hostname
    host_object.ipaddress = object.ip_address
    host_object.name = name
    host_object.uid_ems = uid

    # Add the inventory object for the operating system details:
    os_object = os_collection.find_or_build(host_object)
    os_object.name = object.hostname
    os_object.product_name = object.os_image
    os_object.product_type = object.operating_system
    os_object.version = object.kernel_version

    # Find the storage:
    storage_object = storage_collection.lazy_find(STORAGE_ID)

    # Add the inventory object for the host storage:
    host_storage_collection.find_or_build_by(
      :host    => host_object,
      :storage => storage_object,
    )
  end

  def process_vms(objects)
    objects.each do |object|
      process_vm(object)
    end
  end

  def process_vm(object)
    # Process the domain:
    vm_object = process_domain(object.namespace, object.memory, object.cpu_cores, object.uid, object.name)

    # Add the inventory object for the OperatingSystem
    process_os(vm_object, object.labels, object.annotations)

    # The power status is initially off, it will be set to on later if the virtual machine instance exists:
    vm_object.raw_power_state = 'Succeeded'
  end

  def process_vm_instances(objects)
    objects.each do |object|
      process_vm_instance(object)
    end
  end

  def process_vm_instance(object)
    # Get the basic information:
    uid = object.uid
    name = object.name

    # Get the identifier of the virtual machine from the owner reference:
    unless object.owner_name.nil?
      # seems like valid use case for now
      uid = object.owner_uid
      name = object.owner_name
    end

    # Process the domain:
    vm_object = process_domain(object.namespace, object.memory, object.cpu_cores, uid, name)
    process_status(vm_object, object.ip_address, object.node_name)

    vm_object.raw_power_state = object.status
  end

  def process_domain(namespace, memory, cores, uid, name)
    # Find the storage:
    storage_object = storage_collection.lazy_find(STORAGE_ID)
    # Create the inventory object for the virtual machine:
    vm_object = vm_collection.find_or_build(uid)
    vm_object.connection_state = 'connected'
    vm_object.ems_ref = uid
    vm_object.name = name
    vm_object.storage = storage_object
    vm_object.storages = [storage_object]
    vm_object.template = false
    vm_object.uid_ems = uid
    vm_object.location = namespace

    # Create the inventory object for the hardware:
    hw_object = hw_collection.find_or_build(vm_object)
    hw_object.memory_mb = parse_quantity(memory) / 1.megabytes.to_f if memory
    hw_object.cpu_cores_per_socket = cores
    hw_object.cpu_total_cores = cores

    # Return the created inventory object:
    vm_object
  end

  def parse_quantity(value)
    return nil if value.nil?

    begin
      value.iec_60027_2_to_i
    rescue
      value.decimal_si_to_f
    end
  end

  def process_status(vm_object, ip_address, node_name)
    hw_object = hw_collection.find_or_build(vm_object)

    # Create the inventory object for vm network device
    hardware_networks(hw_object, ip_address, node_name)
  end

  def hardware_networks(hw_object, ip_address, node_name)
    return nil unless ip_address

    network_collection.find_or_build_by(
      :hardware  => hw_object,
      :ipaddress => ip_address,
    ).assign_attributes(
      :ipaddress => ip_address,
      :hostname  => node_name
    )
  end

  def process_templates(objects)
    objects.each do |object|
      process_template(object)
    end
  end

  def process_template(object)
    # Get the basic information:
    uid = object.uid
    vm  = vm_from_objects(object.objects)
    return if vm.nil?

    # Add the inventory object for the template:
    template_object = template_collection.find_or_build(uid)
    template_object.connection_state = 'connected'
    template_object.ems_ref = uid
    template_object.name = object.name
    template_object.raw_power_state = 'never'
    template_object.template = true
    template_object.uid_ems = uid
    template_object.location = object.namespace

    # Add the inventory object for the hardware:
    process_hardware(template_object, object.parameters, object.labels, vm.dig(:spec, :template, :spec, :domain))

    # Add the inventory object for the OperatingSystem
    process_os(template_object, object.labels, object.annotations)
  end

  def vm_from_objects(objects)
    vm = nil
    objects.each do |object|
      if object[:kind] == "VirtualMachine"
        vm = object
      end
    end
    vm
  end

  def process_hardware(template_object, params, labels, domain)
    require 'fog/kubevirt'
    require 'fog/kubevirt/compute/models/template'
    hw_object = hw_collection.find_or_build(template_object)
    memory = default_value(params, 'MEMORY') || domain.dig(:memory, :guest)
    hw_object.memory_mb = parse_quantity(memory) / 1.megabytes.to_f if memory
    cpu = default_value(params, 'CPU_CORES') || domain.dig(:cpu, :cores)
    hw_object.cpu_cores_per_socket = cpu
    hw_object.cpu_total_cores = cpu
    hw_object.guest_os = labels&.dig(Fog::Kubevirt::Compute::Template::OS_LABEL_SYMBOL)

    # Add the inventory objects for the disk:
    process_disks(hw_object, domain)
  end

  def default_value(params, name)
    name_param = params.detect { |param| param[:name] == name }
    name_param[:value] if name_param
  end

  def process_disks(hw_object, domain)
    domain.dig(:devices, :disks).each do |disk|
      disk_object = disk_collection.find_or_build_by(
        :hardware    => hw_object,
        :device_name => disk[:name]
      )
      disk_object.device_name = disk[:name]
      disk_object.location = disk[:volumeName]
      disk_object.device_type = 'disk'
      disk_object.present = true
      disk_object.mode = 'persistent'
      # TODO: what do we need more? We are missing reference to PV or PVC
    end
  end

  def process_os(template_object, labels, annotations)
    require 'fog/kubevirt'
    require 'fog/kubevirt/compute/models/template'
    os_object = vm_os_collection.find_or_build(template_object)
    os_object.product_name = labels&.dig(Fog::Kubevirt::Compute::Template::OS_LABEL_SYMBOL)
    tags = annotations&.dig(:tags) || []
    os_object.product_type = if tags.include?("linux")
                               "linux"
                             elsif tags.include?("windows")
                               "windows"
                             else
                               "other"
                             end
  end
end