Merge pull request #11541 from briancain/feature/hyperv-disk-mgmt

[Feature] Hyper-V Virtual Hard Disk Management
This commit is contained in:
Brian Cain 2020-07-06 15:24:29 -07:00 committed by GitHub
commit 88c2bc2be1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 1095 additions and 31 deletions

View File

@ -12,6 +12,8 @@ module VagrantPlugins
DEFAULT_DISK_TYPES = [:disk, :dvd, :floppy].freeze
FILE_CHAR_REGEX = /[^-a-z0-9_]/i.freeze
# Note: This value is for internal use only
#
# @return [String]
@ -97,7 +99,11 @@ module VagrantPlugins
end
current = @provider_config.merge(current) if !@provider_config.empty?
@provider_config = current
if current
@provider_config = current[:provider_config]
else
@provider_config = {}
end
end
def finalize!
@ -107,27 +113,25 @@ module VagrantPlugins
@size = nil if @size == UNSET_VALUE
@file = nil if @file == UNSET_VALUE
@disk_ext = "vdi" if @disk_ext == UNSET_VALUE
if @primary == UNSET_VALUE
@primary = false
end
if @name == UNSET_VALUE
if @name.is_a?(String) && @name.match(FILE_CHAR_REGEX)
@logger.warn("Vagrant will remove detected invalid characters in '#{@name}' and convert the disk name into something usable for a file")
@name.gsub!(FILE_CHAR_REGEX, "_")
elsif @name == UNSET_VALUE
if @primary
@name = "vagrant_primary"
else
@name = nil
end
end
@provider_config = nil if @provider_config == {}
end
# @return [Array] array of strings of error messages from config option validation
def validate(machine)
errors = _detected_errors
# validate type with list of known disk types
if !DEFAULT_DISK_TYPES.include?(@type)
@ -135,13 +139,20 @@ module VagrantPlugins
types: DEFAULT_DISK_TYPES.join(', '))
end
if @disk_ext
if @disk_ext == UNSET_VALUE
if machine.provider.capability?(:set_default_disk_ext)
@disk_ext = machine.provider.capability(:set_default_disk_ext)
else
@logger.warn("No provider capability defined to set default 'disk_ext' type. Will use 'vdi' for disk extension.")
@disk_ext = "vdi"
end
elsif @disk_ext
@disk_ext = @disk_ext.downcase
if machine.provider.capability?(:validate_disk_ext)
if !machine.provider.capability(:validate_disk_ext, @disk_ext)
if machine.provider.capability?(:get_default_disk_ext)
disk_exts = machine.provider.capability(:get_default_disk_ext).join(', ')
if machine.provider.capability?(:default_disk_exts)
disk_exts = machine.provider.capability(:default_disk_exts).join(', ')
else
disk_exts = "not found"
end
@ -158,12 +169,13 @@ module VagrantPlugins
if @size.is_a?(String)
@size = Vagrant::Util::Numeric.string_to_bytes(@size)
end
if !@size
errors << I18n.t("vagrant.config.disk.invalid_size", name: @name, machine: machine.name)
end
end
if !@size
errors << I18n.t("vagrant.config.disk.invalid_size", name: @name, machine: machine.name)
end
if @file
if !@file.is_a?(String)
errors << I18n.t("vagrant.config.disk.invalid_file_type", file: @file, machine: machine.name)
@ -174,10 +186,12 @@ module VagrantPlugins
end
if @provider_config
if !@provider_config.keys.include?(machine.provider_name)
machine.env.ui.warn(I18n.t("vagrant.config.disk.missing_provider",
machine: machine.name,
provider_name: machine.provider_name))
if !@provider_config.empty?
if !@provider_config.key?(machine.provider_name)
machine.env.ui.warn(I18n.t("vagrant.config.disk.missing_provider",
machine: machine.name,
provider_name: machine.provider_name))
end
end
end

View File

@ -149,6 +149,8 @@ module VagrantPlugins
b3.use NetSetMac
end
b3.use CleanupDisks
b3.use Disk
b3.use StartInstance
b3.use WaitForIPAddress
b3.use WaitForCommunicator, [:running]

View File

@ -0,0 +1,54 @@
require "log4r"
require "vagrant/util/experimental"
module VagrantPlugins
module HyperV
module Cap
module CleanupDisks
LOGGER = Log4r::Logger.new("vagrant::plugins::hyperv::cleanup_disks")
# @param [Vagrant::Machine] machine
# @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks
# @param [Hash] disk_meta_file - A hash of all the previously defined disks from the last configure_disk action
def self.cleanup_disks(machine, defined_disks, disk_meta_file)
return if disk_meta_file.values.flatten.empty?
return if !Vagrant::Util::Experimental.feature_enabled?("disks")
handle_cleanup_disk(machine, defined_disks, disk_meta_file["disk"])
# TODO: Floppy and DVD disks
end
protected
# @param [Vagrant::Machine] machine
# @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks
# @param [Hash] disk_meta - A hash of all the previously defined disks from the last configure_disk action
def self.handle_cleanup_disk(machine, defined_disks, disk_meta)
all_disks = machine.provider.driver.list_hdds
disk_meta.each do |d|
# look at Path instead of Name or UUID
disk_name = File.basename(d["Path"], '.*')
dsk = defined_disks.select { |dk| dk.name == disk_name }
if !dsk.empty? || d["primary"] == true
next
else
LOGGER.warn("Found disk not in Vagrantfile config: '#{d["Name"]}'. Removing disk from guest #{machine.name}")
machine.ui.warn(I18n.t("vagrant.cap.cleanup_disks.disk_cleanup", name: d["Name"]), prefix: true)
disk_actual = all_disks.select { |a| File.realdirpath(a["Path"]) == File.realdirpath(d["Path"]) }.first
if !disk_actual
machine.ui.warn(I18n.t("vagrant.cap.cleanup_disks.disk_not_found", name: d["Name"]), prefix: true)
else
machine.provider.driver.remove_disk(disk_actual["ControllerType"], disk_actual["ControllerNumber"], disk_actual["ControllerLocation"], disk_actual["Path"])
end
end
end
end
end
end
end
end

View File

@ -0,0 +1,200 @@
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.detect { |d| d["ControllerLocation"] == 0 && d["ControllerNumber"] == 0 }
# 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.
current_disk = machine.provider.driver.get_disk(current_disk["Path"])
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.detect { |d| File.basename(d["Path"], '.*') == disk.name}
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
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
#
# @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 = {}
if disk_config.provider_config && disk_config.provider_config.key?(:hyperv)
disk_provider_config = disk_config.provider_config[:hyperv]
end
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
LOGGER.info("Disk already defined by user at '#{disk_file}'. Using this disk instead of creating a new one...")
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"]}
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)
if disk_provider_config.key?(:BlockSizeBytes)
bytes = Vagrant::Util::Numeric.string_to_bytes(disk_provider_config[:BlockSizeBytes])
disk_provider_config[:BlockSizeBytes] = bytes
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

View File

@ -0,0 +1,34 @@
require "log4r"
module VagrantPlugins
module HyperV
module Cap
module ValidateDiskExt
LOGGER = Log4r::Logger.new("vagrant::plugins::hyperv::validate_disk_ext")
# The default set of disk formats that Hyper-V supports
DEFAULT_DISK_EXT_LIST = ["vhd", "vhdx"].map(&:freeze).freeze
DEFAULT_DISK_EXT = "vhdx".freeze
# @param [Vagrant::Machine] machine
# @param [String] disk_ext
# @return [Bool]
def self.validate_disk_ext(machine, disk_ext)
DEFAULT_DISK_EXT_LIST.include?(disk_ext)
end
# @param [Vagrant::Machine] machine
# @return [Array]
def self.default_disk_exts(machine)
DEFAULT_DISK_EXT_LIST
end
# @param [Vagrant::Machine] machine
# @return [String]
def self.set_default_disk_ext(machine)
DEFAULT_DISK_EXT
end
end
end
end
end

View File

@ -218,6 +218,77 @@ module VagrantPlugins
execute(:set_name, VMID: vm_id, VMName: vmname)
end
#
# Disk Driver methods
#
# @param [String] controller_type
# @param [String] controller_number
# @param [String] controller_location
# @param [Hash] opts
# @option opts [String] :ControllerType
# @option opts [String] :ControllerNumber
# @option opts [String] :ControllerLocation
def attach_disk(disk_file_path, **opts)
execute(:attach_disk_drive, VmId: @vm_id, Path: disk_file_path, ControllerType: opts[:ControllerType],
ControllerNumber: opts[:ControllerNumber], ControllerLocation: opts[:ControllerLocation])
end
# @param [String] path
# @param [Int] size_bytes
# @param [Hash] opts
# @option opts [Bool] :Fixed
# @option opts [String] :BlockSizeBytes
# @option opts [String] :LogicalSectorSizeBytes
# @option opts [String] :PhysicalSectorSizeBytes
# @option opts [String] :SourceDisk
# @option opts [Bool] :Differencing
# @option opts [String] :ParentPath
def create_disk(path, size_bytes, **opts)
execute(:new_vhd, Path: path, SizeBytes: size_bytes, Fixed: opts[:Fixed],
BlockSizeBytes: opts[:BlockSizeBytes], LogicalSectorSizeBytes: opts[:LogicalSectorSizeBytes],
PhysicalSectorSizeBytes: opts[:PhysicalSectorSizeBytes],
SourceDisk: opts[:SourceDisk], Differencing: opts[:Differencing],
ParentPath: opts[:ParentPath])
end
# @param [String] disk_file_path
def dismount_disk(disk_file_path)
execute(:dismount_vhd, DiskFilePath: disk_file_path)
end
# @param [String] disk_file_path
def get_disk(disk_file_path)
execute(:get_vhd, DiskFilePath: disk_file_path)
end
# @return [Array[Hash]]
def list_hdds
execute(:list_hdds, VmId: @vm_id)
end
# @param [String] controller_type
# @param [String] controller_number
# @param [String] controller_location
# @param [String] disk_file_path
# @param [Hash] opts
# @option opts [String] :ControllerType
# @option opts [String] :ControllerNumber
# @option opts [String] :ControllerLocation
def remove_disk(controller_type, controller_number, controller_location, disk_file_path, **opts)
execute(:remove_disk_drive, VmId: @vm_id, ControllerType: controller_type,
ControllerNumber: controller_number, ControllerLocation: controller_location,
DiskFilePath: disk_file_path)
end
# @param [String] path
# @param [Int] size_bytes
# @param [Hash] opts
def resize_disk(disk_file_path, size_bytes, **opts)
execute(:resize_disk_drive, VmId: @vm_id, DiskFilePath: disk_file_path,
DiskSize: size_bytes)
end
protected
def execute_powershell(path, options, &block)
@ -227,6 +298,7 @@ module VagrantPlugins
options = options || {}
ps_options = []
options.each do |key, value|
next if !value || value.to_s.empty?
next if value == false
ps_options << "-#{key}"
# If the value is a TrueClass assume switch

View File

@ -32,6 +32,31 @@ module VagrantPlugins
Cap::SnapshotList
end
provider_capability(:hyperv, :configure_disks) do
require_relative "cap/configure_disks"
Cap::ConfigureDisks
end
provider_capability(:hyperv, :cleanup_disks) do
require_relative "cap/cleanup_disks"
Cap::CleanupDisks
end
provider_capability(:hyperv, :validate_disk_ext) do
require_relative "cap/validate_disk_ext"
Cap::ValidateDiskExt
end
provider_capability(:hyperv, :default_disk_exts) do
require_relative "cap/validate_disk_ext"
Cap::ValidateDiskExt
end
provider_capability(:hyperv, :set_default_disk_ext) do
require_relative "cap/validate_disk_ext"
Cap::ValidateDiskExt
end
protected
def self.init!

View File

@ -0,0 +1,28 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$VmId,
[Parameter(Mandatory=$true)]
[string]$Path,
[string]$ControllerType,
[string]$ControllerNumber,
[string]$ControllerLocation
)
$Params = @{}
foreach ($key in $MyInvocation.BoundParameters.keys) {
$value = (Get-Variable -Exclude "ErrorAction" $key).Value
if (($key -ne "VmId") -and ($key -ne "ErrorAction")) {
$Params.Add($key, $value)
}
}
try {
$VM = Hyper-V\Get-VM -Id $VmId
Hyper-V\Add-VMHardDiskDrive -VMName $VM.Name @Params
} catch {
Write-ErrorMessage "Failed to attach disk ${DiskFilePath} to VM ${VM}: ${PSItem}"
exit 1
}

View File

@ -0,0 +1,13 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$DiskFilePath
)
try {
Hyper-V\Dismount-VHD -path $DiskFilePath
} catch {
Write-ErrorMessage "Failed to dismount disk info from disk file path ${DiskFilePath}: ${PSItem}"
exit 1
}

View File

@ -0,0 +1,16 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$DiskFilePath
)
try {
$Disk = Hyper-V\Get-VHD -path $DiskFilePath
} catch {
Write-ErrorMessage "Failed to retrieve disk info from disk file path ${DiskFilePath}: ${PSItem}"
exit 1
}
$result = ConvertTo-json $Disk
Write-OutputMessage $result

View File

@ -14,7 +14,7 @@ try
# type was loaded in Microsoft.HyperV.PowerShell
[void][System.Reflection.Assembly]::LoadWithPartialName('Microsoft.HyperV.PowerShell.Objects, Culture=neutral, PublicKeyToken=31bf3856ad364e35')
} catch {
# Empty catch ok, since if we didn't load the types, we will fail in the next block
# Empty catch ok, since if we didn't load the types, we will fail in the next block
}
$VmmsPath = if ([environment]::Is64BitProcess) { "$($env:SystemRoot)\System32\vmms.exe" } else { "$($env:SystemRoot)\Sysnative\vmms.exe" }

View File

@ -0,0 +1,17 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$VmId
)
try {
$VM = Hyper-V\Get-VM -Id $VmId
$Disks = @(Hyper-V\Get-VMHardDiskDrive -VMName $VM.Name)
} catch {
Write-ErrorMessage "Failed to retrieve all disk info from ${VM}: ${PSItem}"
exit 1
}
$result = ConvertTo-json $Disks
Write-OutputMessage $result

View File

@ -0,0 +1,31 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$Path,
[Parameter(Mandatory=$true)]
[UInt64]$SizeBytes,
[switch]$Fixed,
[switch]$Differencing,
[string]$ParentPath,
[Uint32]$BlockSizeBytes,
[UInt32]$LogicalSectorSizeBytes,
[UInt32]$PhysicalSectorSizeBytes,
[UInt32]$SourceDisk
)
$Params = @{}
foreach ($key in $MyInvocation.BoundParameters.keys) {
$value = (Get-Variable -Exclude "ErrorAction" $key).Value
if ($key -ne "ErrorAction") {
$Params.Add($key, $value)
}
}
try {
Hyper-V\New-VHD @Params
} catch {
Write-ErrorMessage "Failed to create disk ${DiskFilePath}: ${PSItem}"
exit 1
}

View File

@ -0,0 +1,25 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$VmId,
[Parameter(Mandatory=$true)]
[string]$ControllerType,
[Parameter(Mandatory=$true)]
[string]$ControllerNumber,
[Parameter(Mandatory=$true)]
[string]$ControllerLocation,
[Parameter(Mandatory=$true)]
[string]$DiskFilePath
)
try {
$VM = Hyper-V\Get-VM -Id $VmId
Hyper-v\Remove-VMHardDiskDrive -VMName $VM.Name -ControllerType $ControllerType -ControllerNumber $ControllerNumber -ControllerLocation $ControllerLocation
Remove-Item -Path $DiskFilePath
} catch {
Write-ErrorMessage "Failed to remove disk ${DiskFilePath} to VM ${VM}: ${PSItem}"
exit 1
}

View File

@ -0,0 +1,18 @@
#Requires -Modules VagrantMessages
param(
[Parameter(Mandatory=$true)]
[string]$VmId,
[Parameter(Mandatory=$true)]
[string]$DiskFilePath,
[Parameter(Mandatory=$true)]
[UInt64]$DiskSize
)
try {
$VM = Hyper-V\Get-VM -Id $VmId
Hyper-V\Resize-VHD -Path $DiskFilePath -SizeBytes $DiskSize
} catch {
Write-ErrorMessage "Failed to resize disk ${DiskFilePath} for VM ${VM}: ${PSItem}"
exit 1
}

View File

@ -36,7 +36,7 @@ module VagrantPlugins
LOGGER.warn("Found disk not in Vagrantfile config: '#{d["name"]}'. Removing disk from guest #{machine.name}")
disk_info = machine.provider.driver.get_port_and_device(d["uuid"])
machine.ui.warn("Disk '#{d["name"]}' no longer exists in Vagrant config. Removing and closing medium from guest...", prefix: true)
machine.ui.warn(I18n.t("vagrant.cap.cleanup_disks.disk_cleanup", name: d["name"]), prefix: true)
if disk_info.empty?
LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.")

View File

@ -7,18 +7,25 @@ module VagrantPlugins
LOGGER = Log4r::Logger.new("vagrant::plugins::virtualbox::validate_disk_ext")
# The default set of disk formats that VirtualBox supports
DEFAULT_DISK_EXT = ["vdi", "vmdk", "vhd"].map(&:freeze).freeze
DEFAULT_DISK_EXT_LIST = ["vdi", "vmdk", "vhd"].map(&:freeze).freeze
DEFAULT_DISK_EXT = "vdi".freeze
# @param [Vagrant::Machine] machine
# @param [String] disk_ext
# @return [Bool]
def self.validate_disk_ext(machine, disk_ext)
DEFAULT_DISK_EXT.include?(disk_ext)
DEFAULT_DISK_EXT_LIST.include?(disk_ext)
end
# @param [Vagrant::Machine] machine
# @return [Array]
def self.get_default_disk_ext(machine)
def self.default_disk_exts(machine)
DEFAULT_DISK_EXT_LIST
end
# @param [Vagrant::Machine] machine
# @return [String]
def self.set_default_disk_ext(machine)
DEFAULT_DISK_EXT
end
end

View File

@ -54,7 +54,12 @@ module VagrantPlugins
Cap::ValidateDiskExt
end
provider_capability(:virtualbox, :get_default_disk_ext) do
provider_capability(:virtualbox, :default_disk_exts) do
require_relative "cap/validate_disk_ext"
Cap::ValidateDiskExt
end
provider_capability(:virtualbox, :set_default_disk_ext) do
require_relative "cap/validate_disk_ext"
Cap::ValidateDiskExt
end

View File

@ -2211,6 +2211,11 @@ en:
# Translations for Vagrant middleware actions
#-------------------------------------------------------------------------------
cap:
cleanup_disks:
disk_cleanup: |-
Disk '%{name}' no longer exists in Vagrant config. Removing and closing medium from guest...
disk_not_found: |-
Disk '%{name}' could not be found, and could not be properly removed. Please remove this disk manually if it still exists
configure_disks:
start: "Configuring storage mediums..."
floppy_not_supported: "Floppy disk configuration not yet supported. Skipping disk '%{name}'..."

View File

@ -9,8 +9,11 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do
subject { described_class.new(type) }
let(:ui) { double("ui") }
let(:env) { double("env", ui: ui) }
let(:provider) { double("provider") }
let(:machine) { double("machine", provider: provider) }
let(:machine) { double("machine", name: "name", provider: provider, env: env,
provider_name: :virtualbox) }
def assert_invalid
@ -34,6 +37,8 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do
subject.size = 100
allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)
allow(provider).to receive(:capability).with(:validate_disk_ext, "vdi").and_return(true)
allow(provider).to receive(:capability?).with(:set_default_disk_ext).and_return(true)
allow(provider).to receive(:capability).with(:set_default_disk_ext).and_return("vdi")
end
describe "with defaults" do
@ -53,8 +58,25 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do
end
end
describe "defining a new config that needs to match internal restraints" do
before do
describe "with an invalid config" do
let(:invalid_subject) { described_class.new(type) }
it "raises an error if size not set" do
invalid_subject.name = "bar"
subject.finalize!
assert_invalid
end
context "with an invalid disk extension" do
before do
allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)
allow(provider).to receive(:capability).with(:validate_disk_ext, "fake").and_return(false)
end
it "raises an error" do
subject.finalize!
assert_invalid
end
end
end
end

View File

@ -8,7 +8,7 @@ describe VagrantPlugins::Kernel_V2::VMConfig do
subject { described_class.new }
let(:provider) { double("provider") }
let(:machine) { double("machine", provider: provider) }
let(:machine) { double("machine", provider: provider, provider_name: "provider") }
def assert_invalid
errors = subject.validate(machine)
@ -40,6 +40,8 @@ describe VagrantPlugins::Kernel_V2::VMConfig do
allow(provider).to receive(:capability?).with(:validate_disk_ext).and_return(true)
allow(provider).to receive(:capability).with(:validate_disk_ext, "vdi").and_return(true)
allow(provider).to receive(:capability?).with(:set_default_disk_ext).and_return(true)
allow(provider).to receive(:capability).with(:set_default_disk_ext).and_return("vdi")
subject.box = "foo"
end

View File

@ -0,0 +1,96 @@
require_relative "../../../../base"
require Vagrant.source_root.join("plugins/providers/hyperv/cap/cleanup_disks")
describe VagrantPlugins::HyperV::Cap::CleanupDisks do
include_context "unit"
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
let(:driver) { double("driver") }
let(:machine) do
iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|
allow(m.provider).to receive(:driver).and_return(driver)
allow(m).to receive(:state).and_return(state)
end
end
let(:state) do
double(:state)
end
let(:subject) { described_class }
let(:disk_meta_file) { {disk: [], floppy: [], dvd: []} }
let(:defined_disks) { {} }
before do
allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true)
end
context "#cleanup_disks" do
it "returns if there's no data in meta file" do
subject.cleanup_disks(machine, defined_disks, disk_meta_file)
expect(subject).not_to receive(:handle_cleanup_disk)
end
describe "with disks to clean up" do
let(:disk_meta_file) { {disk: [{"UUID"=>"1234", "Path"=> "c:\\users\\vagrant\\storage.vhdx", "Name"=>"storage"}], floppy: [], dvd: []} }
it "calls the cleanup method if a disk_meta file is defined" do
expect(subject).to receive(:handle_cleanup_disk).
with(machine, defined_disks, disk_meta_file["disk"]).
and_return(true)
subject.cleanup_disks(machine, defined_disks, disk_meta_file)
end
end
end
context "#handle_cleanup_disk" do
let(:disk_meta_file) { {disk: [{"UUID"=>"1234", "Path"=> "c:\\users\\vagrant\\storage.vhdx", "Name"=>"storage"}], floppy: [], dvd: []} }
let(:defined_disks) { [] }
let(:all_disks) { [{"UUID"=>"1234", "Path"=> "c:\\users\\vagrant\\storage.vhdx", "Name"=>"storage",
"ControllerType"=>"IDE", "ControllerNumber"=>1, "ControllerLocation"=>0}] }
let(:path) { "C:\\Users\\vagrant\\storage.vhdx" }
it "removes and closes medium from guest" do
expect(driver).to receive(:list_hdds).and_return(all_disks)
expect(driver).to receive(:remove_disk).with("IDE", 1, 0, "c:\\users\\vagrant\\storage.vhdx").and_return(true)
subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])
end
it "displays a warning if the disk could not be determined" do
expect(driver).to receive(:list_hdds).and_return(all_disks)
expect(File).to receive(:realdirpath).and_return(path)
expect(File).to receive(:realdirpath).and_return("")
expect(driver).not_to receive(:remove_disk)
expect(machine.ui).to receive(:warn).twice
subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])
end
describe "when windows paths mix cases" do
let(:disk_meta_file) { {disk: [{"UUID"=>"1234", "Path"=> "c:\\users\\vagrant\\storage.vhdx", "Name"=>"storage"}], floppy: [], dvd: []} }
let(:defined_disks) { [] }
let(:all_disks) { [{"UUID"=>"1234", "Path"=> "C:\\Users\\vagrant\\storage.vhdx", "Name"=>"storage",
"ControllerType"=>"IDE", "ControllerNumber"=>1, "ControllerLocation"=>0}] }
let(:path) { "C:\\Users\\vagrant\\storage.vhdx" }
it "still removes and closes the medium from the guest" do
expect(driver).to receive(:list_hdds).and_return(all_disks)
expect(File).to receive(:realdirpath).twice.and_return(path)
expect(driver).to receive(:remove_disk).with("IDE", 1, 0, path).and_return(true)
subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk])
end
end
end
end

View File

@ -0,0 +1,250 @@
require_relative "../../../../base"
require Vagrant.source_root.join("plugins/providers/hyperv/cap/configure_disks")
describe VagrantPlugins::HyperV::Cap::ConfigureDisks do
include_context "unit"
let(:iso_env) do
# We have to create a Vagrantfile so there is a root path
env = isolated_environment
env.vagrantfile("")
env.create_vagrant_env
end
let(:driver) { double("driver") }
let(:machine) do
iso_env.machine(iso_env.machine_names[0], :dummy).tap do |m|
allow(m.provider).to receive(:driver).and_return(driver)
allow(m).to receive(:state).and_return(state)
end
end
let(:state) do
double(:state)
end
let(:defined_disks) { [double("disk", name: "vagrant_primary", size: "5GB", primary: true, type: :disk),
double("disk", name: "disk-0", size: "5GB", primary: false, type: :disk),
double("disk", name: "disk-1", size: "5GB", primary: false, type: :disk),
double("disk", name: "disk-2", size: "5GB", primary: false, type: :disk)] }
let(:subject) { described_class }
let(:all_disks) { [{"UUID"=>"12345",
"Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx",
"ControllerLocation"=>0,
"ControllerNumber"=>0},
{"UUID"=>"67890",
"Name"=>"disk-0",
"Path"=>"C:/Users/vagrant/disks/disk-0.vhdx",
"ControllerLocation"=>1,
"ControllerNumber"=>0},
{"UUID"=>"324bbb53-d5ad-45f8-9bfa-1f2468b199a8",
"Path"=>"C:/Users/vagrant/disks/disk-1.vhdx",
"Name"=>"disk-1",
"ControllerLocation"=>2,
"ControllerNumber"=>0}] }
before do
allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true)
end
context "#configure_disks" do
let(:dsk_data) { {"UUID"=>"1234", "Name"=>"disk", "Path"=> "C:/Users/vagrant/storage.vhdx"} }
it "configures disks and returns the disks defined" do
allow(driver).to receive(:list_hdds).and_return([])
expect(subject).to receive(:handle_configure_disk).exactly(4).and_return(dsk_data)
subject.configure_disks(machine, defined_disks)
end
describe "with no disks to configure" do
let(:defined_disks) { {} }
it "returns empty hash if no disks to configure" do
expect(subject.configure_disks(machine, defined_disks)).to eq({})
end
end
end
context "#get_current_disk" do
it "gets primary disk uuid if disk to configure is primary" do
expect(driver).to receive(:get_disk).with(all_disks.first["Path"]).and_return(all_disks.first)
primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks)
expect(primary_disk).to eq(all_disks.first)
end
it "finds the disk to configure" do
disk = subject.get_current_disk(machine, defined_disks[1], all_disks)
expect(disk).to eq(all_disks[1])
end
it "returns nil if disk is not found" do
disk = subject.get_current_disk(machine, defined_disks[3], all_disks)
expect(disk).to be_nil
end
end
context "#handle_configure_disk" do
describe "when creating a new disk" do
let(:all_disks) { [{"UUID"=>"12345",
"Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx",
"ControllerLocation"=>0,
"ControllerNumber"=>0}] }
let(:disk_meta) { {"UUID"=>"12345", "Name"=>"vagrant_primary", "Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx" } }
it "creates a new disk if it doesn't yet exist" do
expect(subject).to receive(:create_disk).with(machine, defined_disks[1])
.and_return(disk_meta)
subject.handle_configure_disk(machine, defined_disks[1], all_disks)
end
end
describe "when a disk needs to be resized" do
let(:all_disks) { [{"UUID"=>"12345",
"Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx",
"ControllerLocation"=>0,
"ControllerNumber"=>0},
{"UUID"=>"67890",
"Name"=>"disk-0",
"Path"=>"C:/Users/vagrant/disks/disk-0.vhdx",
"ControllerLocation"=>1,
"ControllerNumber"=>0},
{"UUID"=>"324bbb53-d5ad-45f8-9bfa-1f2468b199a8",
"Path"=>"C:/Users/vagrant/disks/disk-1.vhdx",
"Name"=>"disk-1",
"ControllerLocation"=>2,
"ControllerNumber"=>0}] }
it "resizes a disk" do
expect(subject).to receive(:get_current_disk).
with(machine, defined_disks[1], all_disks).and_return(all_disks[1])
expect(subject).to receive(:compare_disk_size).
with(machine, defined_disks[1], all_disks[1]).and_return(true)
expect(subject).to receive(:resize_disk).
with(machine, defined_disks[1], all_disks[1]).and_return(true)
subject.handle_configure_disk(machine, defined_disks[1], all_disks)
end
end
describe "if no additional disk configuration is required" do
let(:all_disks) { [{"UUID"=>"12345",
"Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx",
"ControllerLocation"=>0,
"ControllerNumber"=>0},
{"UUID"=>"67890",
"Name"=>"disk-0",
"Path"=>"C:/Users/vagrant/disks/disk-0.vhdx",
"ControllerLocation"=>1,
"ControllerNumber"=>0},
{"UUID"=>"324bbb53-d5ad-45f8-9bfa-1f2468b199a8",
"Path"=>"C:/Users/vagrant/disks/disk-1.vhdx",
"Name"=>"disk-1",
"ControllerLocation"=>2,
"ControllerNumber"=>0}] }
it "does nothing if all disks are properly configured" do
expect(subject).to receive(:get_current_disk).
with(machine, defined_disks[1], all_disks).and_return(all_disks[1])
expect(subject).to receive(:compare_disk_size).
with(machine, defined_disks[1], all_disks[1]).and_return(false)
subject.handle_configure_disk(machine, defined_disks[1], all_disks)
end
end
end
context "#compare_disk_size" do
let(:disk_config_small) { double("disk", name: "disk-0", size: 41824.0, primary: false, type: :disk) }
let(:disk_config_large) { double("disk", name: "disk-0", size: 123568719476736.0, primary: false, type: :disk) }
let(:disk_large) { [{"UUID"=>"12345",
"Path"=>"C:/Users/vagrant/disks/ubuntu-18.04-amd64-disk001.vhdx",
"ControllerLocation"=>0,
"ControllerNumber"=>0}] }
let(:disk_small) { {"UUID"=>"67890",
"Path"=>"C:/Users/vagrant/disks/small_disk.vhd",
"Size"=>1073741824.0,
"ControllerLocation"=>1,
"ControllerNumber"=>0} }
it "shows a warning if user attempts to shrink size of a vhd disk" do
expect(machine.ui).to receive(:warn)
expect(driver).to receive(:get_disk).with(all_disks[1]["Path"]).and_return(disk_small)
expect(subject.compare_disk_size(machine, disk_config_small, all_disks[1])).to be_falsey
end
it "returns true if requested size is bigger than current size" do
expect(driver).to receive(:get_disk).with(all_disks[2]["Path"]).and_return(disk_small)
expect(subject.compare_disk_size(machine, disk_config_large, all_disks[2])).to be_truthy
end
end
context "#create_disk" do
let(:disk_provider_config) { {} }
let(:disk_config) { double("disk", name: "disk-0", size: 1073741824.0,
primary: false, type: :disk, disk_ext: "vhdx",
provider_config: disk_provider_config,
file: nil) }
let(:disk_file) { "C:/Users/vagrant/disks/Virtual Hard Disks/disk-0.vhdx" }
let(:data_dir) { Pathname.new("C:/Users/vagrant/disks") }
let(:disk) { {"DiskIdentifier"=>"12345",
"Path"=>"C:/Users/vagrant/disks/Virtual Hard Disks/disk-0.vhdx",
"ControllerLocation"=>1,
"ControllerNumber"=>0} }
it "creates a disk and attaches it to a guest" do
expect(machine).to receive(:data_dir).and_return(data_dir)
expect(driver).to receive(:create_disk).with(disk_file, disk_config.size, {})
expect(driver).to receive(:get_disk).with(disk_file).and_return(disk)
expect(driver).to receive(:attach_disk).with(disk_file, {})
subject.create_disk(machine, disk_config)
end
end
context "#convert_size_vars!" do
let(:disk_provider_config) { {BlockSizeBytes: "128MB", LogicalSectorSizeBytes: 512, PhysicalSectorSizeBytes: 4096 } }
it "converts certain powershell arguments into something usable" do
updated_config = subject.convert_size_vars!(disk_provider_config)
expect(updated_config[:BlockSizeBytes]).to eq(134217728)
expect(updated_config[:LogicalSectorSizeBytes]).to eq(512)
expect(updated_config[:PhysicalSectorSizeBytes]).to eq(4096)
end
end
context "#resize_disk" do
let(:disk_config) { double("disk", name: "disk-0", size: 1073741824.0,
primary: false, type: :disk, disk_ext: "vhdx",
provider_config: nil,
file: nil) }
let(:disk) { {"DiskIdentifier"=>"12345",
"Path"=>"C:/Users/vagrant/disks/disk-0.vhdx",
"ControllerLocation"=>1,
"ControllerNumber"=>0} }
let(:disk_file) { "C:/Users/vagrant/disks/disk-0.vhdx" }
it "resizes the disk" do
expect(driver).to receive(:get_disk).with(disk_file).and_return(disk)
expect(driver).to receive(:resize_disk).with(disk_file, disk_config.size.to_i).and_return(true)
subject.resize_disk(machine, disk_config, all_disks[1])
end
end
end

View File

@ -119,6 +119,7 @@ export default [
'configuration',
'usage',
{ category: 'virtualbox', content: ['usage', 'common-issues'] },
{ category: 'hyperv', content: ['usage', 'common-issues'] },
],
},
'multi-machine',

View File

@ -0,0 +1,26 @@
---
layout: docs
page_title: Common Issues - Disks Hyper-V Provider
sidebar_title: Common Issues
description: |-
This page lists some common issues people run into with Vagrant and Hyper-V
as well as solutions for those issues.
---
# Common Issues and Troubleshooting
This page lists some common issues people run into with Vagrant and Hyper-V
as well as solutions for those issues.
## Are my disks attached?
A handy way to figure out what disks are attached (or not attached) to your guest
is to open up the Hyper-V GUI and select the guest. When selecting a guest on the GUI,
it should open more information about the guest, including storage information. Here
you should see a list of disks attached to your guest.
## Applying Vagrant disk configuration changes to guests
Due to how Hyper-V works, you must reload your guest for any disk config changes
to be applied. So if you update your Vagrantfile to update or even remove disks, make
sure to `vagrant reload` your guests for these changes to be applied.

View File

@ -0,0 +1,33 @@
---
layout: docs
page_title: Disks for Hyper-V Provider
sidebar_title: Hyper-V
description: |-
Vagrant comes with support out of the box for Hyper-V, a free,
cross-platform consumer virtualization product.
---
# Hyper-V
~> **Warning!** This feature is experimental and may break or
change in between releases. Use at your own risk. It currently is not officially
supported or functional.
This feature currently reqiures the experimental flag to be used. To explicitly enable this feature, you can set the experimental flag to:
```
VAGRANT_EXPERIMENTAL="disks"
```
Please note that `VAGRANT_EXPERIMENTAL` is an environment variable. For more
information about this flag visit the [Experimental docs page](/docs/experimental/)
for more info. Without this flag enabled, any disks defined will not be configured.
Because of how Hyper-V handles disk management, a Vagrant guest _must_ be powered
off for any changes to be applied to a guest. If you make a configuration change
with a guests disk, you will need to `vagrant reload` the guest for any changes
to be applied.
For more information on how to use VirtualBox to configure disks for a guest, refer
to the [general usage](/docs/disks/usage) and [configuration](/docs/disks/configuration)
guide for more information.

View File

@ -0,0 +1,71 @@
---
layout: docs
page_title: Usage - Disks Hyper-V Provider
sidebar_title: Usage
description: |-
The Vagrant Hyper-V provider is used just like any other provider. Please
read the general basic usage page for providers.
---
# Usage
~> **Warning!** This feature is experimental and may break or
change in between releases. Use at your own risk. It currently is not officially
supported or functional.
This feature currently reqiures the experimental flag to be used. To explicitly enable this feature, you can set the experimental flag to:
```
VAGRANT_EXPERIMENTAL="disks"
```
Please note that `VAGRANT_EXPERIMENTAL` is an environment variable. For more
information about this flag visit the [Experimental docs page](/docs/experimental/)
for more info. Without this flag enabled, any disks defined will not be configured.
For examples of how to use the disk feature with Hyper-V, please refer to the
[general disk usage guide](/docs/disks/usage) for more examples.
## provider_config options
Most options are used for either creating or attaching a hard disk to your guest.
Vagrant supports most options for these operations. You should be able to define
the powershell specific argument to a given Hyper-V command in the provider_config
hash, and Vagrant should properly pass it along to the command.
To define a provider specific option, please refer to the [Disk Options documentation page](/docs/disks/configuration) for more info.
### Note about options defined below
It is possible these options could be out of date or stale. If you happen to see
an option that has changed or is missing from this page, please open an issue
or pull request on Vagrants GitHub page to correct this.
### New-VHD Supported Options
For more information about each option, please visit the [New-VHD Hyper-V documentation](https://docs.microsoft.com/en-us/powershell/module/hyper-v/new-vhd?view=win10-ps).
__Note:__ By default, all Hyper-V disks are defined as a Dynamic virtual hard disk. If you
wish to make the disk a fixed size, you can set the `Fixed` option below when creating
a new disk.
* `BlockSizeBytes` (string) - Optional argument, i.e. `"128MB"`
* `Differencing` (bool) - If set, the disk will be used to store differencing changes from parent disk (must set `ParentPath`)
* `Fixed` (bool) - If set, the disk will be a fixed size, not dynamically allocated.
* `LogicalSectorSizeBytes` (int) - Optional argument, must be either `512` or `4096`
* `ParentPath` (string) - The parent disk path used if a `Differencing` disk is defined
* `PhysicalSectorSizeBytes` (string) - Optional argument, must be either `512` or `4096`
* `SourceDisk` (int) - Existing disk to use as a source for the new disk
### Add-VMHardDiskDrive Supported Options
For more information about each option, please visit the [Add-VMHardDiskDrive Hyper-V documentation](https://docs.microsoft.com/en-us/powershell/module/hyper-v/add-vmharddiskdrive?view=win10-ps)
Generally, these options do not need to be set or handled by most users. Only
use these options if you are sure you know what you are doing. Vagrant will
be able to attach disks for you without these options, but they are available
if it is required that you specificy a specific location for a disk.
* `ControllerLocation` (int) - The location that the disk should be attached to on the controller
* `ControllerNumber` (int) - The controller to use for attaching the disk
* `ControllerType` (string) - The kind of controller to use when attaching the a disk. Only `"IDE"` and `"SCSI"` are valid.

View File

@ -21,9 +21,6 @@ Please note that `VAGRANT_EXPERIMENTAL` is an environment variable. For more
information about this flag visit the [Experimental docs page](/docs/experimental/)
for more info. Without this flag enabled, any disks defined will not be configured.
Also note that the examples below use the VirtualBox provider, which is the current
supported provider for this feature.
Below are some very simple examples of how to use Vagrant Disks with the VirtualBox provider.
## Basic Examples