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 18
def initialize(app, _env)
  @logger = Log4r::Logger.new('vagrant_libvirt::action::handle_box_image')
  @app = app
end

Protected Class Methods

get_box_disk_settings(image_path) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 171
def self.get_box_disk_settings(image_path)
  stdout, stderr, status = Open3.capture3('qemu-img', 'info', '--output=json', image_path)
  if !status.success?
    raise Errors::BadBoxImage, image: image_path, out: stdout, err: stderr
  end

  image_info = JSON.parse(stdout)
  format = image_info['format']
  virtual_size = ByteNumber.new(image_info['virtual-size'])

  return format, virtual_size
end
get_box_image_path(box, box_name) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 153
def self.get_box_image_path(box, box_name)
  return box.directory.join(box_name).to_s
end
get_virtual_size(env) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 146
def self.get_virtual_size(env)
  # Virtual size has to be set for allocating space in storage pool.
  box_virtual_size = env[:machine].box.metadata['virtual_size']
  raise Errors::NoBoxVirtualSizeSet if box_virtual_size.nil?
  return ByteNumber.from_GB(box_virtual_size)
end
get_volume_name(box, name, path, ui) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 129
def self.get_volume_name(box, name, path, ui)
  version = begin
              box.version.to_s
            rescue
              ''
            end

  if version.empty? || version == '0'
    ui.warn(I18n.t('vagrant_libvirt.box_version_missing', name: box.name.to_s))

    version = "0_#{File.mtime(path).to_i}"
  end

  vol_name = box.name.to_s.dup.gsub('/', '-VAGRANTSLASH-')
  vol_name << "_vagrant_box_image_#{version}_#{name.dup.gsub('/', '-SLASH-')}.img"
end
verify_box_format(box_format, disk_index=nil) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 157
def self.verify_box_format(box_format, disk_index=nil)
  if box_format.nil?
    raise Errors::NoBoxFormatSet
  elsif box_format != 'qcow2'
    if disk_index.nil?
      raise Errors::WrongBoxFormatSet
    else
      raise Errors::WrongDiskFormatSet,
        disk_index: disk_index
    end
  end
  return box_format
end

Public Instance Methods

call(env) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 23
def call(env)
  # Handle box formats converting between v1 => v2 and ensuring
  # any obsolete settings are rejected.

  disks = env[:machine].box.metadata.fetch('disks', [])
  if disks.empty?
    # Handle box v1 format

    # Only qcow2 format is supported in v1, but other formats with backing
    # store capability should be usable.
    box_format = env[:machine].box.metadata['format']
    HandleBoxImage.verify_box_format(box_format)

    image_path = HandleBoxImage.get_box_image_path(env[:machine].box, 'box.img')
    env[:box_volume_number] = 1
    env[:box_volumes] = [{
      :path => image_path,
      :name => HandleBoxImage.get_volume_name(env[:machine].box, 'box', image_path, env[:ui]),
      :virtual_size => HandleBoxImage.get_virtual_size(env),
      :format => box_format,
    }]
  else
    # Handle box v2 format
    # {
    #   'path': '<path-of-file-box>',
    #   'name': '<name-to-use-in-storage>' # optional, will use index
    # }
    #
    env[:box_volume_number] = disks.length()
    target_volumes = Hash[]
    env[:box_volumes] = Array.new(env[:box_volume_number]) { |i|
      raise Errors::BoxFormatMissingAttribute, attribute: "disks[#{i}]['path']" if disks[i]['path'].nil?

      image_path = HandleBoxImage.get_box_image_path(env[:machine].box, disks[i]['path'])
      format, virtual_size = HandleBoxImage.get_box_disk_settings(image_path)
      volume_name = HandleBoxImage.get_volume_name(
        env[:machine].box,
        disks[i].fetch('name', disks[i]['path'].sub(/#{File.extname(disks[i]['path'])}$/, '')),
        image_path,
        env[:ui],
      )

      # allowing name means needing to check that it doesn't cause a clash
      existing = target_volumes[volume_name]
      if !existing.nil?
        raise Errors::BoxFormatDuplicateVolume, volume: volume_name, new_disk: "disks[#{i}]", orig_disk: "disks[#{existing}]"
      end
      target_volumes[volume_name] = i

      {
        :path => image_path,
        :name => volume_name,
        :virtual_size => virtual_size,
        :format => HandleBoxImage.verify_box_format(format)
      }
    }
  end

  # Get config options
  config = env[:machine].provider_config
  box_image_files = []
  env[:box_volumes].each do |d|
    box_image_files.push(d[:path])
  end

  # Override box_virtual_size
  box_virtual_size = env[:box_volumes][0][:virtual_size]
  if config.machine_virtual_size
    config_machine_virtual_size = ByteNumber.from_GB(config.machine_virtual_size)
    if config_machine_virtual_size < box_virtual_size
      # Warn that a virtual size less than the box metadata size
      # is not supported and will be ignored
      env[:ui].warn I18n.t(
        'vagrant_libvirt.warnings.ignoring_virtual_size_too_small',
          requested: config_machine_virtual_size.to_GB, minimum: box_virtual_size.to_GB
      )
    else
      env[:ui].info I18n.t('vagrant_libvirt.manual_resize_required')
      box_virtual_size = config_machine_virtual_size
    end
  end
  # save for use by later actions
  env[:box_volumes][0][:virtual_size] = box_virtual_size
  # special handling for domain volume
  env[:box_volumes][0][:device] ||= config.disk_device

  # while inside the synchronize block take care not to call the next
  # action in the chain, as must exit this block first to prevent
  # locking all subsequent actions as well.
  @@lock.synchronize do
    env[:box_volumes].each_index do |i|
      # Don't continue if image already exists in storage pool.
      box_volume = env[:machine].provider.driver.connection.volumes.all(
        name: env[:box_volumes][i][:name]
      ).first
      next if box_volume && box_volume.id

      send_box_image(env, config, box_image_files[i], env[:box_volumes][i])
    end
  end

  @app.call(env)
end

Protected Instance Methods

send_box_image(env, config, box_image_file, box_volume) click to toggle source
# File lib/vagrant-libvirt/action/handle_box_image.rb, line 184
def send_box_image(env, config, box_image_file, box_volume)
  # 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
  unless File.exist?(box_image_file)
    raise Vagrant::Errors::BoxNotFound, name: env[:machine].box.name
  end
  box_image_size = File.size(box_image_file) # B
  message = "Creating volume #{box_volume[:name]} in storage pool #{config.storage_pool_name}."
  @logger.info(message)
  begin
    fog_volume = env[:machine].provider.driver.connection.volumes.create(
      name: box_volume[:name],
      allocation: "#{box_image_size / 1024 / 1024}M",
      capacity: "#{box_volume[:virtual_size].to_B}B",
      format_type: box_volume[:format],
      owner: storage_uid(env),
      group: storage_gid(env),
      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,
                    box_volume[:name], env) do |progress|
    rewriting(env[:ui]) do |ui|
      ui.clear_line
      ui.report_progress(progress, box_image_size, false)
    end
  end

  # Clear the line one last time since the progress meter doesn't
  # disappear immediately.
  rewriting(env[:ui]) {|ui| 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
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 237
def upload_image(image_file, pool_name, volume_name, env)
  image_size = File.size(image_file) # B

  begin
    pool = env[:machine].provider.driver.connection.client.lookup_storage_pool_by_name(
      pool_name
    )
    volume = pool.lookup_volume_by_name(volume_name)
    stream = env[:machine].provider.driver.connection.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))
        sent = stream.send buff
        progress += sent
        yield progress
      end
    end
  rescue => e
    raise Errors::ImageUploadError,
          error_message: e.message
  end

  progress == image_size
end