ManageIQ/manageiq

View on GitHub
app/models/pxe_server.rb

Summary

Maintainability
A
3 hrs
Test Coverage
D
66%
class PxeServer < ApplicationRecord
  autoload :WimParser, "wim_parser"

  include FileDepotMixin

  alias_attribute :description, :name

  default_value_for :customization_directory, ""

  serialize :visibility

  acts_as_miq_taggable

  validates :uri, :presence => true
  validates :name, :presence => true, :uniqueness_when_changed => true

  has_many :pxe_menus,      :dependent => :destroy
  has_many :pxe_images,     :dependent => :destroy
  has_many :advertised_pxe_images, -> { where.not(pxe_menu_id: nil) }, :class_name => "PxeImage"
  has_many :discovered_pxe_images, -> { where(:pxe_menu_id => nil) }, :class_name => "PxeImage"
  has_many :windows_images, :dependent => :destroy

  def images
    pxe_images + windows_images
  end

  def advertised_images
    advertised_pxe_images + windows_images
  end

  def discovered_images
    discovered_pxe_images + windows_images
  end

  def default_pxe_image_for_windows=(image)
    image.update(:default_for_windows => true)
  end

  def default_pxe_image_for_windows
    pxe_images.find_by(:default_for_windows => true)
  end

  def synchronize_advertised_images
    pxe_menus.each(&:synchronize)
    sync_windows_images

    advertised_pxe_images.reload
    discovered_pxe_images.reload
    pxe_images.reload
    pxe_menus.reload
    windows_images.reload

    update_attribute(:last_refresh_on, Time.now.utc)
  end

  def synchronize_advertised_images_queue
    MiqQueue.put_unless_exists(
      :class_name  => self.class.name,
      :instance_id => id,
      :method_name => "synchronize_advertised_images"
    )
  end

  def sync_images
    sync_pxe_images
    sync_windows_images
    pxe_images.reload
    windows_images.reload
    update_attribute(:last_refresh_on, Time.now.utc)
  end

  def sync_images_queue
    MiqQueue.put_unless_exists(
      :class_name  => self.class.name,
      :instance_id => id,
      :method_name => "sync_images"
    )
  end

  def sync_pxe_images
    _log.info("Synchronizing PXE images on PXE Server [#{name}]...")

    stats = {:adds => 0, :updates => 0, :deletes => 0}
    current = pxe_images.where(:pxe_menu_id => nil).index_by { |i| [i.path, i.name] }

    with_depot do
      begin
        file_glob("#{pxe_directory}/*").each do |f|
          next unless file_file?(f)

          relative_path = Pathname.new(f).relative_path_from(Pathname.new(pxe_directory)).to_s

          contents    = file_read(f)
          menu_class  = PxeMenu.class_from_contents(contents)
          image_class = menu_class.corresponding_image
          image_list  = image_class.parse_contents(contents, File.basename(f))

          # Deal with multiple images with the same label in a file
          incoming = image_list.group_by { |h| h[:label] }
          incoming.each_key do |name|
            array = incoming[name]
            _log.warn("duplicate name <#{name}> in file <#{relative_path}> on PXE Server <#{self.name}>") if array.length > 1
            incoming[name] = array.first
          end

          incoming.each do |_name, ihash|
            image = current.delete([relative_path, ihash[:label]])
            if image.nil?
              image = image_class.new
              pxe_images << image
            end
            stats[image.new_record? ? :adds : :updates] += 1

            image.path = relative_path
            image.parsed_contents = ihash
            image.save!
          end
        end
      rescue => err
        _log.error("Synchronizing PXE images on PXE Server [#{self.name}]: #{err.class.name}: #{err}")
        _log.log_backtrace(err)
      end
    end

    stats[:deletes] = current.length
    pxe_images.delete(current.values) unless current.empty?

    _log.info("Synchronizing PXE images on PXE Server [#{self.name}]... Complete - #{stats.inspect}")
  end

  def sync_windows_images
    return if windows_images_directory.nil?

    _log.info("Synchronizing Windows images on PXE Server [#{name}]...")

    stats = {:adds => 0, :updates => 0, :deletes => 0}
    current = windows_images.index_by { |i| [i.path, i.index] }

    with_depot do
      begin
        file_glob("#{windows_images_directory}/*.wim").each do |f|
          next unless file_file?(f)

          path = Pathname.new(f).relative_path_from(Pathname.new(windows_images_directory)).to_s

          wim_parser = WimParser.new(File.join(depot_root, f))
          wim_parser.xml_data["images"].each do |image_hash|
            index   = image_hash["index"]

            image = current.delete([path, index]) || windows_images.build
            stats[image.new_record? ? :adds : :updates] += 1

            image.update(
              :name        => image_hash["name"],
              :description => image_hash["description"].presence,
              :path        => path,
              :index       => index
            )
          end
        end
      rescue => err
        _log.error("Synchronizing Windows images on PXE Server [#{name}]: #{err.class.name}: #{err}")
        _log.log_backtrace(err)
      end
    end

    stats[:deletes] = current.length
    windows_images.delete(current.values) unless current.empty?

    _log.info("Synchronizing Windows images on PXE Server [#{name}]...Complete - #{stats.inspect}")
  end

  def read_file(filename)
    with_depot { file_read(filename) }
  end

  def write_file(filename, contents)
    with_depot { file_write(filename, contents) }
  end

  def delete_file(filename)
    with_depot { file_delete(filename) }
  end

  def delete_directory(directory)
    with_depot { directory_delete(directory) }
  end

  def create_provisioning_files(pxe_image, mac_address, windows_image = nil, customization_template = nil, substitution_options = nil)
    log_message = "Creating provisioning files for PXE Image [#{pxe_image.description}], Customization Template [#{customization_template.try(:description)}], with MAC Address [#{mac_address}]"
    _log.info("#{log_message}...")
    with_depot do
      pxe_image.create_files_on_server(self, mac_address, customization_template)
      customization_template.create_files_on_server(self, pxe_image, mac_address, windows_image, substitution_options) unless customization_template.nil?
    end
    _log.info("#{log_message}...Complete")
  end

  def delete_provisioning_files(pxe_image, mac_address, windows_image = nil, customization_template = nil)
    log_message = "Deleting provisioning files for PXE Image [#{pxe_image.description}], Customization Template [#{customization_template.try(:description)}], with MAC Address [#{mac_address}]"
    _log.info("#{log_message}...")
    with_depot do
      pxe_image.delete_files_on_server(self, mac_address)
      customization_template.delete_files_on_server(self, pxe_image, mac_address, windows_image) unless customization_template.nil?
    end
    _log.info("#{log_message}...Complete")
  end

  def ensure_menu_list(menu_list)
    current_menus = pxe_menus.map(&:file_name)
    to_destroy = current_menus - menu_list
    to_create = menu_list - current_menus
    transaction do
      pxe_menus.where(:file_name => to_destroy).destroy_all
      to_create.each { |menu| pxe_menus << PxeMenu.create!(:file_name => menu) }
    end
  end

  def self.display_name(number = 1)
    n_('PXE Server', 'PXE Servers', number)
  end
end