vaguerent/plugins/providers/hyperv/cap/configure_disks.rb
2020-06-30 14:22:41 -07:00

243 lines
9.7 KiB
Ruby

require "log4r"
require "fileutils"
require "vagrant/util/numeric"
require "vagrant/util/experimental"
module VagrantPlugins
module HyperV
module Cap
module ConfigureDisks
LOGGER = Log4r::Logger.new("vagrant::plugins::hyperv::configure_disks")
# @param [Vagrant::Machine] machine
# @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks
# @return [Hash] configured_disks - A hash of all the current configured disks
def self.configure_disks(machine, defined_disks)
return {} if defined_disks.empty?
return {} if !Vagrant::Util::Experimental.feature_enabled?("disks")
machine.ui.info(I18n.t("vagrant.cap.configure_disks.start"))
current_disks = machine.provider.driver.list_hdds
configured_disks = {disk: [], floppy: [], dvd: []}
defined_disks.each do |disk|
if disk.type == :disk
disk_data = handle_configure_disk(machine, disk, current_disks)
configured_disks[:disk] << disk_data unless disk_data.empty?
elsif disk.type == :floppy
# TODO: Write me
machine.ui.info(I18n.t("vagrant.cap.configure_disks.floppy_not_supported", name: disk.name))
elsif disk.type == :dvd
# TODO: Write me
machine.ui.info(I18n.t("vagrant.cap.configure_disks.dvd_not_supported", name: disk.name))
end
end
configured_disks
end
protected
# @param [Vagrant::Machine] machine - the current machine
# @param [Config::Disk] disk - the current disk to configure
# @param [Array] all_disks - A list of all currently defined disks in VirtualBox
# @return [Hash] current_disk - Returns the current disk. Returns nil if it doesn't exist
def self.get_current_disk(machine, disk, all_disks)
current_disk = nil
if disk.primary
# Ensure we grab the proper primary disk
# We can't rely on the order of `all_disks`, as they will not
# always come in port order, but primary should always be Location 0 Number 0.
current_disk = all_disks.select { |d| d["ControllerLocation"] == 0 && d["ControllerNumber"] == 0 }.first
# Need to get actual disk info to obtain UUID instead of what's returned
#
# This is not required for newly created disks, as its metadata is
# set when creating and attaching the disk. This is only for the primary
# disk, since it already exists.
real_disk_info = machine.provider.driver.get_disk(current_disk["Path"])
current_disk = real_disk_info
else
# Hyper-V disk names aren't the actual names of the disk, so we have
# to grab the name from the file path instead
current_disk = all_disks.select { |d| File.basename(d["Path"], '.*') == disk.name}.first
end
current_disk
end
# Handles all disk configs of type `:disk`
#
# @param [Vagrant::Machine] machine - the current machine
# @param [Config::Disk] disk - the current disk to configure
# @param [Array] all_disks - A list of all currently defined disks in VirtualBox
# @return [Hash] - disk_metadata
def self.handle_configure_disk(machine, disk, all_disks)
disk_metadata = {}
# Grab the existing configured disk, if it exists
current_disk = get_current_disk(machine, disk, all_disks)
# Configure current disk
if !current_disk
# create new disk and attach
disk_metadata = create_disk(machine, disk)
elsif compare_disk_size(machine, disk, current_disk)
disk_metadata = resize_disk(machine, disk, current_disk)
else
# TODO OLD: What if it needs to be resized?
# Can't use "attached" attribute, because it is false when the guest
# is powered off
#
#disk_info = machine.provider.driver.get_disk(current_disk["Path"])
#if disk_info["Attached"] == false
# LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest")
# machine.provider.driver.attach_disk(nil, nil, nil, current_disk["Path"])
#else
# LOGGER.info("No further configuration required for disk '#{disk.name}'")
#end
disk_metadata = {uuid: current_disk["DiskIdentifier"], name: disk.name, path: current_disk["Path"]}
if disk.primary
disk_metadata[:primary] = true
end
end
disk_metadata
end
# Check to see if current disk is configured based on defined_disks
#
# @param [Kernel_V2::VagrantConfigDisk] disk_config
# @param [Hash] defined_disk
# @return [Boolean]
def self.compare_disk_size(machine, disk_config, defined_disk)
# Hyper-V returns disk size in bytes
requested_disk_size = disk_config.size
disk_actual = machine.provider.driver.get_disk(defined_disk["Path"])
defined_disk_size = disk_actual["Size"]
if defined_disk_size > requested_disk_size
if File.extname(disk_actual["Path"]) == ".vhdx"
# VHDX formats can be shrunk
return true
else
machine.ui.warn(I18n.t("vagrant.cap.configure_disks.shrink_size_not_supported", name: disk_config.name))
return false
end
elsif defined_disk_size < requested_disk_size
return true
else
return false
end
end
# Creates and attaches a disk to a machine
#
# Extra params:
# - Fixed: true
# - BlockSizeBytes: must be 512 or 4096
# - LogicalSectorSizeBytes: must be 512 or 4096
# - PhysicalSectorSizeBytes: must be 512 or 4096
# - SourceDisk: file path
#
# @param [Vagrant::Machine] machine
# @param [Kernel_V2::VagrantConfigDisk] disk_config
def self.create_disk(machine, disk_config)
machine.ui.detail(I18n.t("vagrant.cap.configure_disks.create_disk", name: disk_config.name))
disk_provider_config = {}
disk_provider_config = disk_config.provider_config[:hyperv] if disk_config.provider_config
if !disk_provider_config.empty?
disk_provider_config = convert_size_vars!(disk_provider_config)
end
# Get the machines data dir, that will now be the path for the new disk
guest_disk_folder = machine.data_dir.join("Virtual Hard Disks")
if disk_config.file
disk_file = disk_config.file
else
# Set the extension
disk_ext = disk_config.disk_ext
disk_file = File.join(guest_disk_folder, disk_config.name) + ".#{disk_ext}"
LOGGER.info("Attempting to create a new disk file '#{disk_file}' of size '#{disk_config.size}' bytes")
machine.provider.driver.create_disk(disk_file, disk_config.size, disk_provider_config)
end
disk_info = machine.provider.driver.get_disk(disk_file)
disk_metadata = {uuid: disk_info["DiskIdentifier"], name: disk_config.name, path: disk_info["Path"]}
# TODO: Should we be passing in controller info?
machine.provider.driver.attach_disk(disk_file, disk_provider_config)
disk_metadata
end
# Converts any "shortcut" options such as "123MB" into its byte form. This
# is due to what parameter type is expected when calling the `New-VHD`
# powershell command
#
# @param [Hash] disk_provider_config
# @return [Hash] disk_provider_config
def self.convert_size_vars!(disk_provider_config)
conversion_keys = [:BlockSizeBytes, :LogicalSectorSizeBytes, :PhysicalSectorSizeBytes]
conversion_keys.each do |k|
if disk_provider_config.keys.include?(k)
if k.is_a? Integer
# Assume it is bytes
bytes = disk_provider_config[k]
elsif k == :BlockSizeBytes
bytes = Vagrant::Util::Numeric.string_to_bytes(disk_provider_config[k])
elsif k == :LogicalSectorSizeBytes || k == :PhysicalSectorSizeBytes
# Logical and Physical can only be these exact values, so converting it
# won't work
case disk_provider_config[k]
when "4096MB"
bytes = 4096
when "512MB"
bytes = 512
else
# Their config is wrong
bytes = disk_provider_config[k]
end
else
bytes = disk_provider_config[k]
end
disk_provider_config[k] = bytes
end
end
disk_provider_config
end
# @param [Vagrant::Machine] machine
# @param [Config::Disk] disk_config - the current disk to configure
# @param [Hash] defined_disk - current disk as represented by VirtualBox
# @return [Hash] - disk_metadata
def self.resize_disk(machine, disk_config, defined_disk)
machine.ui.detail(I18n.t("vagrant.cap.configure_disks.resize_disk", name: disk_config.name), prefix: true)
machine.provider.driver.resize_disk(defined_disk["Path"], disk_config.size.to_i)
disk_info = machine.provider.driver.get_disk(defined_disk["Path"])
# Store updated metadata
disk_metadata = {uuid: disk_info["DiskIdentifier"], name: disk_config.name, path: disk_info["Path"]}
disk_metadata
end
end
end
end
end