class VagrantPlugins::ProviderLibvirt::Action::DestroyDomain

Public Class Methods

new(app, _env) click to toggle source
# File lib/vagrant-libvirt/action/destroy_domain.rb, line 14
def initialize(app, _env)
  @logger = Log4r::Logger.new('vagrant_libvirt::action::destroy_domain')
  @app = app
end

Public Instance Methods

call(env) click to toggle source
# File lib/vagrant-libvirt/action/destroy_domain.rb, line 19
def call(env)
  # Destroy the server, remove the tracking ID
  env[:ui].info(I18n.t('vagrant_libvirt.destroy_domain'))

  # Must delete any snapshots before domain can be destroyed
  # Fog Libvirt currently doesn't support snapshots. Use
  # ruby-libvirt client directly. Note this is racy, see
  # http://www.libvirt.org/html/libvirt-libvirt.html#virDomainSnapshotListNames
  libvirt_domain = env[:machine].provider.driver.connection.client.lookup_domain_by_uuid(
    env[:machine].id
  )
  begin
    libvirt_domain.list_snapshots.each do |name|
      @logger.info("Deleting snapshot '#{name}'")
      begin
        libvirt_domain.lookup_snapshot_by_name(name).delete
      rescue => e
        raise Errors::SnapshotDeletionError, error_message: e.message
      end
    end
  rescue
    # Some drivers (xen) don't support getting list of snapshots,
    # not much can be done here about it
    @logger.warn("Failed to get list of snapshots")
  end

  # must remove managed saves
  libvirt_domain.managed_save_remove if libvirt_domain.has_managed_save?

  domain = env[:machine].provider.driver.connection.servers.get(env[:machine].id.to_s)

  undefine_flags = 0
  undefine_flags |= ProviderLibvirt::Util::DomainFlags::VIR_DOMAIN_UNDEFINE_KEEP_NVRAM if env[:machine].provider_config.nvram

  if env[:machine].provider_config.disks.empty? &&
     env[:machine].provider_config.cdroms.empty?
    # if using default configuration of disks and cdroms
    # cdroms are consider volumes, but cannot be destroyed
    destroy_domain(domain, destroy_volumes: true, flags: undefine_flags)
  else
    domain_xml = libvirt_domain.xml_desc(1)
    xml_descr = REXML::Document.new(domain_xml)
    disks_xml = REXML::XPath.match(xml_descr, '/domain/devices/disk[@device="disk"]')
    have_aliases = !(REXML::XPath.match(disks_xml, './alias[@name="ua-box-volume-0"]').first).nil?
    if !have_aliases
      env[:ui].warn(I18n.t('vagrant_libvirt.domain_xml.obsolete_method'))
    end

    destroy_domain(domain, destroy_volumes: false, flags: undefine_flags)

    volumes = domain.volumes

    # Remove root storage. If no aliases available, perform the removal by name and keep track
    # of how many matches there are in the volumes. This will provide a fallback offset to where
    # the additional storage devices are.
    detected_box_volumes = 0
    if have_aliases
      REXML::XPath.match(disks_xml, './alias[contains(@name, "ua-box-volume-")]').each do |box_disk|
        diskname = box_disk.parent.elements['source'].attributes['file'].rpartition('/').last
        detected_box_volumes += 1

        destroy_volume(volumes, diskname, env)
      end
    else
      # fallback to try and infer which boxes are box images, as they are listed first
      # as soon as there is no match, can exit
      disks_xml.each_with_index do |box_disk, idx|
        name = libvirt_domain.name + (idx == 0 ? '.img' : "_#{idx}.img")
        diskname = box_disk.elements['source'].attributes['file'].rpartition('/').last

        break if name != diskname
        detected_box_volumes += 1

        root_disk = volumes.select do |x|
          x.name == name if x
        end.first
        if root_disk
          root_disk.destroy
        end
      end
    end

    # work out if there are any custom disks attached that wasn't done by vagrant-libvirt,
    # and warn there might be unexpected behaviour
    total_disks = disks_xml.length
    offset = total_disks - env[:machine].provider_config.disks.length
    if offset != detected_box_volumes
      env[:ui].warn(I18n.t('vagrant_libvirt.destroy.unexpected_volumes'))
    end

    if !have_aliases
      # if no aliases found, see if it's possible to check the number of box disks
      # otherwise the destroy could remove the wrong disk by accident.
      if env[:machine].box != nil
        box_disks = env[:machine].box.metadata.fetch('disks', [1])
        offset = box_disks.length
        if offset != detected_box_volumes
          env[:ui].warn(I18n.t('vagrant_libvirt.destroy.expected_removal_mismatch'))
        end
      else
        env[:ui].warn(I18n.t('vagrant_libvirt.destroy.box_metadata_unavailable'))
      end

      # offset only used when no aliases available
      offset = detected_box_volumes
    end

    env[:machine].provider_config.disks.each_with_index.each do |disk, index|
      # shared disks remove only manually or ???
      next if disk[:allow_existing]

      # look for exact match using aliases which will be used
      # for subsequent domain creations
      if have_aliases
        domain_disk = REXML::XPath.match(disks_xml, './alias[@name="ua-disk-volume-' + index.to_s + '"]').first
        domain_disk = domain_disk.parent if !domain_disk.nil?
      else
        # otherwise fallback to find the disk by device if specified by user
        # and finally index counting with offset and hope the match is correct
        if !disk[:device].nil?
          domain_disk = REXML::XPath.match(disks_xml, './target[@dev="' + disk[:device] + '"]').first
          domain_disk = domain_disk.parent if !domain_disk.nil?
        else
          domain_disk = disks_xml[offset + index]
        end
      end

      next if domain_disk.nil?

      diskname = domain_disk.elements['source'].attributes['file'].rpartition('/').last
      destroy_volume(volumes, diskname, env)
    end
  end

  @app.call(env)
end

Protected Instance Methods

destroy_domain(domain, destroy_volumes:, flags:) click to toggle source
# File lib/vagrant-libvirt/action/destroy_domain.rb, line 175
def destroy_domain(domain, destroy_volumes:, flags:)
  if domain.method(:destroy).parameters.first.include?(:flags)
    domain.destroy(destroy_volumes: destroy_volumes, flags: flags)
  else
    domain.destroy(destroy_volumes: destroy_volumes)
  end
end
destroy_volume(volumes, diskname, env) click to toggle source
# File lib/vagrant-libvirt/action/destroy_domain.rb, line 158
def destroy_volume(volumes, diskname, env)
  # diskname is unique
  libvirt_disk = volumes.select do |x|
    x.name == diskname if x
  end.first
  if libvirt_disk
    libvirt_disk.destroy
  elsif disk[:path]
    poolname = env[:machine].provider_config.storage_pool_name
    libvirt_disk = volumes.select do |x|
      # FIXME: can remove pool/target.img and pool/123/target.img
      x.path =~ /\/#{disk[:path]}$/ && x.pool_name == poolname
    end.first
    libvirt_disk.destroy if libvirt_disk
  end
end