# Helper class for manipulating VM storage *volumes*, i.e. it deals
# only with creation of images and keeps a name => volume path lookup
# table (plugging drives or getting info of plugged devices is done in
# the VM class). We'd like better coupling, but given the ridiculous
# disconnect between Libvirt::StoragePool and Libvirt::Domain (hint:
# they have nothing with each other to do whatsoever) it's what makes
# sense.

require 'libvirt'
require 'rexml/document'
require 'etc'

class VMStorage

  @@virt = nil

  def initialize(virt, xml_path)
    @@virt ||= virt
    @xml_path = xml_path
    pool_xml = REXML::Document.new(File.read("#{@xml_path}/storage_pool.xml"))
    pool_name = pool_xml.elements['pool/name'].text
    begin
      @pool = @@virt.lookup_storage_pool_by_name(pool_name)
    rescue Libvirt::RetrieveError
      # There's no pool with that name, so we don't have to clear it
    else
      VMStorage.clear_storage_pool(@pool)
    end
    @pool_path = "#{$tmp_dir}/#{pool_name}"
    pool_xml.elements['pool/target/path'].text = @pool_path
    @pool = @@virt.define_storage_pool_xml(pool_xml.to_s)
    @pool.build
    @pool.create
    @pool.refresh
  end

  def VMStorage.clear_storage_pool_volumes(pool)
    was_not_active = !pool.active?
    if was_not_active
      pool.create
    end
    pool.list_volumes.each do |vol_name|
      vol = pool.lookup_volume_by_name(vol_name)
      vol.delete
    end
    if was_not_active
      pool.destroy
    end
  rescue
    # Some of the above operations can fail if the pool's path was
    # deleted by external means; let's ignore that.
  end

  def VMStorage.clear_storage_pool(pool)
    VMStorage.clear_storage_pool_volumes(pool)
    pool.destroy if pool.active?
    pool.undefine
  end

  def clear_pool
    VMStorage.clear_storage_pool(@pool)
  end

  def clear_volumes
    VMStorage.clear_storage_pool_volumes(@pool)
  end

  def create_new_disk(name, options = {})
    options[:size] ||= 2
    options[:unit] ||= "GiB"
    options[:type] ||= "qcow2"
    begin
      old_vol = @pool.lookup_volume_by_name(name)
    rescue Libvirt::RetrieveError
      # noop
    else
      old_vol.delete
    end
    uid = Etc::getpwnam("libvirt-qemu").uid
    gid = Etc::getgrnam("libvirt-qemu").gid
    vol_xml = REXML::Document.new(File.read("#{@xml_path}/volume.xml"))
    vol_xml.elements['volume/name'].text = name
    size_b = convert_to_bytes(options[:size].to_f, options[:unit])
    vol_xml.elements['volume/capacity'].text = size_b.to_s
    vol_xml.elements['volume/target/format'].attributes["type"] = options[:type]
    vol_xml.elements['volume/target/path'].text = "#{@pool_path}/#{name}"
    vol_xml.elements['volume/target/permissions/owner'].text = uid.to_s
    vol_xml.elements['volume/target/permissions/group'].text = gid.to_s
    vol = @pool.create_volume_xml(vol_xml.to_s)
    @pool.refresh
  end

  def clone_to_new_disk(from, to)
    begin
      old_to_vol = @pool.lookup_volume_by_name(to)
    rescue Libvirt::RetrieveError
      # noop
    else
      old_to_vol.delete
    end
    from_vol = @pool.lookup_volume_by_name(from)
    xml = REXML::Document.new(from_vol.xml_desc)
    pool_path = REXML::Document.new(@pool.xml_desc).elements['pool/target/path'].text
    xml.elements['volume/name'].text = to
    xml.elements['volume/target/path'].text = "#{pool_path}/#{to}"
    @pool.create_volume_xml_from(xml.to_s, from_vol)
  end

  def disk_format(name)
    vol = @pool.lookup_volume_by_name(name)
    vol_xml = REXML::Document.new(vol.xml_desc)
    return vol_xml.elements['volume/target/format'].attributes["type"]
  end

  def disk_path(name)
    @pool.lookup_volume_by_name(name).path
  end

  # We use parted for the disk_mk* functions since it can format
  # partitions "inside" the super block device; mkfs.* need a
  # partition device (think /dev/sdaX), so we'd have to use something
  # like losetup or kpartx, which would require administrative
  # privileges. These functions only work for raw disk images.

  # TODO: We should switch to guestfish/libguestfs (which has
  # ruby-bindings) so we could use qcow2 instead of raw, and more
  # easily use LVM volumes.

  # For type, see label-type for mklabel in parted(8)
  def disk_mklabel(name, type)
    assert_equal("raw", disk_format(name))
    path = disk_path(name)
    cmd_helper("/sbin/parted -s '#{path}' mklabel #{type}")
  end

  # For fstype, see fs-type for mkfs in parted(8)
  def disk_mkpartfs(name, fstype)
    assert(disk_format(name), "raw")
    path = disk_path(name)
    cmd_helper("/sbin/parted -s '#{path}' mkpartfs primary '#{fstype}' 0% 100%")
  end

end