diff --git a/plugins/kernel_v2/config/disk.rb b/plugins/kernel_v2/config/disk.rb index 5c3daf44f..87c3d5741 100644 --- a/plugins/kernel_v2/config/disk.rb +++ b/plugins/kernel_v2/config/disk.rb @@ -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 diff --git a/plugins/providers/hyperv/action.rb b/plugins/providers/hyperv/action.rb index c7e1fb939..8925b2ab0 100644 --- a/plugins/providers/hyperv/action.rb +++ b/plugins/providers/hyperv/action.rb @@ -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] diff --git a/plugins/providers/hyperv/cap/cleanup_disks.rb b/plugins/providers/hyperv/cap/cleanup_disks.rb new file mode 100644 index 000000000..5ace44221 --- /dev/null +++ b/plugins/providers/hyperv/cap/cleanup_disks.rb @@ -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 diff --git a/plugins/providers/hyperv/cap/configure_disks.rb b/plugins/providers/hyperv/cap/configure_disks.rb new file mode 100644 index 000000000..e7d5fe96a --- /dev/null +++ b/plugins/providers/hyperv/cap/configure_disks.rb @@ -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 diff --git a/plugins/providers/hyperv/cap/validate_disk_ext.rb b/plugins/providers/hyperv/cap/validate_disk_ext.rb new file mode 100644 index 000000000..74512094a --- /dev/null +++ b/plugins/providers/hyperv/cap/validate_disk_ext.rb @@ -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 diff --git a/plugins/providers/hyperv/driver.rb b/plugins/providers/hyperv/driver.rb index 4f95db8c7..0b281eb3f 100644 --- a/plugins/providers/hyperv/driver.rb +++ b/plugins/providers/hyperv/driver.rb @@ -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 diff --git a/plugins/providers/hyperv/plugin.rb b/plugins/providers/hyperv/plugin.rb index 484bf2cae..c7d6eb2b9 100644 --- a/plugins/providers/hyperv/plugin.rb +++ b/plugins/providers/hyperv/plugin.rb @@ -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! diff --git a/plugins/providers/hyperv/scripts/attach_disk_drive.ps1 b/plugins/providers/hyperv/scripts/attach_disk_drive.ps1 new file mode 100644 index 000000000..64ee1ab27 --- /dev/null +++ b/plugins/providers/hyperv/scripts/attach_disk_drive.ps1 @@ -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 +} diff --git a/plugins/providers/hyperv/scripts/dismount_vhd.ps1 b/plugins/providers/hyperv/scripts/dismount_vhd.ps1 new file mode 100644 index 000000000..e53e359bf --- /dev/null +++ b/plugins/providers/hyperv/scripts/dismount_vhd.ps1 @@ -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 +} diff --git a/plugins/providers/hyperv/scripts/get_vhd.ps1 b/plugins/providers/hyperv/scripts/get_vhd.ps1 new file mode 100644 index 000000000..49124167e --- /dev/null +++ b/plugins/providers/hyperv/scripts/get_vhd.ps1 @@ -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 diff --git a/plugins/providers/hyperv/scripts/get_vm_status.ps1 b/plugins/providers/hyperv/scripts/get_vm_status.ps1 index dbe538440..5c7630d64 100644 --- a/plugins/providers/hyperv/scripts/get_vm_status.ps1 +++ b/plugins/providers/hyperv/scripts/get_vm_status.ps1 @@ -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" } diff --git a/plugins/providers/hyperv/scripts/list_hdds.ps1 b/plugins/providers/hyperv/scripts/list_hdds.ps1 new file mode 100644 index 000000000..907484a33 --- /dev/null +++ b/plugins/providers/hyperv/scripts/list_hdds.ps1 @@ -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 diff --git a/plugins/providers/hyperv/scripts/new_vhd.ps1 b/plugins/providers/hyperv/scripts/new_vhd.ps1 new file mode 100644 index 000000000..a8525928c --- /dev/null +++ b/plugins/providers/hyperv/scripts/new_vhd.ps1 @@ -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 +} diff --git a/plugins/providers/hyperv/scripts/remove_disk_drive.ps1 b/plugins/providers/hyperv/scripts/remove_disk_drive.ps1 new file mode 100644 index 000000000..caff08e68 --- /dev/null +++ b/plugins/providers/hyperv/scripts/remove_disk_drive.ps1 @@ -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 +} diff --git a/plugins/providers/hyperv/scripts/resize_disk_drive.ps1 b/plugins/providers/hyperv/scripts/resize_disk_drive.ps1 new file mode 100644 index 000000000..c890ecd4a --- /dev/null +++ b/plugins/providers/hyperv/scripts/resize_disk_drive.ps1 @@ -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 +} diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index d9e11f7ff..5d1029be5 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -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.") diff --git a/plugins/providers/virtualbox/cap/validate_disk_ext.rb b/plugins/providers/virtualbox/cap/validate_disk_ext.rb index e820c39ab..f5da4e8b4 100644 --- a/plugins/providers/virtualbox/cap/validate_disk_ext.rb +++ b/plugins/providers/virtualbox/cap/validate_disk_ext.rb @@ -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 diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 9cb46b54e..63f6a0e21 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -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 diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5a50a5958..a73840b90 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -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}'..." diff --git a/test/unit/plugins/kernel_v2/config/disk_test.rb b/test/unit/plugins/kernel_v2/config/disk_test.rb index 6b6879f37..967a9046d 100644 --- a/test/unit/plugins/kernel_v2/config/disk_test.rb +++ b/test/unit/plugins/kernel_v2/config/disk_test.rb @@ -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 diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index 848cc5ba8..c6576df6a 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -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 diff --git a/test/unit/plugins/providers/hyperv/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/hyperv/cap/cleanup_disks_test.rb new file mode 100644 index 000000000..383005f32 --- /dev/null +++ b/test/unit/plugins/providers/hyperv/cap/cleanup_disks_test.rb @@ -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 diff --git a/test/unit/plugins/providers/hyperv/cap/configure_disks_test.rb b/test/unit/plugins/providers/hyperv/cap/configure_disks_test.rb new file mode 100644 index 000000000..af51f645e --- /dev/null +++ b/test/unit/plugins/providers/hyperv/cap/configure_disks_test.rb @@ -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 diff --git a/website/data/docs-navigation.js b/website/data/docs-navigation.js index 5a8ff9137..3c6e9e72e 100644 --- a/website/data/docs-navigation.js +++ b/website/data/docs-navigation.js @@ -119,6 +119,7 @@ export default [ 'configuration', 'usage', { category: 'virtualbox', content: ['usage', 'common-issues'] }, + { category: 'hyperv', content: ['usage', 'common-issues'] }, ], }, 'multi-machine', diff --git a/website/pages/docs/disks/hyperv/common-issues.mdx b/website/pages/docs/disks/hyperv/common-issues.mdx new file mode 100644 index 000000000..467a276cc --- /dev/null +++ b/website/pages/docs/disks/hyperv/common-issues.mdx @@ -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. diff --git a/website/pages/docs/disks/hyperv/index.mdx b/website/pages/docs/disks/hyperv/index.mdx new file mode 100644 index 000000000..c64998b21 --- /dev/null +++ b/website/pages/docs/disks/hyperv/index.mdx @@ -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. diff --git a/website/pages/docs/disks/hyperv/usage.mdx b/website/pages/docs/disks/hyperv/usage.mdx new file mode 100644 index 000000000..0aaf51d1a --- /dev/null +++ b/website/pages/docs/disks/hyperv/usage.mdx @@ -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. diff --git a/website/pages/docs/disks/usage.mdx b/website/pages/docs/disks/usage.mdx index 3ab962a92..1bedbf6e9 100644 --- a/website/pages/docs/disks/usage.mdx +++ b/website/pages/docs/disks/usage.mdx @@ -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