class VagrantPlugins::ProviderLibvirt::Action::HandleBoxImage

Public Class Methods

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

Public Instance Methods

call(env) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 15
def call(env)

  # Verify box metadata for mandatory values.
  #
  # Virtual size has to be set for allocating space in storage pool.
  box_virtual_size = env[:machine].box.metadata['virtual_size']
  if box_virtual_size == nil
    raise Errors::NoBoxVirtualSizeSet
  end

  # Support qcow2 format only for now, but other formats with backing
  # store capability should be usable.
  box_format = env[:machine].box.metadata['format']
  if box_format == nil
    raise Errors::NoBoxFormatSet
  elsif box_format != 'qcow2'
    raise Errors::WrongBoxFormatSet
  end

  # Get config options
  config   = env[:machine].provider_config
  box_image_file = env[:machine].box.directory.join('box.img').to_s
  env[:box_volume_name] = env[:machine].box.name.to_s.dup.gsub("/", "-VAGRANTSLASH-")
  env[:box_volume_name] << "_vagrant_box_image_#{env[:machine].box.version.to_s rescue ''}.img"

  @@lock.synchronize do
    # Don't continue if image already exists in storage pool.
    return @app.call(env) if ProviderLibvirt::Util::Collection.find_matching(
      env[:libvirt_compute].volumes.all, env[:box_volume_name])

    # Box is not available as a storage pool volume. Create and upload
    # it as a copy of local box image.
    env[:ui].info(I18n.t('vagrant_libvirt.uploading_volume'))

    # Create new volume in storage pool
    raise Errors::BoxNotFound if !File.exists?(box_image_file)
    box_image_size = File.size(box_image_file) # B
    message = "Creating volume #{env[:box_volume_name]}"
    message << " in storage pool #{config.storage_pool_name}."
    @logger.info(message)
    begin
      fog_volume = env[:libvirt_compute].volumes.create(
        name:         env[:box_volume_name],
        allocation:   "#{box_image_size/1024/1024}M",
        capacity:     "#{box_virtual_size}G",
        format_type:  box_format,
        pool_name:    config.storage_pool_name)
    rescue Fog::Errors::Error => e
      raise Errors::FogCreateVolumeError,
        :error_message => e.message
    end

    # Upload box image to storage pool
    ret = upload_image(box_image_file, config.storage_pool_name,
      env[:box_volume_name], env) do |progress|
        env[:ui].clear_line
        env[:ui].report_progress(progress, box_image_size, false)
    end

    # Clear the line one last time since the progress meter doesn't
    # disappear immediately.
    env[:ui].clear_line

    # If upload failed or was interrupted, remove created volume from
    # storage pool.
    if env[:interrupted] || !ret
      begin
        fog_volume.destroy
      rescue
        nil
      end
    end
  end

  @app.call(env)
end

Protected Instance Methods

upload_image(image_file, pool_name, volume_name, env) { |progress| ... } click to toggle source

Fog libvirt currently doesn't support uploading images to storage pool volumes. Use ruby-libvirt client instead.

# File lib/vagrant-libvirt/action/handle_box_image.rb, line 96
def upload_image(image_file, pool_name, volume_name, env)
  image_size = File.size(image_file) # B

  begin
    pool = env[:libvirt_compute].client.lookup_storage_pool_by_name(
      pool_name)
    volume = pool.lookup_volume_by_name(volume_name)
    stream = env[:libvirt_compute].client.stream
    volume.upload(stream, offset=0, length=image_size)

    # Exception ProviderLibvirt::RetrieveError can be raised if buffer is
    # longer than length accepted by API send function.
    #
    # TODO: How to find out if buffer is too large and what is the
    # length that send function will accept?

    buf_size = 1024*250 # 250K
    progress = 0
    open(image_file, 'rb') do |io|
      while (buff = io.read(buf_size)) do
        sent = stream.send buff
        progress += sent
        yield progress
      end
    end
  rescue => e
    raise Errors::ImageUploadError,
      :error_message => e.message
  end

  return progress == image_size
end