From c52eb1b44c27655394c466734bdc5a7960fc9433 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 12 May 2020 16:52:41 -0400 Subject: [PATCH 01/42] Feature: ISO attachment for VirtualBox This builds on the existing disk functionality, and adds some special IDE controller-related flavor. Considerations for IDE controllers: - Primary/secondary attachments, so that each port can have two devices attached - Adding the ability to address a specific controller name for disk attachment This also prevents a user from attaching multiple instances of the same ISO file, because VirtualBox will assign each of these the same UUID which makes disconnection difficult. However, if multiple copies of the ISO are attached to different devices, removing the DVD config will cause the duplicate devices to be removed. We may want to consider additional work to make the storage controllers truly generic. --- plugins/kernel_v2/config/disk.rb | 6 +- .../providers/virtualbox/cap/cleanup_disks.rb | 62 ++++++++--- .../virtualbox/cap/configure_disks.rb | 78 ++++++++++++-- .../virtualbox/driver/version_5_0.rb | 25 +++-- templates/locales/en.yml | 2 + .../plugins/kernel_v2/config/disk_test.rb | 29 ++++- .../kernel_v2/config/vm_trigger_test.rb | 4 +- .../virtualbox/cap/cleanup_disks_test.rb | 44 +++++++- .../virtualbox/cap/configure_disks_test.rb | 101 ++++++++++++++---- .../virtualbox/driver/version_5_0_test.rb | 28 +++++ 10 files changed, 317 insertions(+), 62 deletions(-) diff --git a/plugins/kernel_v2/config/disk.rb b/plugins/kernel_v2/config/disk.rb index 87c3d5741..1ccbb63e7 100644 --- a/plugins/kernel_v2/config/disk.rb +++ b/plugins/kernel_v2/config/disk.rb @@ -41,7 +41,8 @@ module VagrantPlugins # @return [Integer,String] attr_accessor :size - # Path to the location of the disk file (Optional) + # Path to the location of the disk file (Optional for `:disk` type, + # required for `:dvd` type.) # # @return [String] attr_accessor :file @@ -175,6 +176,9 @@ module VagrantPlugins errors << I18n.t("vagrant.config.disk.invalid_size", name: @name, machine: machine.name) end + if @type == :dvd && !@file + errors << I18n.t("vagrant.config.disk.dvd_type_file_required", name: @name, machine: machine.name) + end if @file if !@file.is_a?(String) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 5d1029be5..d1e96b095 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -16,7 +16,8 @@ module VagrantPlugins return if !Vagrant::Util::Experimental.feature_enabled?("disks") handle_cleanup_disk(machine, defined_disks, disk_meta_file["disk"]) - # TODO: Floppy and DVD disks + handle_cleanup_dvd(machine, defined_disks, disk_meta_file["dvd"]) + # TODO: Floppy disks end protected @@ -28,23 +29,52 @@ module VagrantPlugins vm_info = machine.provider.driver.show_vm_info primary_disk = vm_info["SATA Controller-ImageUUID-0-0"] - disk_meta.each do |d| - dsk = defined_disks.select { |dk| dk.name == d["name"] } - if !dsk.empty? || d["uuid"] == primary_disk - next - else - 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(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.") + unless disk_meta.nil? + disk_meta.each do |d| + dsk = defined_disks.select { |dk| dk.name == d["name"] } + if !dsk.empty? || d["uuid"] == primary_disk + next else - machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device]) - end + 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.provider.driver.close_medium(d["uuid"]) + 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.") + else + machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device]) + end + + machine.provider.driver.close_medium(d["uuid"]) + end + end + end + end + + # @param [Vagrant::Machine] machine + # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds + # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action + def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) + controller = "IDE Controller" + vm_info = machine.provider.driver.show_vm_info + + unless dvd_meta.nil? + dvd_meta.each do |d| + dsk = defined_dvds.select { |dk| dk.name == d["name"] } + if !dsk.empty? + next + else + LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") + (0..1).each do |port| + (0..1).each do |device| + if d["uuid"] == vm_info["#{controller}-ImageUUID-#{port}-#{device}"] + machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) + machine.provider.driver.remove_disk(port.to_s, device.to_s, controller) + end + end + end + end end end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 5d6fe5f51..df6aa54e2 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -39,8 +39,8 @@ module VagrantPlugins # 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)) + dvd_data = handle_configure_dvd(machine, disk) + configured_disks[:dvd] << dvd_data unless dvd_data.empty? end end @@ -108,6 +108,45 @@ module VagrantPlugins disk_metadata end + # Helper method to get the UUID of a specific controller attachment + # + # @param [Vagrant::Machine] machine - the current machine + # @param [String] controller_name - the name of the controller to examine + # @param [String] port - port to look up + # @param [String] device - device to look up + def self.attachment(machine, controller_name, port, device) + vm_info = machine.provider.driver.show_vm_info + vm_info["#{controller_name}-ImageUUID-#{port}-#{device}"] + end + + # Handles all disk configs of type `:dvd` + # + # @param [Vagrant::Machine] machine - the current machine + # @param [Config::Disk] dvd - the current disk to configure + # @return [Hash] - dvd_metadata + def self.handle_configure_dvd(machine, dvd) + controller = "IDE Controller" + vm_info = machine.provider.driver.show_vm_info + + # Check if this DVD file is already attached + (0..1).each do |port| + (0..1).each do |device| + if vm_info["#{controller}-#{port}-#{device}"] == dvd.file + LOGGER.warn("DVD '#{dvd.file}' is already connected to guest '#{machine.name}', skipping...") + return {uuid: vm_info["#{controller}-ImageUUID-#{port}-#{device}"], name: dvd.name} + end + end + end + + # New attachment + disk_info = get_next_port(machine, controller) + machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") + + attachment_uuid = attachment(machine, controller, disk_info[:port], disk_info[:device]) + + {uuid: attachment_uuid, name: dvd.name} + end + # Check to see if current disk is configured based on defined_disks # # @param [Kernel_V2::VagrantConfigDisk] disk_config @@ -167,19 +206,38 @@ module VagrantPlugins # device = disk_info[3] # # @param [Vagrant::Machine] machine + # @param [String] controller name (defaults to "SATA Controller") # @return [Hash] dsk_info - The next available port and device on a given controller - def self.get_next_port(machine) + def self.get_next_port(machine, controller="SATA Controller") vm_info = machine.provider.driver.show_vm_info - dsk_info = {device: "0", port: "0"} + dsk_info = {} - disk_images = vm_info.select { |v| v.include?("ImageUUID") && v.include?("SATA Controller") } - used_ports = disk_images.keys.map { |k| k.split('-') }.map {|v| v[2].to_i} - next_available_port = ((0..(MAX_DISK_NUMBER-1)).to_a - used_ports).first + if controller == "SATA Controller" + disk_images = vm_info.select { |v| v.include?("ImageUUID") && v.include?(controller) } + used_ports = disk_images.keys.map { |k| k.split('-') }.map {|v| v[2].to_i} + next_available_port = ((0..(MAX_DISK_NUMBER-1)).to_a - used_ports).first - dsk_info[:port] = next_available_port.to_s - if dsk_info[:port].empty? + dsk_info[:port] = next_available_port.to_s + dsk_info[:device] = "0" + else + # IDE Controllers have primary/secondary devices, so find the first port + # with an empty device + (0..1).each do |port| + break if dsk_info[:port] && dsk_info[:device] + + (0..1).each do |device| + if vm_info["#{controller}-ImageUUID-#{port}-#{device}"].to_s.empty? + dsk_info[:port] = port.to_s + dsk_info[:device] = device.to_s + break + end + end + end + end + + if dsk_info[:port].to_s.empty? # This likely only occurs if additional disks have been added outside of Vagrant configuration - LOGGER.warn("There are no more available ports to attach disks to for the SATA Controller. Clear up some space on the SATA controller to attach new disks.") + LOGGER.warn("There are no more available ports to attach disks to for the controller '#{controller}'. Clear up some space on the controller '#{controller}' to attach new disks.") raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index b08576a8f..6de2552a8 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -29,13 +29,21 @@ module VagrantPlugins # @param [Hash] opts - additional options def attach_disk(port, device, file, type="hdd", **opts) # Maybe only support SATA Controller for `:disk`??? - controller = "SATA Controller" + if type == "hdd" + controller = "SATA Controller" + else + controller = "IDE Controller" + end comment = "This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant." - execute('storageattach', @uuid, '--storagectl', controller, '--port', - port.to_s, '--device', device.to_s, '--type', type, '--medium', - file, '--comment', comment) + execute('storageattach', @uuid, + '--storagectl', controller, + '--port', port.to_s, + '--device', device.to_s, + '--type', type, + '--medium', file, + '--comment', comment) end def clear_forwarded_ports @@ -227,9 +235,12 @@ module VagrantPlugins # @param [String] port - port on device to attach disk to # @param [String] device - device on controller for disk # @param [Hash] opts - additional options - def remove_disk(port, device) - controller = "SATA Controller" - execute('storageattach', @uuid, '--storagectl', controller, '--port', port.to_s, '--device', device.to_s, '--medium', "none") + def remove_disk(port, device, controller="SATA Controller") + execute('storageattach', @uuid, + '--storagectl', controller, + '--port', port.to_s, + '--device', device.to_s, + '--medium', "none") end # @param [String] disk_file diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 7ce0f57e3..89ec995aa 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1844,6 +1844,8 @@ en: #------------------------------------------------------------------------------- config: disk: + dvd_type_file_required: + A 'file' option is required when defining a disk of type `:dvd` for guest '%{machine}'. invalid_ext: |- Disk type '%{ext}' is not a valid disk extention for '%{name}'. Please pick one of the following supported disk types: %{exts} invalid_type: |- diff --git a/test/unit/plugins/kernel_v2/config/disk_test.rb b/test/unit/plugins/kernel_v2/config/disk_test.rb index 967a9046d..6b9790f1a 100644 --- a/test/unit/plugins/kernel_v2/config/disk_test.rb +++ b/test/unit/plugins/kernel_v2/config/disk_test.rb @@ -15,17 +15,16 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do let(:machine) { double("machine", name: "name", provider: provider, env: env, provider_name: :virtualbox) } - def assert_invalid errors = subject.validate(machine) - if !errors.empty? { |v| !v.empty? } + if errors.empty? raise "No errors: #{errors.inspect}" end end def assert_valid errors = subject.validate(machine) - if !errors.empty? { |v| v.empty? } + if !errors.empty? raise "Errors: #{errors.inspect}" end end @@ -52,7 +51,7 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do expect(subject.type).to eq(type) end - it "defaults to non-primray disk" do + it "defaults to non-primary disk" do subject.finalize! expect(subject.primary).to eq(false) end @@ -79,4 +78,26 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do end end end + + describe "config for dvd type" do + let(:iso_path) { "/tmp/untitled.iso" } + + before do + subject.type = :dvd + subject.name = "untitled" + end + + it "is valid with file path set" do + allow(File).to receive(:file?).with(iso_path).and_return(true) + subject.file = iso_path + subject.finalize! + assert_valid + end + + it "is invalid if file path is unset" do + subject.finalize! + errors = subject.validate(machine) + assert_invalid + end + end end diff --git a/test/unit/plugins/kernel_v2/config/vm_trigger_test.rb b/test/unit/plugins/kernel_v2/config/vm_trigger_test.rb index 6735e219f..53708cb0c 100644 --- a/test/unit/plugins/kernel_v2/config/vm_trigger_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_trigger_test.rb @@ -13,14 +13,14 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigTrigger do def assert_invalid errors = subject.validate(machine) - if !errors.empty? { |v| !v.empty? } + if errors.empty? raise "No errors: #{errors.inspect}" end end def assert_valid errors = subject.validate(machine) - if !errors.empty? { |v| v.empty? } + if !errors.empty? raise "Errors: #{errors.inspect}" end end diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 7a64c14d8..2f4317e1d 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -38,13 +38,13 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do allow(driver).to receive(:show_vm_info).and_return(vm_info) end - context "#cleanup_disks" do + describe "#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 + context "with disks to clean up" do let(:disk_meta_file) { {disk: [{uuid: "1234", name: "storage"}], floppy: [], dvd: []} } it "calls the cleanup method if a disk_meta file is defined" do @@ -55,9 +55,21 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do subject.cleanup_disks(machine, defined_disks, disk_meta_file) end end + + context "with dvd attached" do + let(:disk_meta_file) { {dvd: [{uuid: "12345", name: "iso"}]} } + + it "calls the cleanup method if a disk_meta file is defined" do + expect(subject).to receive(:handle_cleanup_dvd). + with(machine, defined_disks, disk_meta_file["dvd"]). + and_return(true) + + subject.cleanup_disks(machine, defined_disks, disk_meta_file) + end + end end - context "#handle_cleanup_disk" do + describe "#handle_cleanup_disk" do let(:disk_meta_file) { {disk: [{"uuid"=>"67890", "name"=>"storage"}], floppy: [], dvd: []} } let(:defined_disks) { [] } let(:device_info) { {port: "1", device: "0"} } @@ -73,7 +85,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) end - describe "when the disk isn't attached to a guest" do + context "when the disk isn't attached to a guest" do it "only closes the medium" do allow(driver).to receive(:get_port_and_device). with("67890"). @@ -85,4 +97,28 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end end end + + describe "#handle_cleanup_dvd" do + let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234" } } + let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso"}]} } + let(:defined_disks) { [] } + + it "removes the medium from guest" do + expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) + + subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) + end + + context "multiple copies of the same ISO attached" do + let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234", + "IDE Controller-ImageUUID-0-1" => "1234"} } + + it "removes all media with that UUID" do + expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) + expect(driver).to receive(:remove_disk).with("0", "1", "IDE Controller").and_return(true) + + subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) + end + end + end end diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 05ac77e68..53390f9f0 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -68,7 +68,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(driver).to receive(:show_vm_info).and_return(vm_info) end - context "#configure_disks" do + describe "#configure_disks" do let(:dsk_data) { {uuid: "1234", name: "disk"} } it "configures disks and returns the disks defined" do allow(driver).to receive(:list_hdds).and_return([]) @@ -77,14 +77,14 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do subject.configure_disks(machine, defined_disks) end - describe "with no disks to configure" do + context "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 - describe "with over the disk limit for a given device" do + context "with over the disk limit for a given device" do let(:defined_disks) { (1..31).each { |i| double("disk-#{i}") }.to_a } it "raises an exception if the disks defined exceed the limit for a SATA Controller" do @@ -92,9 +92,20 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) end end + + context "with dvd type" do + let(:defined_disks) { [double("dvd", type: :dvd)] } + let(:dvd_data) { {uuid: "1234", name: "dvd"} } + + it "handles configuration of the dvd" do + allow(driver).to receive(:list_hdds).and_return([]) + expect(subject).to receive(:handle_configure_dvd).and_return(dvd_data) + subject.configure_disks(machine, defined_disks) + end + end end - context "#get_current_disk" do + describe "#get_current_disk" do it "gets primary disk uuid if disk to configure is primary" do primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks) expect(primary_disk).to eq(all_disks.first) @@ -111,8 +122,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "#handle_configure_disk" do - describe "when creating a new disk" do + describe "#handle_configure_disk" do + context "when creating a new disk" do let(:all_disks) { [{"UUID"=>"12345", "Parent UUID"=>"base", "State"=>"created", @@ -133,7 +144,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - describe "when a disk needs to be resized" do + context "when a disk needs to be resized" do let(:all_disks) { [{"UUID"=>"12345", "Parent UUID"=>"base", "State"=>"created", @@ -167,7 +178,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - describe "if no additional disk configuration is required" do + context "if no additional disk configuration is required" do let(:all_disks) { [{"UUID"=>"12345", "Parent UUID"=>"base", "State"=>"created", @@ -221,7 +232,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "#compare_disk_size" do + describe "#compare_disk_size" do let(:disk_config_small) { double("disk", name: "disk-0", size: 1073741824.0, primary: false, type: :disk) } let(:disk_config_large) { double("disk", name: "disk-0", size: 68719476736.0, primary: false, type: :disk) } @@ -235,7 +246,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "#create_disk" do + describe "#create_disk" do let(:disk_config) { double("disk", name: "disk-0", size: 1073741824.0, primary: false, type: :disk, disk_ext: "vdi", provider_config: nil) } @@ -262,16 +273,39 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "#get_next_port" do - it "determines the next available port to use" do + describe ".get_next_port" do + it "determines the next available port and device to use" do dsk_info = subject.get_next_port(machine) - expect(dsk_info[:device]).to eq("0") expect(dsk_info[:port]).to eq("2") + expect(dsk_info[:device]).to eq("0") + end + + context "with an IDE controller" do + let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "00000aaaaa", + "IDE Controller-ImageUUID-0-1" => "11111bbbbb" } } + + it "determines the next available port and device to use" do + dsk_info = subject.get_next_port(machine, "IDE Controller") + expect(dsk_info[:port]).to eq("1") + expect(dsk_info[:device]).to eq("0") + end + end + + context "with a full IDE controller" do + let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "00000aaaaa", + "IDE Controller-ImageUUID-0-1" => "11111bbbbb", + "IDE Controller-ImageUUID-1-0" => "22222ccccc", + "IDE Controller-ImageUUID-1-1" => "33333ddddd"} } + + it "raises an error" do + expect { subject.get_next_port(machine, "IDE Controller") } + .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) + end end end - context "#resize_disk" do - describe "when a disk is vmdk format" do + describe "#resize_disk" do + context "when a disk is vmdk format" do let(:disk_config) { double("disk", name: "vagrant_primary", size: 1073741824.0, primary: false, type: :disk, disk_ext: "vmdk", provider_config: nil) } @@ -341,7 +375,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - describe "when a disk is vdi format" do + context "when a disk is vdi format" do let(:disk_config) { double("disk", name: "disk-0", size: 1073741824.0, primary: false, type: :disk, disk_ext: "vdi", provider_config: nil) } @@ -353,13 +387,44 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "#vmdk_to_vdi" do + describe "#vmdk_to_vdi" do it "converts a disk from vmdk to vdi" do end end - context "#vdi_to_vmdk" do + describe "#vdi_to_vmdk" do it "converts a disk from vdi to vmdk" do end end + + describe ".handle_configure_dvd" do + let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } + + before do + allow(subject).to receive(:get_next_port).with(machine, "IDE Controller").and_return({device: "0", port: "0"}) + end + + it "attaches to the next available port and device" do + expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") + + subject.handle_configure_dvd(machine, dvd_config) + end + + it "returns the UUID of the newly-attached dvd" do + allow(subject).to receive(:attachment).with(machine, "IDE Controller", "0", "0").and_return("12345") + expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") + + disk_meta = subject.handle_configure_dvd(machine, dvd_config) + expect(disk_meta[:uuid]).to eq("12345") + end + + context "when an ISO file is already attached" do + let(:vm_info) { {"IDE Controller-0-0" => "/tmp/untitled.iso" } } + + it "skips the attachment" do + expect(driver).not_to receive(:attach_disk) + subject.handle_configure_dvd(machine, dvd_config) + end + end + end end diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index d094028ca..5224800ab 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -90,4 +90,32 @@ OUTPUT end end end + + describe "#attach_disk" do + it "attaches a dvddrive device to the IDE controller" do + expect(subject).to receive(:execute) do |*args| + storagectl = args[args.index("--storagectl") + 1] + expect(storagectl).to eq("IDE Controller") + end + subject.attach_disk(anything, anything, anything, "dvddrive") + end + end + + describe "#remove_disk" do + it "removes a disk from the SATA Controller by default" do + expect(subject).to receive(:execute) do |*args| + storagectl = args[args.index("--storagectl") + 1] + expect(storagectl).to eq("SATA Controller") + end + subject.remove_disk(anything, anything) + end + + it "can remove a disk from the specified controller" do + expect(subject).to receive(:execute) do |*args| + storagectl = args[args.index("--storagectl") + 1] + expect(storagectl).to eq("IDE Controller") + end + subject.remove_disk(anything, anything, "IDE Controller") + end + end end From 8985369eabfd5132d8d253ef5627182731004371 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 15 May 2020 11:18:48 -0400 Subject: [PATCH 02/42] Raise an error if disk files are re-used in config --- plugins/kernel_v2/config/vm.rb | 7 +++++++ templates/locales/en.yml | 6 ++++++ test/unit/plugins/kernel_v2/config/vm_test.rb | 16 ++++++++++++++++ 3 files changed, 29 insertions(+) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 746896190..cd9e2e4d9 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -914,6 +914,13 @@ module VagrantPlugins name: duplicate_names) end + disk_files = @disks.select { |d| d.type == :disk }.map { |d| d.file } + duplicate_files = disk_files.detect { |d| disk_files.count(d) > 1 } + if duplicate_files && duplicate_files.size + errors << I18n.t("vagrant.config.vm.multiple_disk_files_error", + file: duplicate_files) + end + @disks.each do |d| error = d.validate(machine) errors.concat error if !error.empty? diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 89ec995aa..9ec834609 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1972,6 +1972,12 @@ en: Ignoring provider config for validation... multiple_primary_disks_error: |- There are more than one primary disks defined for guest '%{name}'. Please ensure that only one disk has been defined as a primary disk. + multiple_disk_files_error: |- + The following disk file is used multiple times in the disk configuration: + + %{file} + + Each disk file may only be attached to a VM once. multiple_disk_names_error: |- Duplicate disk names defined: '%{name}'. Disk names must be unique. multiple_networks_set_hostname: "Multiple networks have set `:hostname`" diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index c6576df6a..952fc2d61 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -622,6 +622,22 @@ describe VagrantPlugins::Kernel_V2::VMConfig do assert_invalid end + it "raises an error with duplicate disk files" do + allow(File).to receive(:file?).with("bar.vmdk").and_return(true) + subject.disk(:disk, size: 100, name: "foo1", file: "bar.vmdk") + subject.disk(:disk, size: 100, name: "foo2", file: "bar.vmdk") + subject.finalize! + assert_invalid + end + + it "does not raise an error with duplicate dvd files" do + allow(File).to receive(:file?).with("bar.iso").and_return(true) + subject.disk(:dvd, name: "foo1", file: "bar.iso") + subject.disk(:dvd, name: "foo2", file: "bar.iso") + subject.finalize! + assert_valid + end + it "does not merge duplicate disks" do subject.disk(:disk, size: 1000, primary: false, name: "storage") subject.disk(:disk, size: 1000, primary: false, name: "backup") From 8407d79100af36a1fdd2f567b6de4b1434eded26 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 27 May 2020 12:28:47 -0400 Subject: [PATCH 03/42] Refactor disk configuration to use dynamic names This commit adds a new VirtualBox provider helper method to return a list of storage controllers so Vagrant can find a storage controller with the desired characteristics (type IDE or SATA). This still needs to get wired up to the disk cleanup method. --- .../virtualbox/cap/configure_disks.rb | 86 +++++++++++-------- .../virtualbox/driver/version_5_0.rb | 29 +++++++ .../virtualbox/cap/cleanup_disks_test.rb | 4 + .../virtualbox/cap/configure_disks_test.rb | 56 ++++++++---- .../virtualbox/driver/version_5_0_test.rb | 25 ++++++ 5 files changed, 146 insertions(+), 54 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index df6aa54e2..bef7053aa 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -9,8 +9,8 @@ module VagrantPlugins module ConfigureDisks LOGGER = Log4r::Logger.new("vagrant::plugins::virtualbox::configure_disks") - # The max amount of disks that can be attached to a single device in a controller - MAX_DISK_NUMBER = 30.freeze + SATA_CONTROLLER_TYPES = ["IntelAhci"].freeze + IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].freeze # @param [Vagrant::Machine] machine # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks @@ -20,8 +20,15 @@ module VagrantPlugins return {} if !Vagrant::Util::Experimental.feature_enabled?("disks") - if defined_disks.size > MAX_DISK_NUMBER - # you can only attach up to 30 disks per controller, INCLUDING the primary disk + disks_defined = defined_disks.select { |d| d.type == :disk } + controller = sata_controller(machine) + if disks_defined.any? && controller && disks_defined.size > controller[:maxportcount] + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + end + + dvds_defined = defined_disks.select { |d| d.type == :dvd } + controller = ide_controller(machine) + if dvds_defined.any? && controller && dvds_defined.size > controller[:maxportcount] raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end @@ -59,8 +66,9 @@ module VagrantPlugins # 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 is always Port 0 Device 0. - vm_info = machine.provider.driver.show_vm_info - primary_uuid = vm_info["SATA Controller-ImageUUID-0-0"] + controller = sata_controller(machine) + primary = controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" } + primary_uuid = primary[:uuid] current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first else @@ -108,6 +116,14 @@ module VagrantPlugins disk_metadata end + def self.sata_controller(machine) + machine.provider.driver.storage_controllers.detect { |c| SATA_CONTROLLER_TYPES.include?(c[:type]) } + end + + def self.ide_controller(machine) + machine.provider.driver.storage_controllers.detect { |c| IDE_CONTROLLER_TYPES.include?(c[:type]) } + end + # Helper method to get the UUID of a specific controller attachment # # @param [Vagrant::Machine] machine - the current machine @@ -125,26 +141,21 @@ module VagrantPlugins # @param [Config::Disk] dvd - the current disk to configure # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd) - controller = "IDE Controller" - vm_info = machine.provider.driver.show_vm_info + controller = ide_controller(machine) - # Check if this DVD file is already attached - (0..1).each do |port| - (0..1).each do |device| - if vm_info["#{controller}-#{port}-#{device}"] == dvd.file - LOGGER.warn("DVD '#{dvd.file}' is already connected to guest '#{machine.name}', skipping...") - return {uuid: vm_info["#{controller}-ImageUUID-#{port}-#{device}"], name: dvd.name} - end - end - end - - # New attachment - disk_info = get_next_port(machine, controller) + disk_info = get_next_port(machine, controller[:name]) machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") - attachment_uuid = attachment(machine, controller, disk_info[:port], disk_info[:device]) + # Refresh the controller information + controller = ide_controller(machine) + attachment = controller[:attachments].detect { |c| c[:port] == disk_info[:port] && + c[:device] == disk_info[:device] } - {uuid: attachment_uuid, name: dvd.name} + if attachment + {uuid: attachment[:uuid], name: dvd.name} + else + {} + end end # Check to see if current disk is configured based on defined_disks @@ -208,29 +219,32 @@ module VagrantPlugins # @param [Vagrant::Machine] machine # @param [String] controller name (defaults to "SATA Controller") # @return [Hash] dsk_info - The next available port and device on a given controller - def self.get_next_port(machine, controller="SATA Controller") - vm_info = machine.provider.driver.show_vm_info + def self.get_next_port(machine, controller_name="SATA Controller") + controller = machine.provider.driver.storage_controllers.detect { |c| c[:name] == controller_name } + # definitely need an error for this dsk_info = {} - if controller == "SATA Controller" - disk_images = vm_info.select { |v| v.include?("ImageUUID") && v.include?(controller) } - used_ports = disk_images.keys.map { |k| k.split('-') }.map {|v| v[2].to_i} - next_available_port = ((0..(MAX_DISK_NUMBER-1)).to_a - used_ports).first + if controller[:type] == "IntelAhci" # SATA Controller + used_ports = controller[:attachments].map { |a| a[:port].to_i } + next_available_port = ((0..(controller[:maxportcount]-1)).to_a - used_ports).first dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" else # IDE Controllers have primary/secondary devices, so find the first port # with an empty device - (0..1).each do |port| - break if dsk_info[:port] && dsk_info[:device] + (0..(controller[:maxportcount]-1)).each do |port| + # Skip this port if it's full + port_attachments = controller[:attachments].select { |a| a[:port] == port.to_s } + next if port_attachments.count == 2 - (0..1).each do |device| - if vm_info["#{controller}-ImageUUID-#{port}-#{device}"].to_s.empty? - dsk_info[:port] = port.to_s - dsk_info[:device] = device.to_s - break - end + dsk_info[:port] = port.to_s + + # Check for a free device + if port_attachments.detect { |a| a[:device] == "0" } + dsk_info[:device] = "1" + else + dsk_info[:device] = "0" end end end diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 6de2552a8..25bf388e5 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -934,6 +934,35 @@ module VagrantPlugins destination end + # Helper method to get a list of storage controllers added to the + # current VM + def storage_controllers + vm_info = show_vm_info + count = vm_info.count { |key, value| key.match(/^storagecontrollername/) } + + (0..count - 1).map do |n| + # basic controller metadata + name = vm_info["storagecontrollername#{n}"] + type = vm_info["storagecontrollertype#{n}"] + maxportcount = vm_info["storagecontrollermaxportcount#{n}"].to_i + + # build attachments array + attachments = [] + vm_info.each do |k, v| + if /^#{name}-ImageUUID-(\d+)-(\d+)$/ =~ k + attachments << {port: $1.to_s, device: $2.to_s, uuid: v} + end + end + + { + name: name, + type: type, + maxportcount: maxportcount, + attachments: attachments + } + end + end + protected def valid_ip_address?(ip) diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 2f4317e1d..918846cf7 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -33,9 +33,13 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:vm_info) { {"SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } + let(:storage_controllers) { + } + before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) + allow(driver).to receive(:storage_controllers).and_return(storage_controllers) end describe "#cleanup_disks" do diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 53390f9f0..0e250128b 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -25,9 +25,18 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do double(:state) end - let(:vm_info) { {"SATA Controller-ImageUUID-0-0" => "12345", + let(:vm_info) { {"storagecontrollername0" => "SATA Controller", + "storagecontrollertype0" => "IntelAhci", + "storagecontrollermaxportcount0" => "30", + "SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } + let(:storage_controllers) { [{name: "SATA Controller", + type: "IntelAhci", + maxportcount: 30, + attachments: [{port: "0", device: "0", uuid: "12345"}, + {port: "1", device: "0", uuid: "67890"}]}] } + 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), @@ -66,6 +75,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) + allow(driver).to receive(:storage_controllers).and_return(storage_controllers) end describe "#configure_disks" do @@ -85,7 +95,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with over the disk limit for a given device" do - let(:defined_disks) { (1..31).each { |i| double("disk-#{i}") }.to_a } + let(:defined_disks) { (1..31).map { |i| double("disk-#{i}", type: :disk) }.to_a } it "raises an exception if the disks defined exceed the limit for a SATA Controller" do expect{subject.configure_disks(machine, defined_disks)}. @@ -281,8 +291,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with an IDE controller" do - let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "00000aaaaa", - "IDE Controller-ImageUUID-0-1" => "11111bbbbb" } } + let(:storage_controllers) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: [{port: "0", device: "0", uuid: "12345"}, + {port: "0", device: "1", uuid: "67890"}]}] } it "determines the next available port and device to use" do dsk_info = subject.get_next_port(machine, "IDE Controller") @@ -292,10 +305,13 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with a full IDE controller" do - let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "00000aaaaa", - "IDE Controller-ImageUUID-0-1" => "11111bbbbb", - "IDE Controller-ImageUUID-1-0" => "22222ccccc", - "IDE Controller-ImageUUID-1-1" => "33333ddddd"} } + let(:storage_controllers) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: [{port: "0", device: "0", uuid: "11111"}, + {port: "0", device: "1", uuid: "22222"}, + {port: "1", device: "0", uuid: "33333"}, + {port: "1", device: "1", uuid: "44444"}]}] } it "raises an error" do expect { subject.get_next_port(machine, "IDE Controller") } @@ -399,9 +415,19 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe ".handle_configure_dvd" do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } + let(:storage_controllers) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: []}] } + + let(:single_attachment) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: [port: "0", device: "0", uuid: "12345"]}] } before do allow(subject).to receive(:get_next_port).with(machine, "IDE Controller").and_return({device: "0", port: "0"}) + allow(driver).to receive(:storage_controllers).and_return(storage_controllers) end it "attaches to the next available port and device" do @@ -411,20 +437,14 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end it "returns the UUID of the newly-attached dvd" do - allow(subject).to receive(:attachment).with(machine, "IDE Controller", "0", "0").and_return("12345") + expect(driver).to receive(:storage_controllers).and_return( + storage_controllers, + single_attachment + ) expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") disk_meta = subject.handle_configure_dvd(machine, dvd_config) expect(disk_meta[:uuid]).to eq("12345") end - - context "when an ISO file is already attached" do - let(:vm_info) { {"IDE Controller-0-0" => "/tmp/untitled.iso" } } - - it "skips the attachment" do - expect(driver).not_to receive(:attach_disk) - subject.handle_configure_dvd(machine, dvd_config) - end - end end end diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index 5224800ab..c3b413206 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -118,4 +118,29 @@ OUTPUT subject.remove_disk(anything, anything, "IDE Controller") end end + + describe "#storage_controllers" do + before do + allow(subject).to receive(:show_vm_info).and_return( + {"storagecontrollername0" => "SATA Controller", + "storagecontrollertype0" => "IntelAhci", + "storagecontrollermaxportcount0" => "30", + "SATA Controller-ImageUUID-0-0" => "12345", + "SATA Controller-ImageUUID-1-0" => "67890"} + ) + end + + it "returns the storage controllers" do + expect(subject.storage_controllers.first[:name]).to eq("SATA Controller") + expect(subject.storage_controllers.first[:type]).to eq("IntelAhci") + expect(subject.storage_controllers.first[:maxportcount]).to eq(30) + end + + it "includes attachments for each storage controller" do + expect(subject.storage_controllers.first[:attachments]) + .to include(port: "0", device: "0", uuid: "12345") + expect(subject.storage_controllers.first[:attachments]) + .to include(port: "1", device: "0", uuid: "67890") + end + end end From 8739c04aa97db1e3a1166f5a1cc101dfcfddd50e Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 28 May 2020 11:28:36 -0400 Subject: [PATCH 04/42] Mock out storage controllers --- .../providers/virtualbox/cap/cleanup_disks.rb | 4 ++-- .../virtualbox/cap/cleanup_disks_test.rb | 17 +++++++++++++++-- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index d1e96b095..4c215118e 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -26,8 +26,8 @@ module VagrantPlugins # @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) - vm_info = machine.provider.driver.show_vm_info - primary_disk = vm_info["SATA Controller-ImageUUID-0-0"] + storage_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "IntelAhci" } + primary_disk = storage_controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] unless disk_meta.nil? disk_meta.each do |d| diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 918846cf7..3b92bf683 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -33,8 +33,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:vm_info) { {"SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:storage_controllers) { - } + let(:storage_controllers) { [{name: "SATA Controller", + type: "IntelAhci", + maxportcount: 30, + attachments: [{port: "0", device: "0", uuid: "12345"}, + {port: "1", device: "0", uuid: "67890"}]}] } before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) @@ -104,6 +107,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do describe "#handle_cleanup_dvd" do let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234" } } + let(:storage_controllers) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: [{port: "0", device: "0", uuid: "1234"}]}] } let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso"}]} } let(:defined_disks) { [] } @@ -117,6 +124,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234", "IDE Controller-ImageUUID-0-1" => "1234"} } + let(:storage_controllers) { [{name: "IDE Controller", + type: "PIIX4", + maxportcount: 2, + attachments: [{port: "0", device: "0", uuid: "1234"}, + {port: "0", device: "1", uuid: "1234"}]}] } + it "removes all media with that UUID" do expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) expect(driver).to receive(:remove_disk).with("0", "1", "IDE Controller").and_return(true) From 2e879aa06f9b69a850bd9d7476540d63dd07c554 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 28 May 2020 11:37:31 -0400 Subject: [PATCH 05/42] Scan storage controllers for DVD cleanup --- .../providers/virtualbox/cap/cleanup_disks.rb | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 4c215118e..8467d6f46 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -26,8 +26,8 @@ module VagrantPlugins # @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) - storage_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "IntelAhci" } - primary_disk = storage_controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] + sata_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "IntelAhci" } + primary_disk = sata_controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] unless disk_meta.nil? disk_meta.each do |d| @@ -43,6 +43,7 @@ module VagrantPlugins if disk_info.empty? LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else + # TODO: write test for sata controller with another name machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device]) end @@ -56,8 +57,7 @@ module VagrantPlugins # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) - controller = "IDE Controller" - vm_info = machine.provider.driver.show_vm_info + ide_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "PIIX4" } unless dvd_meta.nil? dvd_meta.each do |d| @@ -66,13 +66,10 @@ module VagrantPlugins next else LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") - (0..1).each do |port| - (0..1).each do |device| - if d["uuid"] == vm_info["#{controller}-ImageUUID-#{port}-#{device}"] - machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - machine.provider.driver.remove_disk(port.to_s, device.to_s, controller) - end - end + attachments = ide_controller[:attachments].select { |a| a[:uuid] == d["uuid"] } + attachments.each do |attachment| + machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) + machine.provider.driver.remove_disk(attachment[:port].to_s, attachment[:device].to_s, ide_controller[:name]) end end end From 61f59de69b9c24f4a17a8ce7c51c7e06a2c25b35 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 29 May 2020 10:12:04 -0400 Subject: [PATCH 06/42] Extract StorageController object --- .../providers/virtualbox/cap/cleanup_disks.rb | 10 +-- .../virtualbox/cap/configure_disks.rb | 58 +++++------------ .../virtualbox/driver/version_5_0.rb | 9 +-- .../virtualbox/model/storage_controller.rb | 53 +++++++++++++++ plugins/providers/virtualbox/plugin.rb | 1 + .../virtualbox/cap/cleanup_disks_test.rb | 30 ++++----- .../virtualbox/cap/configure_disks_test.rb | 65 +++++++------------ .../models/storage_controller_test.rb | 28 ++++++++ 8 files changed, 144 insertions(+), 110 deletions(-) create mode 100644 plugins/providers/virtualbox/model/storage_controller.rb create mode 100644 test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 8467d6f46..c02820d7b 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -26,8 +26,8 @@ module VagrantPlugins # @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) - sata_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "IntelAhci" } - primary_disk = sata_controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] + controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + primary_disk = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] unless disk_meta.nil? disk_meta.each do |d| @@ -57,7 +57,7 @@ module VagrantPlugins # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) - ide_controller = machine.provider.driver.storage_controllers.detect { |c| c[:type] == "PIIX4" } + controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } unless dvd_meta.nil? dvd_meta.each do |d| @@ -66,10 +66,10 @@ module VagrantPlugins next else LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") - attachments = ide_controller[:attachments].select { |a| a[:uuid] == d["uuid"] } + attachments = controller.attachments.select { |a| a[:uuid] == d["uuid"] } attachments.each do |attachment| machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - machine.provider.driver.remove_disk(attachment[:port].to_s, attachment[:device].to_s, ide_controller[:name]) + machine.provider.driver.remove_disk(attachment[:port].to_s, attachment[:device].to_s, controller.name) end end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index bef7053aa..8cfa9cd96 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -9,9 +9,6 @@ module VagrantPlugins module ConfigureDisks LOGGER = Log4r::Logger.new("vagrant::plugins::virtualbox::configure_disks") - SATA_CONTROLLER_TYPES = ["IntelAhci"].freeze - IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].freeze - # @param [Vagrant::Machine] machine # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks # @return [Hash] configured_disks - A hash of all the current configured disks @@ -21,14 +18,14 @@ module VagrantPlugins return {} if !Vagrant::Util::Experimental.feature_enabled?("disks") disks_defined = defined_disks.select { |d| d.type == :disk } - controller = sata_controller(machine) - if disks_defined.any? && controller && disks_defined.size > controller[:maxportcount] + controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + if disks_defined.any? && controller && disks_defined.size > controller.maxportcount raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end dvds_defined = defined_disks.select { |d| d.type == :dvd } - controller = ide_controller(machine) - if dvds_defined.any? && controller && dvds_defined.size > controller[:maxportcount] + controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } + if dvds_defined.any? && controller && dvds_defined.size > controller.maxportcount raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end @@ -66,8 +63,8 @@ module VagrantPlugins # 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 is always Port 0 Device 0. - controller = sata_controller(machine) - primary = controller[:attachments].detect { |a| a[:port] == "0" && a[:device] == "0" } + controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } primary_uuid = primary[:uuid] current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first @@ -116,40 +113,21 @@ module VagrantPlugins disk_metadata end - def self.sata_controller(machine) - machine.provider.driver.storage_controllers.detect { |c| SATA_CONTROLLER_TYPES.include?(c[:type]) } - end - - def self.ide_controller(machine) - machine.provider.driver.storage_controllers.detect { |c| IDE_CONTROLLER_TYPES.include?(c[:type]) } - end - - # Helper method to get the UUID of a specific controller attachment - # - # @param [Vagrant::Machine] machine - the current machine - # @param [String] controller_name - the name of the controller to examine - # @param [String] port - port to look up - # @param [String] device - device to look up - def self.attachment(machine, controller_name, port, device) - vm_info = machine.provider.driver.show_vm_info - vm_info["#{controller_name}-ImageUUID-#{port}-#{device}"] - end - # Handles all disk configs of type `:dvd` # # @param [Vagrant::Machine] machine - the current machine # @param [Config::Disk] dvd - the current disk to configure # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd) - controller = ide_controller(machine) + controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } - disk_info = get_next_port(machine, controller[:name]) + disk_info = get_next_port(machine, controller.name) machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") # Refresh the controller information - controller = ide_controller(machine) - attachment = controller[:attachments].detect { |c| c[:port] == disk_info[:port] && - c[:device] == disk_info[:device] } + controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } + attachment = controller.attachments.detect { |a| a[:port] == disk_info[:port] && + a[:device] == disk_info[:device] } if attachment {uuid: attachment[:uuid], name: dvd.name} @@ -220,22 +198,22 @@ module VagrantPlugins # @param [String] controller name (defaults to "SATA Controller") # @return [Hash] dsk_info - The next available port and device on a given controller def self.get_next_port(machine, controller_name="SATA Controller") - controller = machine.provider.driver.storage_controllers.detect { |c| c[:name] == controller_name } - # definitely need an error for this + controller = machine.provider.driver.storage_controllers.detect { |c| c.name == controller_name } + # TODO: definitely need an error for this dsk_info = {} - if controller[:type] == "IntelAhci" # SATA Controller - used_ports = controller[:attachments].map { |a| a[:port].to_i } - next_available_port = ((0..(controller[:maxportcount]-1)).to_a - used_ports).first + if controller.sata_controller? + used_ports = controller.attachments.map { |a| a[:port].to_i } + next_available_port = ((0..(controller.maxportcount-1)).to_a - used_ports).first dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" else # IDE Controllers have primary/secondary devices, so find the first port # with an empty device - (0..(controller[:maxportcount]-1)).each do |port| + (0..(controller.maxportcount-1)).each do |port| # Skip this port if it's full - port_attachments = controller[:attachments].select { |a| a[:port] == port.to_s } + port_attachments = controller.attachments.select { |a| a[:port] == port.to_s } next if port_attachments.count == 2 dsk_info[:port] = port.to_s diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 25bf388e5..9030b893d 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -936,6 +936,8 @@ module VagrantPlugins # Helper method to get a list of storage controllers added to the # current VM + # + # @return [Array] def storage_controllers vm_info = show_vm_info count = vm_info.count { |key, value| key.match(/^storagecontrollername/) } @@ -954,12 +956,7 @@ module VagrantPlugins end end - { - name: name, - type: type, - maxportcount: maxportcount, - attachments: attachments - } + Model::StorageController.new(name, type, maxportcount, attachments) end end diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb new file mode 100644 index 000000000..a1b7e1280 --- /dev/null +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -0,0 +1,53 @@ +module VagrantPlugins + module ProviderVirtualBox + module Model + # Represents a storage controller for VirtualBox. Storage controllers + # have a type, a name, and can have hard disks or optical attached. + class StorageController + + SATA_CONTROLLER_TYPES = ["IntelAhci"].freeze + IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].freeze + + # The name of the storage controller. + # + # @return [String] + attr_reader :name + + # The specific type of controller. + # + # @return [String] + attr_reader :type + + # The maximum number of avilable ports for the storage controller. For + # SATA controllers, this indicates the number of disks that can be + # attached. For IDE controllers, this indicates that n*2 disks can be + # attached (primary/secondary). + # + # @return [Integer] + attr_reader :maxportcount + + # The list of disks/ISOs attached to each storage controller. + # + # @return [Array] + attr_reader :attachments + + def initialize(name, type, maxportcount, attachments) + @name = name + @type = type + @maxportcount = maxportcount.to_i + + attachments ||= [] + @attachments = attachments + end + + def sata_controller? + SATA_CONTROLLER_TYPES.include?(@type) + end + + def ide_controller? + IDE_CONTROLLER_TYPES.include?(@type) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index 63f6a0e21..eb62b009c 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -89,6 +89,7 @@ module VagrantPlugins module Model autoload :ForwardedPort, File.expand_path("../model/forwarded_port", __FILE__) + autoload :StorageController, File.expand_path("../model/storage_controller", __FILE__) end module Util diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 3b92bf683..e97ebba84 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -33,16 +33,18 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:vm_info) { {"SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:storage_controllers) { [{name: "SATA Controller", - type: "IntelAhci", - maxportcount: 30, - attachments: [{port: "0", device: "0", uuid: "12345"}, - {port: "1", device: "0", uuid: "67890"}]}] } + let(:controller) { double("controller", name: "SATA Controller", maxportcount: 30, sata_controller?: true, ide_controller?: false) } + + let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, + {port: "1", device: "0", uuid: "67890"}]} + + let(:storage_controllers) { [ controller ] } before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) allow(driver).to receive(:storage_controllers).and_return(storage_controllers) + allow(controller).to receive(:attachments).and_return(attachments) end describe "#cleanup_disks" do @@ -107,10 +109,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do describe "#handle_cleanup_dvd" do let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234" } } - let(:storage_controllers) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: [{port: "0", device: "0", uuid: "1234"}]}] } + + let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } + let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } + let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso"}]} } let(:defined_disks) { [] } @@ -121,14 +123,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end context "multiple copies of the same ISO attached" do - let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234", - "IDE Controller-ImageUUID-0-1" => "1234"} } - - let(:storage_controllers) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: [{port: "0", device: "0", uuid: "1234"}, - {port: "0", device: "1", uuid: "1234"}]}] } + let(:attachments) { [{port: "0", device: "0", uuid: "1234"}, + {port: "0", device: "1", uuid: "1234"}] } it "removes all media with that UUID" do expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 0e250128b..1da64890b 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -31,11 +31,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do "SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:storage_controllers) { [{name: "SATA Controller", - type: "IntelAhci", - maxportcount: 30, - attachments: [{port: "0", device: "0", uuid: "12345"}, - {port: "1", device: "0", uuid: "67890"}]}] } + let(:controller) { double("controller", name: "SATA Controller", maxportcount: 30, sata_controller?: true, ide_controller?: false) } + + let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, + {port: "1", device: "0", uuid: "67890"}]} + + let(:storage_controllers) { [ controller ] } 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), @@ -76,6 +77,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) allow(driver).to receive(:storage_controllers).and_return(storage_controllers) + allow(controller).to receive(:attachments).and_return(attachments) end describe "#configure_disks" do @@ -291,31 +293,26 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with an IDE controller" do - let(:storage_controllers) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: [{port: "0", device: "0", uuid: "12345"}, - {port: "0", device: "1", uuid: "67890"}]}] } + let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } + let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, + {port: "0", device: "1", uuid: "67890"}] } it "determines the next available port and device to use" do dsk_info = subject.get_next_port(machine, "IDE Controller") expect(dsk_info[:port]).to eq("1") expect(dsk_info[:device]).to eq("0") end - end - context "with a full IDE controller" do - let(:storage_controllers) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: [{port: "0", device: "0", uuid: "11111"}, - {port: "0", device: "1", uuid: "22222"}, - {port: "1", device: "0", uuid: "33333"}, - {port: "1", device: "1", uuid: "44444"}]}] } + context "that is full" do + let(:attachments) { [{port: "0", device: "0", uuid: "11111"}, + {port: "0", device: "1", uuid: "22222"}, + {port: "1", device: "0", uuid: "33333"}, + {port: "1", device: "1", uuid: "44444"}] } - it "raises an error" do - expect { subject.get_next_port(machine, "IDE Controller") } - .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) + it "raises an error" do + expect { subject.get_next_port(machine, "IDE Controller") } + .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) + end end end end @@ -415,32 +412,16 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe ".handle_configure_dvd" do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } - let(:storage_controllers) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: []}] } - - let(:single_attachment) { [{name: "IDE Controller", - type: "PIIX4", - maxportcount: 2, - attachments: [port: "0", device: "0", uuid: "12345"]}] } + let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } before do allow(subject).to receive(:get_next_port).with(machine, "IDE Controller").and_return({device: "0", port: "0"}) - allow(driver).to receive(:storage_controllers).and_return(storage_controllers) - end - - it "attaches to the next available port and device" do - expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") - - subject.handle_configure_dvd(machine, dvd_config) + allow(controller).to receive(:attachments).and_return( + [port: "0", device: "0", uuid: "12345"] + ) end it "returns the UUID of the newly-attached dvd" do - expect(driver).to receive(:storage_controllers).and_return( - storage_controllers, - single_attachment - ) expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") disk_meta = subject.handle_configure_dvd(machine, dvd_config) diff --git a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb new file mode 100644 index 000000000..897722352 --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb @@ -0,0 +1,28 @@ +require File.expand_path("../../base", __FILE__) + +describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do + include_context "unit" + + let(:name) {} + let(:type) {} + let(:maxportcount) {} + let(:attachments) {} + + subject { described_class.new(name, type, maxportcount, attachments) } + + describe "#sata_controller?" do + let(:type) { "IntelAhci" } + + it "is true for a SATA type" do + expect(subject.sata_controller?).to be(true) + end + end + + describe "#ide_controller?" do + let(:type) { "PIIX4" } + + it "is true for an IDE type" do + expect(subject.ide_controller?).to be(true) + end + end +end From 5abde797e48e7b44d3d2c18eafd2003d2f5ba540 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 1 Jun 2020 10:57:52 -0400 Subject: [PATCH 07/42] Forward #storage_controller to driver --- plugins/providers/virtualbox/driver/meta.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index 7175112ba..f6f1ad161 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -146,6 +146,7 @@ module VagrantPlugins :share_folders, :ssh_port, :start, + :storage_controllers, :suspend, :vdi_to_vmdk, :verify!, From 38e986300b6e2cb0384ecb938e22b67351770efe Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 1 Jun 2020 11:00:30 -0400 Subject: [PATCH 08/42] Fix up tests to use new interface --- .../providers/virtualbox/driver/version_5_0_test.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index c3b413206..b6b23e0d2 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -131,15 +131,15 @@ OUTPUT end it "returns the storage controllers" do - expect(subject.storage_controllers.first[:name]).to eq("SATA Controller") - expect(subject.storage_controllers.first[:type]).to eq("IntelAhci") - expect(subject.storage_controllers.first[:maxportcount]).to eq(30) + expect(subject.storage_controllers.first.name).to eq("SATA Controller") + expect(subject.storage_controllers.first.type).to eq("IntelAhci") + expect(subject.storage_controllers.first.maxportcount).to eq(30) end it "includes attachments for each storage controller" do - expect(subject.storage_controllers.first[:attachments]) + expect(subject.storage_controllers.first.attachments) .to include(port: "0", device: "0", uuid: "12345") - expect(subject.storage_controllers.first[:attachments]) + expect(subject.storage_controllers.first.attachments) .to include(port: "1", device: "0", uuid: "67890") end end From 4480eb0d88e3f0815f835f8a1efa06a821c7fb73 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 1 Jun 2020 15:22:28 -0400 Subject: [PATCH 09/42] Freeze values --- plugins/providers/virtualbox/model/storage_controller.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index a1b7e1280..7343ec9de 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -2,11 +2,11 @@ module VagrantPlugins module ProviderVirtualBox module Model # Represents a storage controller for VirtualBox. Storage controllers - # have a type, a name, and can have hard disks or optical attached. + # have a type, a name, and can have hard disks or optical drives attached. class StorageController - SATA_CONTROLLER_TYPES = ["IntelAhci"].freeze - IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].freeze + SATA_CONTROLLER_TYPES = ["IntelAhci"].map(&:freeze).freeze + IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].map(&:freeze).freeze # The name of the storage controller. # From e21fb59380c9a9fcee892b3f8a69144382a35842 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 1 Jun 2020 15:43:39 -0400 Subject: [PATCH 10/42] DVD attachments should never be primary --- plugins/kernel_v2/config/disk.rb | 4 ++++ templates/locales/en.yml | 3 +++ test/unit/plugins/kernel_v2/config/disk_test.rb | 14 ++++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/plugins/kernel_v2/config/disk.rb b/plugins/kernel_v2/config/disk.rb index 1ccbb63e7..41cd65cdd 100644 --- a/plugins/kernel_v2/config/disk.rb +++ b/plugins/kernel_v2/config/disk.rb @@ -180,6 +180,10 @@ module VagrantPlugins errors << I18n.t("vagrant.config.disk.dvd_type_file_required", name: @name, machine: machine.name) end + if @type == :dvd && @primary + errors << I18n.t("vagrant.config.disk.dvd_type_primary", 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) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 9ec834609..071b3fa94 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1846,6 +1846,9 @@ en: disk: dvd_type_file_required: A 'file' option is required when defining a disk of type `:dvd` for guest '%{machine}'. + dvd_type_primary: |- + Disks of type ':dvd' cannot be defined as a primary disks. Please + remove the 'primary' argument for disk '%{name}' on guest '%{machine}'. invalid_ext: |- Disk type '%{ext}' is not a valid disk extention for '%{name}'. Please pick one of the following supported disk types: %{exts} invalid_type: |- diff --git a/test/unit/plugins/kernel_v2/config/disk_test.rb b/test/unit/plugins/kernel_v2/config/disk_test.rb index 6b9790f1a..e986bdae0 100644 --- a/test/unit/plugins/kernel_v2/config/disk_test.rb +++ b/test/unit/plugins/kernel_v2/config/disk_test.rb @@ -85,18 +85,24 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do before do subject.type = :dvd subject.name = "untitled" - end - - it "is valid with file path set" do allow(File).to receive(:file?).with(iso_path).and_return(true) subject.file = iso_path + end + + it "is valid with test defaults" do subject.finalize! assert_valid end it "is invalid if file path is unset" do + subject.file = nil + subject.finalize! + assert_invalid + end + + it "is invalid if primary" do + subject.primary = true subject.finalize! - errors = subject.validate(machine) assert_invalid end end From 3a515cc7d6c82ac488da8e2a757f8a70e82eb278 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 1 Jun 2020 17:24:08 -0400 Subject: [PATCH 11/42] Error if the required storage controller not found This commit adds a new error type that can be raised whenever a storage controller of the required type is not found. This indicates that a user needs to either add the storage controller manually or change their disk configuration. It also removes the last hardcoded instance of "SATA Controller" as a default argument. --- lib/vagrant/errors.rb | 4 ++ .../providers/virtualbox/cap/cleanup_disks.rb | 2 +- .../virtualbox/cap/configure_disks.rb | 27 +++++--- templates/locales/en.yml | 4 ++ .../virtualbox/cap/configure_disks_test.rb | 64 +++++++++++++++---- 5 files changed, 78 insertions(+), 23 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 910f64098..619803022 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -932,6 +932,10 @@ module Vagrant error_key(:virtualbox_disks_defined_exceed_limit) end + class VirtualBoxDisksControllerNotFound < VagrantError + error_key(:virtualbox_disks_controller_not_found) + end + class VirtualBoxGuestPropertyNotFound < VagrantError error_key(:virtualbox_guest_property_not_found) end diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index c02820d7b..9481b5dd9 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -29,7 +29,7 @@ module VagrantPlugins controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } primary_disk = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] - unless disk_meta.nil? + if disk_meta disk_meta.each do |d| dsk = defined_disks.select { |dk| dk.name == d["name"] } if !dsk.empty? || d["uuid"] == primary_disk diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 8cfa9cd96..a225ec092 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -18,15 +18,23 @@ module VagrantPlugins return {} if !Vagrant::Util::Experimental.feature_enabled?("disks") disks_defined = defined_disks.select { |d| d.type == :disk } - controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } - if disks_defined.any? && controller && disks_defined.size > controller.maxportcount - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + if disks_defined.any? + controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + if controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, disk_type: ':disk', controller_type: 'SATA' + elsif disks_defined.size > controller.maxportcount + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + end end dvds_defined = defined_disks.select { |d| d.type == :dvd } - controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } - if dvds_defined.any? && controller && dvds_defined.size > controller.maxportcount - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + if dvds_defined.any? + controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } + if controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, disk_type: ':dvd', controller_type: 'IDE' + elsif dvds_defined.size > controller.maxportcount + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + end end machine.ui.info(I18n.t("vagrant.cap.configure_disks.start")) @@ -99,7 +107,8 @@ module VagrantPlugins disk_info = machine.provider.driver.get_port_and_device(current_disk["UUID"]) if disk_info.empty? LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest") - dsk_info = get_next_port(machine) + controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + dsk_info = get_next_port(machine, controller.name) machine.provider.driver.attach_disk(dsk_info[:port], dsk_info[:device], current_disk["Location"]) @@ -195,9 +204,9 @@ module VagrantPlugins # device = disk_info[3] # # @param [Vagrant::Machine] machine - # @param [String] controller name (defaults to "SATA Controller") + # @param [String] controller name # @return [Hash] dsk_info - The next available port and device on a given controller - def self.get_next_port(machine, controller_name="SATA Controller") + def self.get_next_port(machine, controller_name) controller = machine.provider.driver.storage_controllers.detect { |c| c.name == controller_name } # TODO: definitely need an error for this dsk_info = {} diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 071b3fa94..d23c2935f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1678,6 +1678,10 @@ en: 4.2.14 contains a critical bug which prevents it from working with Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade VirtualBox. + virtualbox_disks_controller_not_found: |- + The current disk configuration requires a controller of type '%{controller_type}'. + Please add the proper controller to your guest using provider-specific + customizations, or remove the '%{disk_type}' disk configurations. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to 30 disks to be attached to a single guest using the SATA Controller, including the primray disk. diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 1da64890b..6ea05d8b3 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -31,13 +31,21 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do "SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:controller) { double("controller", name: "SATA Controller", maxportcount: 30, sata_controller?: true, ide_controller?: false) } + let(:sata_controller) { double("sata_controller", + name: "SATA Controller", + maxportcount: 30, + sata_controller?: true, + ide_controller?: false) } + + let(:ide_controller) { double("ide_controller", + name: "IDE Controller", + maxportcount: 2, + ide_controller?: true, + sata_controller?: false) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} - let(:storage_controllers) { [ controller ] } - 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), @@ -76,8 +84,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) - allow(driver).to receive(:storage_controllers).and_return(storage_controllers) - allow(controller).to receive(:attachments).and_return(attachments) + allow(sata_controller).to receive(:attachments).and_return(attachments) + allow(driver).to receive(:storage_controllers).and_return([sata_controller]) end describe "#configure_disks" do @@ -105,7 +113,22 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end + context "missing SATA controller" do + before do + allow(driver).to receive(:storage_controllers).and_return([]) + end + + it "raises an error" do + expect { subject.configure_disks(machine, defined_disks) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end + context "with dvd type" do + before do + allow(driver).to receive(:storage_controllers).and_return([ide_controller]) + end + let(:defined_disks) { [double("dvd", type: :dvd)] } let(:dvd_data) { {uuid: "1234", name: "dvd"} } @@ -114,6 +137,17 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(subject).to receive(:handle_configure_dvd).and_return(dvd_data) subject.configure_disks(machine, defined_disks) end + + context "missing IDE controller" do + before do + allow(driver).to receive(:storage_controllers).and_return([]) + end + + it "raises an error" do + expect { subject.configure_disks(machine, defined_disks) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end end end @@ -287,18 +321,22 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe ".get_next_port" do it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine) + dsk_info = subject.get_next_port(machine, sata_controller.name) expect(dsk_info[:port]).to eq("2") expect(dsk_info[:device]).to eq("0") end - context "with an IDE controller" do - let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } + context "guest with an IDE controller" do let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "0", device: "1", uuid: "67890"}] } + before do + allow(ide_controller).to receive(:attachments).and_return(attachments) + allow(driver).to receive(:storage_controllers).and_return([ide_controller]) + end + it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine, "IDE Controller") + dsk_info = subject.get_next_port(machine, ide_controller.name) expect(dsk_info[:port]).to eq("1") expect(dsk_info[:device]).to eq("0") end @@ -310,7 +348,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do {port: "1", device: "1", uuid: "44444"}] } it "raises an error" do - expect { subject.get_next_port(machine, "IDE Controller") } + expect { subject.get_next_port(machine, ide_controller.name) } .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) end end @@ -412,13 +450,13 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe ".handle_configure_dvd" do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } - let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } before do - allow(subject).to receive(:get_next_port).with(machine, "IDE Controller").and_return({device: "0", port: "0"}) - allow(controller).to receive(:attachments).and_return( + allow(subject).to receive(:get_next_port).with(machine, ide_controller.name).and_return({device: "0", port: "0"}) + allow(ide_controller).to receive(:attachments).and_return( [port: "0", device: "0", uuid: "12345"] ) + allow(driver).to receive(:storage_controllers).and_return([ide_controller]) end it "returns the UUID of the newly-attached dvd" do From 958023dbb9cf458afbfc2d6619f9e6aa27b8aa4b Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 2 Jun 2020 14:04:09 -0400 Subject: [PATCH 12/42] Create #get_controller method in driver This makes it easier to check if the required controller can be found, and automatically raise an error if it is not. Add a #storage_bus method to the StorageController class as a shorthand way to check the general storage controller type. --- .../virtualbox/cap/configure_disks.rb | 22 +++++------- .../virtualbox/driver/version_5_0.rb | 18 +++++++++- .../virtualbox/model/storage_controller.rb | 23 +++++++----- templates/locales/en.yml | 7 ++-- .../virtualbox/cap/configure_disks_test.rb | 35 +++++++------------ .../models/storage_controller_test.rb | 28 ++++++++++----- 6 files changed, 77 insertions(+), 56 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index a225ec092..1846a25ef 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -19,20 +19,16 @@ module VagrantPlugins disks_defined = defined_disks.select { |d| d.type == :disk } if disks_defined.any? - controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } - if controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, disk_type: ':disk', controller_type: 'SATA' - elsif disks_defined.size > controller.maxportcount + controller = machine.provider.driver.get_controller('SATA') + if disks_defined.size > controller.maxportcount raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end end dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? - controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } - if controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, disk_type: ':dvd', controller_type: 'IDE' - elsif dvds_defined.size > controller.maxportcount + controller = machine.provider.driver.get_controller('IDE') + if disks_defined.size > controller.maxportcount raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end end @@ -71,7 +67,7 @@ module VagrantPlugins # 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 is always Port 0 Device 0. - controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + controller = machine.provider.driver.get_controller('SATA') primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } primary_uuid = primary[:uuid] @@ -107,7 +103,7 @@ module VagrantPlugins disk_info = machine.provider.driver.get_port_and_device(current_disk["UUID"]) if disk_info.empty? LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest") - controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + controller = machine.provider.driver.get_controller('SATA') dsk_info = get_next_port(machine, controller.name) machine.provider.driver.attach_disk(dsk_info[:port], dsk_info[:device], @@ -128,13 +124,13 @@ module VagrantPlugins # @param [Config::Disk] dvd - the current disk to configure # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd) - controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } + controller = machine.provider.driver.get_controller('IDE') disk_info = get_next_port(machine, controller.name) machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") # Refresh the controller information - controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } + controller = machine.provider.driver.get_controller('IDE') attachment = controller.attachments.detect { |a| a[:port] == disk_info[:port] && a[:device] == disk_info[:device] } @@ -211,7 +207,7 @@ module VagrantPlugins # TODO: definitely need an error for this dsk_info = {} - if controller.sata_controller? + if controller.storage_bus == 'SATA' used_ports = controller.attachments.map { |a| a[:port].to_i } next_available_port = ((0..(controller.maxportcount-1)).to_a - used_ports).first diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 9030b893d..e2a753b4c 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -937,7 +937,7 @@ module VagrantPlugins # Helper method to get a list of storage controllers added to the # current VM # - # @return [Array] + # @return [Array] def storage_controllers vm_info = show_vm_info count = vm_info.count { |key, value| key.match(/^storagecontrollername/) } @@ -960,6 +960,22 @@ module VagrantPlugins end end + + # Get the controller that uses the specified storage bus. + # + # A VirtualBoxDisksControllerNotFound error is raised if a compatible + # storage controller cannot be found. + # + # @param [String] storage_bus - for example, 'IDE' or 'SATA' + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller + def get_controller(storage_bus) + controller = storage_controllers.detect { |c| c.storage_bus == storage_bus } + if controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: storage_bus + end + controller + end + protected def valid_ip_address?(ip) diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index 7343ec9de..600f174eb 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -18,6 +18,12 @@ module VagrantPlugins # @return [String] attr_reader :type + # The storage bus associated with the storage controller, which can be + # inferred from its specific type. + # + # @return [String] + attr_reader :storage_bus + # The maximum number of avilable ports for the storage controller. For # SATA controllers, this indicates the number of disks that can be # attached. For IDE controllers, this indicates that n*2 disks can be @@ -34,19 +40,20 @@ module VagrantPlugins def initialize(name, type, maxportcount, attachments) @name = name @type = type + + if SATA_CONTROLLER_TYPES.include?(@type) + @storage_bus = 'SATA' + elsif IDE_CONTROLLER_TYPES.include?(@type) + @storage_bus = 'IDE' + else + @storage_bus = 'Unknown' + end + @maxportcount = maxportcount.to_i attachments ||= [] @attachments = attachments end - - def sata_controller? - SATA_CONTROLLER_TYPES.include?(@type) - end - - def ide_controller? - IDE_CONTROLLER_TYPES.include?(@type) - end end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index d23c2935f..0d882aae7 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1679,9 +1679,10 @@ en: Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade VirtualBox. virtualbox_disks_controller_not_found: |- - The current disk configuration requires a controller of type '%{controller_type}'. - Please add the proper controller to your guest using provider-specific - customizations, or remove the '%{disk_type}' disk configurations. + The current disk configuration requires a controller with storage bus + '%{storage_bus}', but no such controller was found. Please add the + proper controller to your guest using provider-specific + customizations. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to 30 disks to be attached to a single guest using the SATA Controller, including the primray disk. diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 6ea05d8b3..68ca5be4e 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -31,17 +31,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do "SATA Controller-ImageUUID-0-0" => "12345", "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:sata_controller) { double("sata_controller", - name: "SATA Controller", - maxportcount: 30, - sata_controller?: true, - ide_controller?: false) } - - let(:ide_controller) { double("ide_controller", - name: "IDE Controller", - maxportcount: 2, - ide_controller?: true, - sata_controller?: false) } + let(:sata_controller) { double("controller", name: "SATA Controller", storage_bus: "SATA", maxportcount: 30) } + let(:ide_controller) { double("controller", name: "IDE Controller", storage_bus: "IDE", maxportcount: 2) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} @@ -84,8 +75,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:show_vm_info).and_return(vm_info) + allow(sata_controller).to receive(:attachments).and_return(attachments) - allow(driver).to receive(:storage_controllers).and_return([sata_controller]) + + allow(driver).to receive(:get_controller).with("IDE").and_return(ide_controller) + allow(driver).to receive(:get_controller).with("SATA").and_return(sata_controller) + allow(driver).to receive(:storage_controllers).and_return([ide_controller, sata_controller]) end describe "#configure_disks" do @@ -113,9 +108,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "missing SATA controller" do + context "no SATA controller" do before do - allow(driver).to receive(:storage_controllers).and_return([]) + allow(driver).to receive(:get_controller).with("SATA"). + and_raise(Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA") end it "raises an error" do @@ -125,10 +121,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with dvd type" do - before do - allow(driver).to receive(:storage_controllers).and_return([ide_controller]) - end - let(:defined_disks) { [double("dvd", type: :dvd)] } let(:dvd_data) { {uuid: "1234", name: "dvd"} } @@ -138,9 +130,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do subject.configure_disks(machine, defined_disks) end - context "missing IDE controller" do + context "no IDE controller" do before do - allow(driver).to receive(:storage_controllers).and_return([]) + allow(driver).to receive(:get_controller).with("IDE"). + and_raise(Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "IDE") end it "raises an error" do @@ -332,7 +325,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(ide_controller).to receive(:attachments).and_return(attachments) - allow(driver).to receive(:storage_controllers).and_return([ide_controller]) end it "determines the next available port and device to use" do @@ -456,7 +448,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(ide_controller).to receive(:attachments).and_return( [port: "0", device: "0", uuid: "12345"] ) - allow(driver).to receive(:storage_controllers).and_return([ide_controller]) end it "returns the UUID of the newly-attached dvd" do diff --git a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb index 897722352..015fb2c71 100644 --- a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb +++ b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb @@ -10,19 +10,29 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do subject { described_class.new(name, type, maxportcount, attachments) } - describe "#sata_controller?" do - let(:type) { "IntelAhci" } + describe "#initialize" do + context "with SATA controller type" do + let(:type) { "IntelAhci" } - it "is true for a SATA type" do - expect(subject.sata_controller?).to be(true) + it "is recognizes a SATA controller" do + expect(subject.storage_bus).to eq('SATA') + end end - end - describe "#ide_controller?" do - let(:type) { "PIIX4" } + context "with IDE controller type" do + let(:type) { "PIIX4" } - it "is true for an IDE type" do - expect(subject.ide_controller?).to be(true) + it "recognizes an IDE controller" do + expect(subject.storage_bus).to eq('IDE') + end + end + + context "with some other type" do + let(:type) { "foo" } + + it "is unknown" do + expect(subject.storage_bus).to eq('Unknown') + end end end end From 4736fbc88fc83fdf3b135769a64855a9a638fc27 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 2 Jun 2020 15:31:59 -0400 Subject: [PATCH 13/42] Require storage controller for .get_next_port This also includes #get_controller refactorings and cleanup for the cleanup_disks capabaility. --- .../providers/virtualbox/cap/cleanup_disks.rb | 7 +++---- .../virtualbox/cap/configure_disks.rb | 13 ++++++------ plugins/providers/virtualbox/driver/meta.rb | 1 + .../virtualbox/driver/version_5_0.rb | 1 - .../virtualbox/cap/cleanup_disks_test.rb | 21 ++++++++++--------- .../virtualbox/cap/configure_disks_test.rb | 19 ++++++----------- 6 files changed, 27 insertions(+), 35 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 9481b5dd9..deb4073d1 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -26,7 +26,7 @@ module VagrantPlugins # @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) - controller = machine.provider.driver.storage_controllers.detect { |c| c.sata_controller? } + controller = machine.provider.driver.get_controller('SATA') primary_disk = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] if disk_meta @@ -57,9 +57,8 @@ module VagrantPlugins # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) - controller = machine.provider.driver.storage_controllers.detect { |c| c.ide_controller? } - - unless dvd_meta.nil? + if dvd_meta + controller = machine.provider.driver.get_controller('IDE') dvd_meta.each do |d| dsk = defined_dvds.select { |dk| dk.name == d["name"] } if !dsk.empty? diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 1846a25ef..148dd880b 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -104,7 +104,7 @@ module VagrantPlugins if disk_info.empty? LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest") controller = machine.provider.driver.get_controller('SATA') - dsk_info = get_next_port(machine, controller.name) + dsk_info = get_next_port(machine, controller) machine.provider.driver.attach_disk(dsk_info[:port], dsk_info[:device], current_disk["Location"]) @@ -126,7 +126,7 @@ module VagrantPlugins def self.handle_configure_dvd(machine, dvd) controller = machine.provider.driver.get_controller('IDE') - disk_info = get_next_port(machine, controller.name) + disk_info = get_next_port(machine, controller) machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") # Refresh the controller information @@ -181,7 +181,8 @@ module VagrantPlugins disk_var = machine.provider.driver.create_disk(disk_file, disk_config.size, disk_ext.upcase) disk_metadata = {uuid: disk_var.split(':').last.strip, name: disk_config.name} - dsk_controller_info = get_next_port(machine) + controller = machine.provider.driver.get_controller('SATA') + dsk_controller_info = get_next_port(machine, controller) machine.provider.driver.attach_disk(dsk_controller_info[:port], dsk_controller_info[:device], disk_file) disk_metadata @@ -200,11 +201,9 @@ module VagrantPlugins # device = disk_info[3] # # @param [Vagrant::Machine] machine - # @param [String] controller name + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller # @return [Hash] dsk_info - The next available port and device on a given controller - def self.get_next_port(machine, controller_name) - controller = machine.provider.driver.storage_controllers.detect { |c| c.name == controller_name } - # TODO: definitely need an error for this + def self.get_next_port(machine, controller) dsk_info = {} if controller.storage_bus == 'SATA' diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index f6f1ad161..ea9d4e720 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -116,6 +116,7 @@ module VagrantPlugins :execute_command, :export, :forward_ports, + :get_controller, :get_port_and_device, :halt, :import, diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index e2a753b4c..207c989c5 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -960,7 +960,6 @@ module VagrantPlugins end end - # Get the controller that uses the specified storage bus. # # A VirtualBoxDisksControllerNotFound error is raised if a compatible diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index e97ebba84..0094c5c52 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -30,10 +30,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:disk_meta_file) { {disk: [], floppy: [], dvd: []} } let(:defined_disks) { {} } - let(:vm_info) { {"SATA Controller-ImageUUID-0-0" => "12345", - "SATA Controller-ImageUUID-1-0" => "67890"} } - - let(:controller) { double("controller", name: "SATA Controller", maxportcount: 30, sata_controller?: true, ide_controller?: false) } + let(:sata_controller) { double("controller", name: "SATA Controller", storage_bus: "SATA", maxportcount: 30) } + let(:ide_controller) { double("controller", name: "IDE Controller", storage_bus: "IDE", maxportcount: 2) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} @@ -42,9 +40,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) - allow(driver).to receive(:show_vm_info).and_return(vm_info) - allow(driver).to receive(:storage_controllers).and_return(storage_controllers) - allow(controller).to receive(:attachments).and_return(attachments) + allow(sata_controller).to receive(:attachments).and_return(attachments) + + allow(driver).to receive(:get_controller).with("IDE").and_return(ide_controller) + allow(driver).to receive(:get_controller).with("SATA").and_return(sata_controller) + allow(driver).to receive(:storage_controllers).and_return([ide_controller, sata_controller]) end describe "#cleanup_disks" do @@ -108,14 +108,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end describe "#handle_cleanup_dvd" do - let(:vm_info) { {"IDE Controller-ImageUUID-0-0" => "1234" } } - - let(:controller) { double("controller", name: "IDE Controller", maxportcount: 2, sata_controller?: false, ide_controller?: true) } let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso"}]} } let(:defined_disks) { [] } + before do + allow(ide_controller).to receive(:attachments).and_return(attachments) + end + it "removes the medium from guest" do expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 68ca5be4e..ce1128f1e 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -25,12 +25,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do double(:state) end - let(:vm_info) { {"storagecontrollername0" => "SATA Controller", - "storagecontrollertype0" => "IntelAhci", - "storagecontrollermaxportcount0" => "30", - "SATA Controller-ImageUUID-0-0" => "12345", - "SATA Controller-ImageUUID-1-0" => "67890"} } - let(:sata_controller) { double("controller", name: "SATA Controller", storage_bus: "SATA", maxportcount: 30) } let(:ide_controller) { double("controller", name: "IDE Controller", storage_bus: "IDE", maxportcount: 2) } @@ -74,8 +68,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) - allow(driver).to receive(:show_vm_info).and_return(vm_info) - allow(sata_controller).to receive(:attachments).and_return(attachments) allow(driver).to receive(:get_controller).with("IDE").and_return(ide_controller) @@ -301,7 +293,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:create_disk). with(disk_file, disk_config.size, "VDI").and_return(disk_data) - expect(subject).to receive(:get_next_port).with(machine). + expect(subject).to receive(:get_next_port).with(machine, sata_controller). and_return(port_and_device) expect(driver).to receive(:attach_disk).with(port_and_device[:port], @@ -314,7 +306,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe ".get_next_port" do it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine, sata_controller.name) + dsk_info = subject.get_next_port(machine, sata_controller) expect(dsk_info[:port]).to eq("2") expect(dsk_info[:device]).to eq("0") end @@ -328,7 +320,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine, ide_controller.name) + dsk_info = subject.get_next_port(machine, ide_controller) expect(dsk_info[:port]).to eq("1") expect(dsk_info[:device]).to eq("0") end @@ -340,7 +332,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do {port: "1", device: "1", uuid: "44444"}] } it "raises an error" do - expect { subject.get_next_port(machine, ide_controller.name) } + expect { subject.get_next_port(machine, ide_controller) } .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) end end @@ -444,7 +436,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } before do - allow(subject).to receive(:get_next_port).with(machine, ide_controller.name).and_return({device: "0", port: "0"}) + allow(subject).to receive(:get_next_port).with(machine, ide_controller). + and_return({device: "0", port: "0"}) allow(ide_controller).to receive(:attachments).and_return( [port: "0", device: "0", uuid: "12345"] ) From 61f43fb59f2eb28c8f1a09b2cb6d21e541834006 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 3 Jun 2020 11:36:57 -0400 Subject: [PATCH 14/42] Require controller name for #remove_disk Also use #get_controller methods when attaching a disk. --- .../providers/virtualbox/cap/cleanup_disks.rb | 2 +- .../providers/virtualbox/cap/configure_disks.rb | 3 ++- .../providers/virtualbox/driver/version_5_0.rb | 13 ++++++------- .../virtualbox/cap/cleanup_disks_test.rb | 2 +- .../virtualbox/cap/configure_disks_test.rb | 4 ++-- .../virtualbox/driver/version_5_0_test.rb | 16 +++++++--------- 6 files changed, 19 insertions(+), 21 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index deb4073d1..c6c0ce466 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -44,7 +44,7 @@ module VagrantPlugins LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else # TODO: write test for sata controller with another name - machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device]) + machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device], controller.name) end machine.provider.driver.close_medium(d["uuid"]) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 148dd880b..3413ee0ad 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -263,9 +263,10 @@ module VagrantPlugins begin # Danger Zone + controller = machine.provider.driver.get_controller('SATA') # remove and close original volume - machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device]) + machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device], controller.name) # Create a backup of the original disk if something goes wrong LOGGER.warn("Making a backup of the original disk at #{defined_disk["Location"]}") FileUtils.mv(defined_disk["Location"], backup_disk_location) diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 207c989c5..c994669c1 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -28,17 +28,16 @@ module VagrantPlugins # @param [String] type - type of disk to attach # @param [Hash] opts - additional options def attach_disk(port, device, file, type="hdd", **opts) - # Maybe only support SATA Controller for `:disk`??? if type == "hdd" - controller = "SATA Controller" + controller = get_controller('SATA') else - controller = "IDE Controller" + controller = get_controller('IDE') end comment = "This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant." execute('storageattach', @uuid, - '--storagectl', controller, + '--storagectl', controller.name, '--port', port.to_s, '--device', device.to_s, '--type', type, @@ -234,10 +233,10 @@ module VagrantPlugins # @param [String] port - port on device to attach disk to # @param [String] device - device on controller for disk - # @param [Hash] opts - additional options - def remove_disk(port, device, controller="SATA Controller") + # @param [String] controller_name - controller name to remove disk from + def remove_disk(port, device, controller_name) execute('storageattach', @uuid, - '--storagectl', controller, + '--storagectl', controller_name, '--port', port.to_s, '--device', device.to_s, '--medium', "none") diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 0094c5c52..a505a1d1b 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -88,7 +88,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do with("67890"). and_return(device_info) - expect(driver).to receive(:remove_disk).with("1", "0").and_return(true) + expect(driver).to receive(:remove_disk).with("1", "0", sata_controller.name).and_return(true) expect(driver).to receive(:close_medium).with("67890").and_return(true) subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index ce1128f1e..9cc5fa682 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -361,7 +361,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i). and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device]). + expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], sata_controller.name). and_return(true) expect(driver).to receive(:close_medium).with("12345") @@ -393,7 +393,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i). and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device]). + expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], sata_controller.name). and_return(true) expect(driver).to receive(:close_medium).with("12345") diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index b6b23e0d2..f441afbf6 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -92,6 +92,12 @@ OUTPUT end describe "#attach_disk" do + let(:controller) { double("controller", name: "IDE Controller", storage_bus: "IDE") } + + before do + allow(subject).to receive(:get_controller).with(controller.storage_bus).and_return(controller) + end + it "attaches a dvddrive device to the IDE controller" do expect(subject).to receive(:execute) do |*args| storagectl = args[args.index("--storagectl") + 1] @@ -102,15 +108,7 @@ OUTPUT end describe "#remove_disk" do - it "removes a disk from the SATA Controller by default" do - expect(subject).to receive(:execute) do |*args| - storagectl = args[args.index("--storagectl") + 1] - expect(storagectl).to eq("SATA Controller") - end - subject.remove_disk(anything, anything) - end - - it "can remove a disk from the specified controller" do + it "removes a disk from the specified controller" do expect(subject).to receive(:execute) do |*args| storagectl = args[args.index("--storagectl") + 1] expect(storagectl).to eq("IDE Controller") From b9705214bd8cda79f2d7c98b4d85e4f7b6cc0eaa Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 4 Jun 2020 15:50:41 -0400 Subject: [PATCH 15/42] Replace backticks with single quotes --- templates/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 0d882aae7..a553b68e3 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1850,7 +1850,7 @@ en: config: disk: dvd_type_file_required: - A 'file' option is required when defining a disk of type `:dvd` for guest '%{machine}'. + A 'file' option is required when defining a disk of type ':dvd' for guest '%{machine}'. dvd_type_primary: |- Disks of type ':dvd' cannot be defined as a primary disks. Please remove the 'primary' argument for disk '%{name}' on guest '%{machine}'. From 8561467e40a4f1aa11839675b32a694b7e59153c Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 4 Jun 2020 15:55:13 -0400 Subject: [PATCH 16/42] IDE controllers have two devices/port --- plugins/providers/virtualbox/cap/configure_disks.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 3413ee0ad..14913fac3 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -28,7 +28,7 @@ module VagrantPlugins dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? controller = machine.provider.driver.get_controller('IDE') - if disks_defined.size > controller.maxportcount + if disks_defined.size > controller.maxportcount * 2 raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit end end From 1e6eb0d6366471728672db296e9f9ae94ce9ce09 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 5 Jun 2020 11:13:36 -0400 Subject: [PATCH 17/42] Raise an error if primary disk can't be found --- lib/vagrant/errors.rb | 4 ++++ plugins/providers/virtualbox/cap/cleanup_disks.rb | 8 ++++++-- plugins/providers/virtualbox/cap/configure_disks.rb | 3 +++ templates/locales/en.yml | 4 ++++ .../providers/virtualbox/cap/cleanup_disks_test.rb | 6 ++++++ .../providers/virtualbox/cap/configure_disks_test.rb | 6 ++++++ 6 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 619803022..5a219570e 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -936,6 +936,10 @@ module Vagrant error_key(:virtualbox_disks_controller_not_found) end + class VirtualBoxDisksPrimaryNotFound < VagrantError + error_key(:virtualbox_disks_primary_not_found) + end + class VirtualBoxGuestPropertyNotFound < VagrantError error_key(:virtualbox_guest_property_not_found) end diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index c6c0ce466..ec95c604d 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -27,12 +27,16 @@ module VagrantPlugins # @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) controller = machine.provider.driver.get_controller('SATA') - primary_disk = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" }[:uuid] + primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } + if primary.nil? + raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound + end + primary_uuid = primary[:uuid] if disk_meta disk_meta.each do |d| dsk = defined_disks.select { |dk| dk.name == d["name"] } - if !dsk.empty? || d["uuid"] == primary_disk + if !dsk.empty? || d["uuid"] == primary_uuid next else LOGGER.warn("Found disk not in Vagrantfile config: '#{d["name"]}'. Removing disk from guest #{machine.name}") diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 14913fac3..2edb557f8 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -69,6 +69,9 @@ module VagrantPlugins # always come in port order, but primary is always Port 0 Device 0. controller = machine.provider.driver.get_controller('SATA') primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } + if primary.nil? + raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound + end primary_uuid = primary[:uuid] current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first diff --git a/templates/locales/en.yml b/templates/locales/en.yml index a553b68e3..a6a27b877 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1683,6 +1683,10 @@ en: '%{storage_bus}', but no such controller was found. Please add the proper controller to your guest using provider-specific customizations. + virtualbox_disks_primary_not_found: |- + Vagrant was not able to detect a primary disk attached to the SATA + controller. Vagrant Disks requires that the primary disk be attached + to the first port of the SATA controller in order to manage it. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to 30 disks to be attached to a single guest using the SATA Controller, including the primray disk. diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index a505a1d1b..6eedf57db 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -63,6 +63,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do subject.cleanup_disks(machine, defined_disks, disk_meta_file) end + + it "raises an error if primary disk can't be found" do + allow(sata_controller).to receive(:attachments).and_return([]) + expect { subject.cleanup_disks(machine, defined_disks, disk_meta_file) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) + end end context "with dvd attached" do diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 9cc5fa682..5ae19f423 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -142,6 +142,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(primary_disk).to eq(all_disks.first) end + it "raises an error if primary disk can't be found" do + allow(sata_controller).to receive(:attachments).and_return([]) + expect { subject.get_current_disk(machine, defined_disks.first, all_disks) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) + 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]) From 53f7412821c5d3202760f4e8dc222998850fa1e1 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 5 Jun 2020 12:03:56 -0400 Subject: [PATCH 18/42] Make VirtualBoxDisksDefinedExceedLimit error generic Create a #limit method in the StorageController class so we can customize the error message when a storage controller is full. --- .../virtualbox/cap/configure_disks.rb | 22 ++++++++++++++----- .../virtualbox/model/storage_controller.rb | 18 +++++++++++---- templates/locales/en.yml | 7 +++--- .../virtualbox/cap/configure_disks_test.rb | 9 ++++++-- .../models/storage_controller_test.rb | 12 +++++++++- 5 files changed, 52 insertions(+), 16 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 2edb557f8..b5792d19b 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -20,16 +20,22 @@ module VagrantPlugins disks_defined = defined_disks.select { |d| d.type == :disk } if disks_defined.any? controller = machine.provider.driver.get_controller('SATA') - if disks_defined.size > controller.maxportcount - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + if disks_defined.size > controller.limit + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: controller.limit, + storage_bus: 'SATA', + type: :disk end end dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? controller = machine.provider.driver.get_controller('IDE') - if disks_defined.size > controller.maxportcount * 2 - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + if disks_defined.size > controller.limit + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: controller.limit, + storage_bus: 'IDE', + type: :dvd end end @@ -236,8 +242,12 @@ module VagrantPlugins if dsk_info[:port].to_s.empty? # This likely only occurs if additional disks have been added outside of Vagrant configuration - LOGGER.warn("There are no more available ports to attach disks to for the controller '#{controller}'. Clear up some space on the controller '#{controller}' to attach new disks.") - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit + LOGGER.warn("There is no more available space to attach disks to for the controller '#{controller}'. Clear up some space on the controller '#{controller}' to attach new disks.") + disk_type = controller.storage_bus == 'SATA' ? :disk : :dvd + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: controller.limit, + storage_bus: controller.storage_bus, + type: disk_type end dsk_info diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index 600f174eb..abd4b8c7d 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -24,14 +24,19 @@ module VagrantPlugins # @return [String] attr_reader :storage_bus - # The maximum number of avilable ports for the storage controller. For - # SATA controllers, this indicates the number of disks that can be - # attached. For IDE controllers, this indicates that n*2 disks can be - # attached (primary/secondary). + # The maximum number of avilable ports for the storage controller. # # @return [Integer] attr_reader :maxportcount + # The maximum number of individual disks that can be attached to the + # storage controller. For SATA controllers, this equals the maximum + # number of ports. For IDE controllers, this will be twice the max + # number of ports (primary/secondary). + # + # @return [Integer] + attr_reader :limit + # The list of disks/ISOs attached to each storage controller. # # @return [Array] @@ -50,6 +55,11 @@ module VagrantPlugins end @maxportcount = maxportcount.to_i + if @storage_bus == 'IDE' + @limit = @maxportcount * 2 + else + @limit = @maxportcount + end attachments ||= [] @attachments = attachments diff --git a/templates/locales/en.yml b/templates/locales/en.yml index a6a27b877..1218d9a56 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1688,10 +1688,11 @@ en: controller. Vagrant Disks requires that the primary disk be attached to the first port of the SATA controller in order to manage it. virtualbox_disks_defined_exceed_limit: |- - VirtualBox only allows up to 30 disks to be attached to a single guest using the SATA Controller, - including the primray disk. + VirtualBox only allows up to %{limit} disks to be attached to a single + guest using the %{storage_bus} controller, including the primary disk. - Please ensure only up to 30 disks are configured for your guest. + Please ensure only up to %{limit} disks of type '%{type}' are + configured for your guest. virtualbox_guest_property_not_found: |- Could not find a required VirtualBox guest property: %{guest_property} diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 5ae19f423..334893541 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -25,8 +25,13 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do double(:state) end - let(:sata_controller) { double("controller", name: "SATA Controller", storage_bus: "SATA", maxportcount: 30) } - let(:ide_controller) { double("controller", name: "IDE Controller", storage_bus: "IDE", maxportcount: 2) } + let(:sata_controller) { double("controller", name: "SATA Controller", + storage_bus: "SATA", maxportcount: 30, + limit: 30) } + + let(:ide_controller) { double("controller", name: "IDE Controller", + storage_bus: "IDE", maxportcount: 2, + limit: 4) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} diff --git a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb index 015fb2c71..d25298449 100644 --- a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb +++ b/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb @@ -13,18 +13,28 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do describe "#initialize" do context "with SATA controller type" do let(:type) { "IntelAhci" } + let(:maxportcount) { 30 } - it "is recognizes a SATA controller" do + it "recognizes a SATA controller" do expect(subject.storage_bus).to eq('SATA') end + + it "calculates the maximum number of attachments" do + expect(subject.limit).to eq(30) + end end context "with IDE controller type" do let(:type) { "PIIX4" } + let(:maxportcount) { 2 } it "recognizes an IDE controller" do expect(subject.storage_bus).to eq('IDE') end + + it "calculates the maximum number of attachments" do + expect(subject.limit).to eq(4) + end end context "with some other type" do From c9be89718daeb367879f26b948be7aa5fada7111 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 5 Jun 2020 13:59:15 -0400 Subject: [PATCH 19/42] Write documentation for :dvd disk type --- website/pages/docs/boxes/index.mdx | 6 ++-- website/pages/docs/disks/configuration.mdx | 24 ++++++++++----- website/pages/docs/disks/usage.mdx | 30 +++++++++++++++---- website/pages/docs/disks/virtualbox/index.mdx | 20 +++++++++++-- 4 files changed, 62 insertions(+), 18 deletions(-) diff --git a/website/pages/docs/boxes/index.mdx b/website/pages/docs/boxes/index.mdx index eb9157985..f2da40784 100644 --- a/website/pages/docs/boxes/index.mdx +++ b/website/pages/docs/boxes/index.mdx @@ -60,7 +60,7 @@ with third-party published boxes. ## Official Boxes -HashiCorp (the makers of Vagrant) publish a basic Ubuntu 18.04 64-bit box that is available for minimal use cases. It is highly optimized, small in size, and includes support for Virtualbox, Hyper-V, and VMware. You can use it like this: +HashiCorp (the makers of Vagrant) publish a basic Ubuntu 18.04 64-bit box that is available for minimal use cases. It is highly optimized, small in size, and includes support for VirtualBox, Hyper-V, and VMware. You can use it like this: ```shell-session $ vagrant init hashicorp/bionic64 @@ -74,10 +74,10 @@ Vagrant.configure("2") do |config| end ``` -For other users, we recommend the [Bento boxes](https://vagrantcloud.com/bento). The Bento boxes are [open source](https://github.com/chef/bento) and built for a number of providers including VMware, Virtualbox, and Parallels. There are a variety of operating systems and versions available. +For other users, we recommend the [Bento boxes](https://vagrantcloud.com/bento). The Bento boxes are [open source](https://github.com/chef/bento) and built for a number of providers including VMware, VirtualBox, and Parallels. There are a variety of operating systems and versions available. These are the only two officially-recommended box sets. Special thanks to the Bento project for providing a solid base template for the `hashicorp/bionic64` box. -~> **It is often a point of confusion**, but Canonical (the company that makes the Ubuntu operating system) publishes boxes under the "ubuntu" namespace on Vagrant Cloud. These boxes only support Virtualbox and do not provide an ideal experience for most users. If you encounter issues with these boxes, please try the Bento boxes instead. +~> **It is often a point of confusion**, but Canonical (the company that makes the Ubuntu operating system) publishes boxes under the "ubuntu" namespace on Vagrant Cloud. These boxes only support VirtualBox and do not provide an ideal experience for most users. If you encounter issues with these boxes, please try the Bento boxes instead. diff --git a/website/pages/docs/disks/configuration.mdx b/website/pages/docs/disks/configuration.mdx index 59650e651..ec6cb9a01 100644 --- a/website/pages/docs/disks/configuration.mdx +++ b/website/pages/docs/disks/configuration.mdx @@ -14,13 +14,21 @@ Vagrant Disks has several options that allow users to define and attach disks to - `disk_ext` (string) - Optional argument that defines what kind of file extension a disk should have. Defaults to `"vdi"` if unspecified. For a list of supported disk extensions, please check the specific provider being used. -- `file` (string) - Optional argument that defines a path on disk pointing to - the location of a disk file that already exists. + Not used for type `:dvd.` + +- `file` (string) - Ffor type `:dvd`, this is a required argument that should + point to an .iso file on the host machine. For type `:disk`, this is an + optional argument that can point to the location of a disk file that already + exists. + - `name` (string) - Required option to give the disk a name. This name will be - used as the filename when creating the disk. -- `primary` (boolean) - Optional argument that configures a given disk to be the - "primary" disk to manage on the guest. There can only be one `primary` disk per guest. - Defaults to `false` if not specified. + also be used as the filename when creating a virtual hard disk. + +- `primary` (boolean) - Optional argument that configures a given disk to be + the "primary" disk to manage on the guest. There can only be one `primary` + disk per guest, and it must be of type `:disk`. Defaults to `false` if not + specified. + - `provider_config` (hash) - Additional provider specific options for managing a given disk. Please refer to the provider specific documentation to see any available provider_config options. @@ -31,7 +39,8 @@ Vagrant Disks has several options that allow users to define and attach disks to - `{providername: {diskoption: value}, otherprovidername: {diskoption: value}` - A hash where the top level key(s) are one or more providers, and each provider keys values are a hash of options and their values. -- `size` (String) - The size of the disk to create. For example, `"10GB"`. +- `size` (String) - The size of the disk to create. For example, `"10GB"`. Not + used for type `:dvd.` **Note:** More specific examples of these can be found under the provider specific disk page. The `provider_config` option will depend on the provider @@ -53,6 +62,7 @@ You can set a disk type with the first argument of a disk config in your Vagrant ```ruby config.vm.disk :disk, name: "backup", size: "10GB" +config.vm.disk :dvd, name: "installer", path: "./installer.iso" config.vm.disk :floppy, name: "cool_files" ``` diff --git a/website/pages/docs/disks/usage.mdx b/website/pages/docs/disks/usage.mdx index 1bedbf6e9..4a3fdd49a 100644 --- a/website/pages/docs/disks/usage.mdx +++ b/website/pages/docs/disks/usage.mdx @@ -54,7 +54,7 @@ end It should be noted that due to how VirtualBox functions, it is not possible to shrink the size of a disk. -### Attaching new disks +### Attaching new hard disks Vagrant can attach multiple disks to a guest using the VirtualBox provider. An example of attaching a single disk to a guest with 10 GB of storage can be found below: @@ -86,12 +86,32 @@ Vagrant.configure("2") do |config| end ``` -Note: Virtualbox only allows for up to 30 disks to be attached to a given SATA Controller, +Note: VirtualBox only allows for up to 30 disks to be attached to a given SATA Controller, and this number includes the primary disk! Attempting to configure more than 30 will result in a Vagrant error. +### Attaching optical drives + +Vagrant can attach .iso files as optical drives using the VirtualBox provider. +An example of attaching an optical drive to a guest can be found below: + +```ruby +Vagrant.configure("2") do |config| + config.vm.define "hashicorp" do |h| + h.vm.box = "hashicorp/bionic64" + h.vm.provider :virtualbox + + h.vm.disk :dvd, name: "installer", file: "./installer.iso" + end +end +``` + +Note: VirtualBox only allows for up to 4 optical drives to be attached to a +given IDE controller. + ### Removing Disks -If you have removed a disk from your Vagrant config and wish for it to be detached from the guest, -you will need to `vagrant reload` your guest to apply these changes. **NOTE:** Doing so -will also delete the medium from your hard drive. +If you have removed a disk from your Vagrant config and wish for it to be +detached from the guest, you will need to `vagrant reload` your guest to apply +these changes. **NOTE:** Removing virtual hard disks created by Vagrant will +also delete the medium from your hard drive. diff --git a/website/pages/docs/disks/virtualbox/index.mdx b/website/pages/docs/disks/virtualbox/index.mdx index 8f0241767..974efe76f 100644 --- a/website/pages/docs/disks/virtualbox/index.mdx +++ b/website/pages/docs/disks/virtualbox/index.mdx @@ -31,9 +31,23 @@ 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. -When new disks are defined to be attached to a guest, Vagrant will create and attach -these disks to a guests SATA Controller. It should be noted that up to 30 disks -can be attached to the SATA Controller. +When new disks are defined to be attached to a guest, Vagrant will attach +disks to a particular storage controller based on the type of disk configured: + +- For the `:disk` type, Vagrant will attach the disk to a guest's SATA + controller. It will also create the disk if necessary. +- For the `:dvd` type, Vagrant will attach the disk to a guest's IDE + controller. + +Vagrant will not be able to configure disks of a given type if the associated +storage controller does not exist. In this case, you may use +[provider-specific customizations](/docs/providers/virtualbox/configuration#vboxmanage-customizations) +to add a required storage controller. + +It should also be noted that storage controllers have different limits for the +number of disks that can be attached. Attempting to configure more than the +maximum number of disks for a storage controller type will result in a Vagrant +error. 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) From c9bdcb68394a2a2d63e8078974e3939843b0dfa3 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 5 Jun 2020 18:00:18 -0400 Subject: [PATCH 20/42] Error on unsupported storage controller type --- lib/vagrant/errors.rb | 4 ++++ plugins/providers/virtualbox/cap/configure_disks.rb | 4 +++- templates/locales/en.yml | 11 +++++++---- .../providers/virtualbox/cap/configure_disks_test.rb | 9 +++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 5a219570e..52fc1a897 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -940,6 +940,10 @@ module Vagrant error_key(:virtualbox_disks_primary_not_found) end + class VirtualBoxDisksUnsupportedController < VagrantError + error_key(:virtualbox_disks_unsupported_controller) + end + class VirtualBoxGuestPropertyNotFound < VagrantError error_key(:virtualbox_guest_property_not_found) end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index b5792d19b..d420ca91e 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -221,7 +221,7 @@ module VagrantPlugins dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" - else + elsif controller.storage_bus == 'IDE' # IDE Controllers have primary/secondary devices, so find the first port # with an empty device (0..(controller.maxportcount-1)).each do |port| @@ -238,6 +238,8 @@ module VagrantPlugins dsk_info[:device] = "0" end end + else + raise Vagrant::Errors::VirtualBoxDisksUnsupportedController, controller_name: controller.name end if dsk_info[:port].to_s.empty? diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 1218d9a56..5c1421f5f 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1683,16 +1683,19 @@ en: '%{storage_bus}', but no such controller was found. Please add the proper controller to your guest using provider-specific customizations. - virtualbox_disks_primary_not_found: |- - Vagrant was not able to detect a primary disk attached to the SATA - controller. Vagrant Disks requires that the primary disk be attached - to the first port of the SATA controller in order to manage it. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to %{limit} disks to be attached to a single guest using the %{storage_bus} controller, including the primary disk. Please ensure only up to %{limit} disks of type '%{type}' are configured for your guest. + virtualbox_disks_primary_not_found: |- + Vagrant was not able to detect a primary disk attached to the SATA + controller. Vagrant Disks requires that the primary disk be attached + to the first port of the SATA controller in order to manage it. + virtualbox_disks_unsupported_controller: |- + An disk operation was attempted on the controller '%{controller_name}', + but Vagrant doesn't support this type of disk controller. virtualbox_guest_property_not_found: |- Could not find a required VirtualBox guest property: %{guest_property} diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 334893541..78a1d3436 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -348,6 +348,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end end + + context "unsupported storage controller" do + let(:controller) { double("controller", name: "Adaptec ACB-4000A", storage_bus: "SASI") } + + it "raises an error" do + expect { subject.get_next_port(machine, controller) } + .to raise_error(Vagrant::Errors::VirtualBoxDisksUnsupportedController) + end + end end describe "#resize_disk" do From e01b51fa43022cc5de037d786ad6dc88b130524e Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 10 Jun 2020 12:48:15 -0400 Subject: [PATCH 21/42] Disk management with a single controller .configure_disks is responsible for determining the disk controller(s) to use, given the machine configuration and the current disk config. When a machine has a single controller, use it for all attachments. When a machine has multiple controllers, attach disks to the SATA controller and DVDs to the IDE controller. This commit also returns additional attachment information (controller/port/device) with the disk metadata. --- .../virtualbox/cap/configure_disks.rb | 163 +++++++++----- .../virtualbox/driver/version_5_0.rb | 11 +- templates/locales/en.yml | 8 +- .../virtualbox/cap/configure_disks_test.rb | 207 ++++++++---------- 4 files changed, 204 insertions(+), 185 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index d420ca91e..0dbc46aa0 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -17,43 +17,65 @@ module VagrantPlugins return {} if !Vagrant::Util::Experimental.feature_enabled?("disks") - disks_defined = defined_disks.select { |d| d.type == :disk } - if disks_defined.any? - controller = machine.provider.driver.get_controller('SATA') - if disks_defined.size > controller.limit - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, - limit: controller.limit, - storage_bus: 'SATA', - type: :disk - end - end - - dvds_defined = defined_disks.select { |d| d.type == :dvd } - if dvds_defined.any? - controller = machine.provider.driver.get_controller('IDE') - if disks_defined.size > controller.limit - raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, - limit: controller.limit, - storage_bus: 'IDE', - type: :dvd - end - end - machine.ui.info(I18n.t("vagrant.cap.configure_disks.start")) + storage_controllers = machine.provider.driver.storage_controllers + + # Check to determine which controller we should attach disks to. + # If there is only one storage controller attached to the VM, use + # it. If there are multiple controllers (e.g. IDE/SATA), attach DVDs + # to the IDE controller and disks to the SATA controller. + if storage_controllers.size == 1 + controller = storage_controllers.first + + # The only way you can define up to the controller limit is if + # exactly one disk is a primary disk, otherwise we need to reserve + # a slot for the primary + if (defined_disks.any? { |d| d.primary } && defined_disks.size > controller.limit) || + defined_disks.size > controller.limit - 1 + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: controller.limit, + name: controller.name + else + disk_controller = controller + dvd_controller = controller + end + else + disks_defined = defined_disks.select { |d| d.type == :disk } + if disks_defined.any? + disk_controller = machine.provider.driver.get_controller('SATA') + if (disks_defined.any? { |d| d.primary } && disks_defined.size > disk_controller.limit) || + disks_defined.size > disk_controller.limit - 1 + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: disk_controller.limit, + name: disk_controller.name + end + end + + dvds_defined = defined_disks.select { |d| d.type == :dvd } + if dvds_defined.any? + dvd_controller = machine.provider.driver.get_controller('IDE') + if disks_defined.size > dvd_controller.limit + raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, + limit: dvd_controller.limit, + name: dvd_controller.name + end + end + end + 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) + disk_data = handle_configure_disk(machine, disk, current_disks, disk_controller) 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 - dvd_data = handle_configure_dvd(machine, disk) + dvd_data = handle_configure_dvd(machine, disk, dvd_controller) configured_disks[:dvd] << dvd_data unless dvd_data.empty? end end @@ -66,14 +88,15 @@ module VagrantPlugins # @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 + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use # @return [Hash] current_disk - Returns the current disk. Returns nil if it doesn't exist - def self.get_current_disk(machine, disk, all_disks) + def self.get_current_disk(machine, disk, all_disks, controller) 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 is always Port 0 Device 0. - controller = machine.provider.driver.get_controller('SATA') primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } if primary.nil? raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound @@ -93,35 +116,43 @@ module VagrantPlugins # @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 + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use # @return [Hash] - disk_metadata - def self.handle_configure_disk(machine, disk, all_disks) + def self.handle_configure_disk(machine, disk, all_disks, controller) disk_metadata = {} # Grab the existing configured disk, if it exists - current_disk = get_current_disk(machine, disk, all_disks) + current_disk = get_current_disk(machine, disk, all_disks, controller) # Configure current disk if !current_disk # create new disk and attach - disk_metadata = create_disk(machine, disk) + disk_metadata = create_disk(machine, disk, controller) elsif compare_disk_size(machine, disk, current_disk) - disk_metadata = resize_disk(machine, disk, current_disk) + disk_metadata = resize_disk(machine, disk, current_disk, controller) else # TODO: What if it needs to be resized? disk_info = machine.provider.driver.get_port_and_device(current_disk["UUID"]) if disk_info.empty? LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest") - controller = machine.provider.driver.get_controller('SATA') dsk_info = get_next_port(machine, controller) machine.provider.driver.attach_disk(dsk_info[:port], dsk_info[:device], - current_disk["Location"]) + current_disk["Location"], + controller.name) + disk_metadata[:port] = dsk_info[:port] + disk_metadata[:device] = dsk_info[:device] else LOGGER.info("No further configuration required for disk '#{disk.name}'") + disk_metadata[:port] = disk_info[:port] + disk_metadata[:device] = disk_info[:device] end - disk_metadata = {uuid: current_disk["UUID"], name: disk.name} + disk_metadata[:uuid] = current_disk["UUID"] + disk_metadata[:name] = disk.name + disk_metadata[:controller] = controller.name end disk_metadata @@ -131,20 +162,23 @@ module VagrantPlugins # # @param [Vagrant::Machine] machine - the current machine # @param [Config::Disk] dvd - the current disk to configure + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use # @return [Hash] - dvd_metadata - def self.handle_configure_dvd(machine, dvd) - controller = machine.provider.driver.get_controller('IDE') - + def self.handle_configure_dvd(machine, dvd, controller) disk_info = get_next_port(machine, controller) - machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], dvd.file, "dvddrive") + port = disk_info[:port] + device = disk_info[:device] + + machine.provider.driver.attach_disk(port, device, dvd.file, "dvddrive", controller.name) # Refresh the controller information - controller = machine.provider.driver.get_controller('IDE') - attachment = controller.attachments.detect { |a| a[:port] == disk_info[:port] && - a[:device] == disk_info[:device] } + controller = machine.provider.driver.get_controller(controller.storage_bus) + attachment = controller.attachments.detect { |a| a[:port] == port && + a[:device] == device } if attachment - {uuid: attachment[:uuid], name: dvd.name} + {uuid: attachment[:uuid], name: dvd.name, controller: controller.name, port: port, device: device} else {} end @@ -173,7 +207,9 @@ module VagrantPlugins # # @param [Vagrant::Machine] machine # @param [Kernel_V2::VagrantConfigDisk] disk_config - def self.create_disk(machine, disk_config) + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use + def self.create_disk(machine, disk_config, controller) machine.ui.detail(I18n.t("vagrant.cap.configure_disks.create_disk", name: disk_config.name)) # NOTE: At the moment, there are no provider specific configs for VirtualBox # but we grab it anyway for future use. @@ -188,11 +224,15 @@ module VagrantPlugins LOGGER.info("Attempting to create a new disk file '#{disk_file}' of size '#{disk_config.size}' bytes") disk_var = machine.provider.driver.create_disk(disk_file, disk_config.size, disk_ext.upcase) - disk_metadata = {uuid: disk_var.split(':').last.strip, name: disk_config.name} - - controller = machine.provider.driver.get_controller('SATA') dsk_controller_info = get_next_port(machine, controller) - machine.provider.driver.attach_disk(dsk_controller_info[:port], dsk_controller_info[:device], disk_file) + machine.provider.driver.attach_disk(dsk_controller_info[:port], + dsk_controller_info[:device], + disk_file, + controller.name) + + disk_metadata = {uuid: disk_var.split(':').last.strip, name: disk_config.name, + controller: controller.name, port: dsk_controller_info[:port], + device: dsk_controller_info[:device]} disk_metadata end @@ -210,7 +250,8 @@ module VagrantPlugins # device = disk_info[3] # # @param [Vagrant::Machine] machine - # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use # @return [Hash] dsk_info - The next available port and device on a given controller def self.get_next_port(machine, controller) dsk_info = {} @@ -245,11 +286,9 @@ module VagrantPlugins if dsk_info[:port].to_s.empty? # This likely only occurs if additional disks have been added outside of Vagrant configuration LOGGER.warn("There is no more available space to attach disks to for the controller '#{controller}'. Clear up some space on the controller '#{controller}' to attach new disks.") - disk_type = controller.storage_bus == 'SATA' ? :disk : :dvd raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: controller.limit, - storage_bus: controller.storage_bus, - type: disk_type + name: controller.name end dsk_info @@ -258,8 +297,10 @@ module VagrantPlugins # @param [Vagrant::Machine] machine # @param [Config::Disk] disk_config - the current disk to configure # @param [Hash] defined_disk - current disk as represented by VirtualBox + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - + # the storage controller to use # @return [Hash] - disk_metadata - def self.resize_disk(machine, disk_config, defined_disk) + def self.resize_disk(machine, disk_config, defined_disk, controller) machine.ui.detail(I18n.t("vagrant.cap.configure_disks.resize_disk", name: disk_config.name), prefix: true) if defined_disk["Storage format"] == "VMDK" @@ -278,8 +319,6 @@ module VagrantPlugins begin # Danger Zone - controller = machine.provider.driver.get_controller('SATA') - # remove and close original volume machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device], controller.name) # Create a backup of the original disk if something goes wrong @@ -292,13 +331,16 @@ module VagrantPlugins # clone back to original vmdk format and attach resized disk vmdk_disk_file = machine.provider.driver.vdi_to_vmdk(vdi_disk_file) - machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], vmdk_disk_file, "hdd") + machine.provider.driver.attach_disk(disk_info[:port], + disk_info[:device], + vmdk_disk_file, "hdd", + controller.name) rescue ScriptError, SignalException, StandardError LOGGER.warn("Vagrant encountered an error while trying to resize a disk. Vagrant will now attempt to reattach and preserve the original disk...") machine.ui.error(I18n.t("vagrant.cap.configure_disks.recovery_from_resize", location: original_disk["Location"], name: machine.name)) - recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file) + recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file, controller) raise ensure @@ -314,9 +356,11 @@ module VagrantPlugins defined_disk = new_disk_info else machine.provider.driver.resize_disk(defined_disk["Location"], disk_config.size.to_i) + disk_info = machine.provider.driver.get_port_and_device(defined_disk["UUID"]) end - disk_metadata = {uuid: defined_disk["UUID"], name: disk_config.name} + disk_metadata = {uuid: defined_disk["UUID"], name: disk_config.name, controller: controller.name, + port: disk_info[:port], device: disk_info[:device]} disk_metadata end @@ -336,8 +380,11 @@ module VagrantPlugins # move backup to original name FileUtils.mv(backup_disk_location, original_disk["Location"], force: true) # Attach disk - machine.provider.driver. - attach_disk(disk_info[:port], disk_info[:device], original_disk["Location"], "hdd") + machine.provider.driver.attach_disk(disk_info[:port], + disk_info[:device], + original_disk["Location"], + "hdd", + controller.name) # Remove cloned disk if still hanging around if vdi_disk_file diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index c994669c1..b6eb6eca6 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -26,18 +26,13 @@ module VagrantPlugins # @param [String] device - device on controller for disk # @param [String] file - disk file path # @param [String] type - type of disk to attach + # @param [String] controller_name - name of storage controller to attach disk to # @param [Hash] opts - additional options - def attach_disk(port, device, file, type="hdd", **opts) - if type == "hdd" - controller = get_controller('SATA') - else - controller = get_controller('IDE') - end - + def attach_disk(port, device, file, type, controller_name, **opts) comment = "This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant." execute('storageattach', @uuid, - '--storagectl', controller.name, + '--storagectl', controller_name, '--port', port.to_s, '--device', device.to_s, '--type', type, diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 5c1421f5f..902b54337 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1684,11 +1684,9 @@ en: proper controller to your guest using provider-specific customizations. virtualbox_disks_defined_exceed_limit: |- - VirtualBox only allows up to %{limit} disks to be attached to a single - guest using the %{storage_bus} controller, including the primary disk. - - Please ensure only up to %{limit} disks of type '%{type}' are - configured for your guest. + VirtualBox only allows up to %{limit} disks to be attached to the + storage controller '%{name}'. Please remove some disks from your disk + configuration, or attach a new storage controller. virtualbox_disks_primary_not_found: |- Vagrant was not able to detect a primary disk attached to the SATA controller. Vagrant Disks requires that the primary disk be attached diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 78a1d3436..c3dca4f0f 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -25,13 +25,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do double(:state) end - let(:sata_controller) { double("controller", name: "SATA Controller", - storage_bus: "SATA", maxportcount: 30, - limit: 30) } - - let(:ide_controller) { double("controller", name: "IDE Controller", - storage_bus: "IDE", maxportcount: 2, - limit: 4) } + let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} @@ -73,31 +67,46 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) - allow(sata_controller).to receive(:attachments).and_return(attachments) - - allow(driver).to receive(:get_controller).with("IDE").and_return(ide_controller) - allow(driver).to receive(:get_controller).with("SATA").and_return(sata_controller) - allow(driver).to receive(:storage_controllers).and_return([ide_controller, sata_controller]) + allow(controller).to receive(:attachments).and_return(attachments) + allow(driver).to receive(:storage_controllers).and_return([controller]) + allow(driver).to receive(:get_controller).with(controller.storage_bus).and_return(controller) end describe "#configure_disks" do let(:dsk_data) { {uuid: "1234", name: "disk"} } - it "configures disks and returns the disks defined" do - allow(driver).to receive(:list_hdds).and_return([]) + let(:dvd) { double("dvd", type: :dvd, name: "dvd", primary: false) } - expect(subject).to receive(:handle_configure_disk).exactly(4).and_return(dsk_data) + before do + allow(driver).to receive(:list_hdds).and_return([]) + end + + it "configures disks and returns the disks defined" do + expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller). + exactly(4).and_return(dsk_data) + subject.configure_disks(machine, defined_disks) + end + + it "configures dvd and returns the disks defined" do + defined_disks = [ dvd ] + + expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller). + and_return({}) subject.configure_disks(machine, defined_disks) end context "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 + # NOTE: In this scenario, one slot must be reserved for the primary + # disk, so the controller limit goes down by 1 when there is no primary + # disk defined in the config. context "with over the disk limit for a given device" do - let(:defined_disks) { (1..31).map { |i| double("disk-#{i}", type: :disk) }.to_a } + let(:defined_disks) { (1..controller.limit).map { |i| double("disk-#{i}", type: :disk, primary: false) }.to_a } it "raises an exception if the disks defined exceed the limit for a SATA Controller" do expect{subject.configure_disks(machine, defined_disks)}. @@ -105,61 +114,51 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end - context "no SATA controller" do + context "with more than one storage controller" do + let(:controller1) { double("controller1", storage_bus: "IDE", limit: 4) } + let(:controller2) { double("controller2", storage_bus: "SATA", limit: 30) } + before do - allow(driver).to receive(:get_controller).with("SATA"). - and_raise(Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA") + allow(driver).to receive(:storage_controllers).and_return([controller1, controller2]) + allow(driver).to receive(:get_controller).with(controller1.storage_bus).and_return(controller1) + allow(driver).to receive(:get_controller).with(controller2.storage_bus).and_return(controller2) end - it "raises an error" do - expect { subject.configure_disks(machine, defined_disks) }. - to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) - end - end - - context "with dvd type" do - let(:defined_disks) { [double("dvd", type: :dvd)] } - let(:dvd_data) { {uuid: "1234", name: "dvd"} } - - it "handles configuration of the dvd" do - allow(driver).to receive(:list_hdds).and_return([]) - expect(subject).to receive(:handle_configure_dvd).and_return(dvd_data) + it "attaches disks to the SATA controller" do + expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller2). + exactly(4).and_return(dsk_data) subject.configure_disks(machine, defined_disks) end - context "no IDE controller" do - before do - allow(driver).to receive(:get_controller).with("IDE"). - and_raise(Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "IDE") - end + it "attaches dvds to the IDE controller" do + defined_disks = [ dvd ] - it "raises an error" do - expect { subject.configure_disks(machine, defined_disks) }. - to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) - end + expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller1). + and_return({}) + subject.configure_disks(machine, defined_disks) end end end describe "#get_current_disk" do it "gets primary disk uuid if disk to configure is primary" do - primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks) + primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks, controller) expect(primary_disk).to eq(all_disks.first) end it "raises an error if primary disk can't be found" do - allow(sata_controller).to receive(:attachments).and_return([]) - expect { subject.get_current_disk(machine, defined_disks.first, all_disks) }. + allow(controller).to receive(:attachments).and_return([]) + expect { subject.get_current_disk(machine, defined_disks.first, all_disks, controller) }. to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end it "finds the disk to configure" do - disk = subject.get_current_disk(machine, defined_disks[1], all_disks) + disk = subject.get_current_disk(machine, defined_disks[1], all_disks, controller) 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) + disk = subject.get_current_disk(machine, defined_disks[3], all_disks, controller) expect(disk).to be_nil end end @@ -176,13 +175,23 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do "Capacity"=>"65536 MBytes", "Encryption"=>"disabled"}] } - let(:disk_meta) { {uuid: "67890", name: "disk-0"} } + let(:disk_meta) { {uuid: "67890", name: "disk-0", controller: "controller", port: "1", device: "1"} } it "creates a new disk if it doesn't yet exist" do - expect(subject).to receive(:create_disk).with(machine, defined_disks[1]) + expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller) .and_return(disk_meta) - subject.handle_configure_disk(machine, defined_disks[1], all_disks) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + end + + it "includes disk attachment info in metadata" do + expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller) + .and_return(disk_meta) + + disk_metadata = subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + expect(disk_metadata).to have_key(:controller) + expect(disk_metadata).to have_key(:port) + expect(disk_metadata).to have_key(:device) end end @@ -208,15 +217,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "resizes a disk" do expect(subject).to receive(:get_current_disk). - with(machine, defined_disks[1], all_disks).and_return(all_disks[1]) + with(machine, defined_disks[1], all_disks, controller).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) + with(machine, defined_disks[1], all_disks[1], controller).and_return({}) - subject.handle_configure_disk(machine, defined_disks[1], all_disks) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) end end @@ -244,7 +253,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "reattaches disk if vagrant defined disk exists but is not attached to guest" do expect(subject).to receive(:get_current_disk). - with(machine, defined_disks[1], all_disks).and_return(all_disks[1]) + with(machine, defined_disks[1], all_disks, controller).and_return(all_disks[1]) expect(subject).to receive(:compare_disk_size). with(machine, defined_disks[1], all_disks[1]).and_return(false) @@ -254,14 +263,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:attach_disk).with((disk_info[:port].to_i + 1).to_s, disk_info[:device], - all_disks[1]["Location"]) + all_disks[1]["Location"], + controller.name) - subject.handle_configure_disk(machine, defined_disks[1], all_disks) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) end 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]) + with(machine, defined_disks[1], all_disks, controller).and_return(all_disks[1]) expect(subject).to receive(:compare_disk_size). with(machine, defined_disks[1], all_disks[1]).and_return(false) @@ -269,7 +279,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:get_port_and_device).with("67890"). and_return(disk_info) - subject.handle_configure_disk(machine, defined_disks[1], all_disks) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) end end end @@ -304,59 +314,24 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:create_disk). with(disk_file, disk_config.size, "VDI").and_return(disk_data) - expect(subject).to receive(:get_next_port).with(machine, sata_controller). + expect(subject).to receive(:get_next_port).with(machine, controller). and_return(port_and_device) expect(driver).to receive(:attach_disk).with(port_and_device[:port], port_and_device[:device], - disk_file) + disk_file, + controller.name) - subject.create_disk(machine, disk_config) + subject.create_disk(machine, disk_config, controller) end end describe ".get_next_port" do it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine, sata_controller) + dsk_info = subject.get_next_port(machine, controller) expect(dsk_info[:port]).to eq("2") expect(dsk_info[:device]).to eq("0") end - - context "guest with an IDE controller" do - let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, - {port: "0", device: "1", uuid: "67890"}] } - - before do - allow(ide_controller).to receive(:attachments).and_return(attachments) - end - - it "determines the next available port and device to use" do - dsk_info = subject.get_next_port(machine, ide_controller) - expect(dsk_info[:port]).to eq("1") - expect(dsk_info[:device]).to eq("0") - end - - context "that is full" do - let(:attachments) { [{port: "0", device: "0", uuid: "11111"}, - {port: "0", device: "1", uuid: "22222"}, - {port: "1", device: "0", uuid: "33333"}, - {port: "1", device: "1", uuid: "44444"}] } - - it "raises an error" do - expect { subject.get_next_port(machine, ide_controller) } - .to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) - end - end - end - - context "unsupported storage controller" do - let(:controller) { double("controller", name: "Adaptec ACB-4000A", storage_bus: "SASI") } - - it "raises an error" do - expect { subject.get_next_port(machine, controller) } - .to raise_error(Vagrant::Errors::VirtualBoxDisksUnsupportedController) - end - end end describe "#resize_disk" do @@ -378,10 +353,9 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:vmdk_to_vdi).with(all_disks[0]["Location"]). and_return(vdi_disk_file) - expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i). - and_return(true) + expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], sata_controller.name). + expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], controller.name). and_return(true) expect(driver).to receive(:close_medium).with("12345") @@ -389,7 +363,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do and_return(vmdk_disk_file) expect(driver).to receive(:attach_disk). - with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd").and_return(true) + with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller.name).and_return(true) expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) expect(driver).to receive(:list_hdds).and_return(all_disks) @@ -397,7 +371,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(FileUtils).to receive(:remove).with("#{vmdk_disk_file}.backup", force: true). and_return(true) - subject.resize_disk(machine, disk_config, all_disks[0]) + subject.resize_disk(machine, disk_config, all_disks[0], controller) end it "reattaches original disk if something goes wrong" do @@ -410,10 +384,9 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:vmdk_to_vdi).with(all_disks[0]["Location"]). and_return(vdi_disk_file) - expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i). - and_return(true) + expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], sata_controller.name). + expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], controller.name). and_return(true) expect(driver).to receive(:close_medium).with("12345") @@ -423,10 +396,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do and_return(true) expect(driver).to receive(:attach_disk). - with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd").and_return(true) + with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller).and_return(true) expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) - expect{subject.resize_disk(machine, disk_config, all_disks[0])}.to raise_error(Exception) + expect{subject.resize_disk(machine, disk_config, all_disks[0], controller)}.to raise_error(Exception) end end @@ -437,7 +410,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "resizes the disk" do expect(driver).to receive(:resize_disk).with(all_disks[1]["Location"], disk_config.size.to_i) - subject.resize_disk(machine, disk_config, all_disks[1]) + expect(driver).to receive(:get_port_and_device).with(all_disks[1]["UUID"]). + and_return({port: "1", device: "0"}) + + subject.resize_disk(machine, disk_config, all_disks[1], controller) end end end @@ -456,18 +432,21 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } before do - allow(subject).to receive(:get_next_port).with(machine, ide_controller). + allow(subject).to receive(:get_next_port).with(machine, controller). and_return({device: "0", port: "0"}) - allow(ide_controller).to receive(:attachments).and_return( + allow(controller).to receive(:attachments).and_return( [port: "0", device: "0", uuid: "12345"] ) end - it "returns the UUID of the newly-attached dvd" do - expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive") + it "includes disk attachment info in metadata" do + expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive", controller.name) - disk_meta = subject.handle_configure_dvd(machine, dvd_config) - expect(disk_meta[:uuid]).to eq("12345") + dvd_metadata = subject.handle_configure_dvd(machine, dvd_config, controller) + expect(dvd_metadata[:uuid]).to eq("12345") + expect(dvd_metadata).to have_key(:controller) + expect(dvd_metadata).to have_key(:port) + expect(dvd_metadata).to have_key(:device) end end end From dfd3bc915cc709e9ab79dfa42d683c3a6d721a22 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 15 Jun 2020 17:42:55 -0400 Subject: [PATCH 22/42] Remove disks based on metadata This commit changes the disk_cleanup behavior to remove disks based on what has been recorded in the disk_meta file. --- .../providers/virtualbox/cap/cleanup_disks.rb | 32 +++++++----- .../virtualbox/cap/configure_disks.rb | 44 ++++++++-------- plugins/providers/virtualbox/driver/meta.rb | 2 +- .../virtualbox/driver/version_5_0.rb | 5 +- .../virtualbox/cap/cleanup_disks_test.rb | 50 +++++-------------- .../virtualbox/cap/configure_disks_test.rb | 15 +++++- .../virtualbox/driver/version_5_0_test.rb | 47 ++++++++--------- 7 files changed, 94 insertions(+), 101 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index ec95c604d..f081dc7fe 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -26,8 +26,17 @@ module VagrantPlugins # @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) - controller = machine.provider.driver.get_controller('SATA') - primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } + storage_controllers = machine.provider.driver.read_storage_controllers + if storage_controllers.size == 1 + primary_controller = storage_controllers.first + else + primary_controller = storage_controllers.detect { |c| c.storage_bus == "SATA" } + if primary_controller.nil? + # raise exception + end + end + + primary = primary_controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } if primary.nil? raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound end @@ -40,15 +49,16 @@ module VagrantPlugins next else 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(I18n.t("vagrant.cap.cleanup_disks.disk_cleanup", name: d["name"]), prefix: true) - if disk_info.empty? + controller = storage_controllers.detect { |c| c.name == d["controller"] } + disk_info = controller.attachments.detect { |a| a[:port] == d["port"] && + a[:device] == d["device"] } + + if disk_info.nil? LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else - # TODO: write test for sata controller with another name - machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device], controller.name) + machine.provider.driver.remove_disk(d["port"], d["device"], d["controller"]) end machine.provider.driver.close_medium(d["uuid"]) @@ -62,18 +72,14 @@ module VagrantPlugins # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) if dvd_meta - controller = machine.provider.driver.get_controller('IDE') dvd_meta.each do |d| dsk = defined_dvds.select { |dk| dk.name == d["name"] } if !dsk.empty? next else LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") - attachments = controller.attachments.select { |a| a[:uuid] == d["uuid"] } - attachments.each do |attachment| - machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - machine.provider.driver.remove_disk(attachment[:port].to_s, attachment[:device].to_s, controller.name) - end + machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) + machine.provider.driver.remove_disk(d["port"], d["device"], d["controller"]) end end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 0dbc46aa0..cb00dcb4b 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -19,7 +19,7 @@ module VagrantPlugins machine.ui.info(I18n.t("vagrant.cap.configure_disks.start")) - storage_controllers = machine.provider.driver.storage_controllers + storage_controllers = machine.provider.driver.read_storage_controllers # Check to determine which controller we should attach disks to. # If there is only one storage controller attached to the VM, use @@ -32,7 +32,7 @@ module VagrantPlugins # exactly one disk is a primary disk, otherwise we need to reserve # a slot for the primary if (defined_disks.any? { |d| d.primary } && defined_disks.size > controller.limit) || - defined_disks.size > controller.limit - 1 + defined_disks.size > controller.limit - 1 raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: controller.limit, name: controller.name @@ -43,9 +43,9 @@ module VagrantPlugins else disks_defined = defined_disks.select { |d| d.type == :disk } if disks_defined.any? - disk_controller = machine.provider.driver.get_controller('SATA') + disk_controller = machine.provider.driver.get_controller("SATA") if (disks_defined.any? { |d| d.primary } && disks_defined.size > disk_controller.limit) || - disks_defined.size > disk_controller.limit - 1 + disks_defined.size > disk_controller.limit - 1 raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: disk_controller.limit, name: disk_controller.name @@ -54,7 +54,7 @@ module VagrantPlugins dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? - dvd_controller = machine.provider.driver.get_controller('IDE') + dvd_controller = machine.provider.driver.get_controller("IDE") if disks_defined.size > dvd_controller.limit raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: dvd_controller.limit, @@ -65,7 +65,7 @@ module VagrantPlugins current_disks = machine.provider.driver.list_hdds - configured_disks = {disk: [], floppy: [], dvd: []} + configured_disks = { disk: [], floppy: [], dvd: [] } defined_disks.each do |disk| if disk.type == :disk @@ -105,7 +105,7 @@ module VagrantPlugins current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first else - current_disk = all_disks.select { |d| d["Disk Name"] == disk.name}.first + current_disk = all_disks.select { |d| d["Disk Name"] == disk.name }.first end current_disk @@ -141,6 +141,7 @@ module VagrantPlugins machine.provider.driver.attach_disk(dsk_info[:port], dsk_info[:device], current_disk["Location"], + "hdd", controller.name) disk_metadata[:port] = dsk_info[:port] disk_metadata[:device] = dsk_info[:device] @@ -174,11 +175,10 @@ module VagrantPlugins # Refresh the controller information controller = machine.provider.driver.get_controller(controller.storage_bus) - attachment = controller.attachments.detect { |a| a[:port] == port && - a[:device] == device } + attachment = controller.attachments.detect { |a| a[:port] == port && a[:device] == device } if attachment - {uuid: attachment[:uuid], name: dvd.name, controller: controller.name, port: port, device: device} + { uuid: attachment[:uuid], name: dvd.name, controller: controller.name, port: port, device: device } else {} end @@ -228,11 +228,12 @@ module VagrantPlugins machine.provider.driver.attach_disk(dsk_controller_info[:port], dsk_controller_info[:device], disk_file, + "hdd", controller.name) - disk_metadata = {uuid: disk_var.split(':').last.strip, name: disk_config.name, - controller: controller.name, port: dsk_controller_info[:port], - device: dsk_controller_info[:device]} + disk_metadata = { uuid: disk_var.split(":").last.strip, name: disk_config.name, + controller: controller.name, port: dsk_controller_info[:port], + device: dsk_controller_info[:device] } disk_metadata end @@ -256,16 +257,16 @@ module VagrantPlugins def self.get_next_port(machine, controller) dsk_info = {} - if controller.storage_bus == 'SATA' + if controller.storage_bus == "SATA" used_ports = controller.attachments.map { |a| a[:port].to_i } - next_available_port = ((0..(controller.maxportcount-1)).to_a - used_ports).first + next_available_port = ((0..(controller.maxportcount - 1)).to_a - used_ports).first dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" - elsif controller.storage_bus == 'IDE' + elsif controller.storage_bus == "IDE" # IDE Controllers have primary/secondary devices, so find the first port # with an empty device - (0..(controller.maxportcount-1)).each do |port| + (0..(controller.maxportcount - 1)).each do |port| # Skip this port if it's full port_attachments = controller.attachments.select { |a| a[:port] == port.to_s } next if port_attachments.count == 2 @@ -278,6 +279,8 @@ module VagrantPlugins else dsk_info[:device] = "0" end + + break if dsk_info[:port] end else raise Vagrant::Errors::VirtualBoxDisksUnsupportedController, controller_name: controller.name @@ -333,7 +336,8 @@ module VagrantPlugins vmdk_disk_file = machine.provider.driver.vdi_to_vmdk(vdi_disk_file) machine.provider.driver.attach_disk(disk_info[:port], disk_info[:device], - vmdk_disk_file, "hdd", + vmdk_disk_file, + "hdd", controller.name) rescue ScriptError, SignalException, StandardError LOGGER.warn("Vagrant encountered an error while trying to resize a disk. Vagrant will now attempt to reattach and preserve the original disk...") @@ -359,8 +363,8 @@ module VagrantPlugins disk_info = machine.provider.driver.get_port_and_device(defined_disk["UUID"]) end - disk_metadata = {uuid: defined_disk["UUID"], name: disk_config.name, controller: controller.name, - port: disk_info[:port], device: disk_info[:device]} + disk_metadata = { uuid: defined_disk["UUID"], name: disk_config.name, controller: controller.name, + port: disk_info[:port], device: disk_info[:device] } disk_metadata end diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index ea9d4e720..c5f0104d5 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -134,6 +134,7 @@ module VagrantPlugins :read_machine_folder, :read_network_interfaces, :read_state, + :read_storage_controllers, :read_used_ports, :read_vms, :reconfig_host_only, @@ -147,7 +148,6 @@ module VagrantPlugins :share_folders, :ssh_port, :start, - :storage_controllers, :suspend, :vdi_to_vmdk, :verify!, diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index b6eb6eca6..69da6c022 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -932,9 +932,9 @@ module VagrantPlugins # current VM # # @return [Array] - def storage_controllers + def read_storage_controllers vm_info = show_vm_info - count = vm_info.count { |key, value| key.match(/^storagecontrollername/) } + count = vm_info.count { |key, value| key.match(/^storagecontrollername\d+$/) } (0..count - 1).map do |n| # basic controller metadata @@ -962,6 +962,7 @@ module VagrantPlugins # @param [String] storage_bus - for example, 'IDE' or 'SATA' # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller def get_controller(storage_bus) + storage_controllers = read_storage_controllers controller = storage_controllers.detect { |c| c.storage_bus == storage_bus } if controller.nil? raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: storage_bus diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 6eedf57db..6bdbf8c3b 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -30,21 +30,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:disk_meta_file) { {disk: [], floppy: [], dvd: []} } let(:defined_disks) { {} } - let(:sata_controller) { double("controller", name: "SATA Controller", storage_bus: "SATA", maxportcount: 30) } - let(:ide_controller) { double("controller", name: "IDE Controller", storage_bus: "IDE", maxportcount: 2) } - let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} - let(:storage_controllers) { [ controller ] } + let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) - allow(sata_controller).to receive(:attachments).and_return(attachments) - - allow(driver).to receive(:get_controller).with("IDE").and_return(ide_controller) - allow(driver).to receive(:get_controller).with("SATA").and_return(sata_controller) - allow(driver).to receive(:storage_controllers).and_return([ide_controller, sata_controller]) + allow(controller).to receive(:attachments).and_return(attachments) + allow(driver).to receive(:read_storage_controllers).and_return([controller]) end describe "#cleanup_disks" do @@ -65,7 +59,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end it "raises an error if primary disk can't be found" do - allow(sata_controller).to receive(:attachments).and_return([]) + allow(controller).to receive(:attachments).and_return([]) expect { subject.cleanup_disks(machine, defined_disks, disk_meta_file) }. to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end @@ -85,27 +79,22 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end describe "#handle_cleanup_disk" do - let(:disk_meta_file) { {disk: [{"uuid"=>"67890", "name"=>"storage"}], floppy: [], dvd: []} } + let(:disk_meta_file) { { disk: [{ "uuid" => "67890", "name" => "storage", "controller" => "controller", "port" => "1", "device" => "0" }], floppy: [], dvd: [] } } + let(:defined_disks) { [] } let(:device_info) { {port: "1", device: "0"} } it "removes and closes medium from guest" do - allow(driver).to receive(:get_port_and_device). - with("67890"). - and_return(device_info) - - expect(driver).to receive(:remove_disk).with("1", "0", sata_controller.name).and_return(true) + expect(driver).to receive(:remove_disk).with("1", "0", "controller").and_return(true) expect(driver).to receive(:close_medium).with("67890").and_return(true) subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) end context "when the disk isn't attached to a guest" do - it "only closes the medium" do - allow(driver).to receive(:get_port_and_device). - with("67890"). - and_return({}) + let(:attachments) { [{port: "0", device: "0", uuid: "12345"}] } + it "only closes the medium" do expect(driver).to receive(:close_medium).with("67890").and_return(true) subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) @@ -116,29 +105,14 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do describe "#handle_cleanup_dvd" do let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } - let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso"}]} } + let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso", "port" => "0", "device" => "0", "controller" => "controller" }]} } + let(:defined_disks) { [] } - before do - allow(ide_controller).to receive(:attachments).and_return(attachments) - end - it "removes the medium from guest" do - expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) + expect(driver).to receive(:remove_disk).with("0", "0", "controller").and_return(true) subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) end - - context "multiple copies of the same ISO attached" do - let(:attachments) { [{port: "0", device: "0", uuid: "1234"}, - {port: "0", device: "1", uuid: "1234"}] } - - it "removes all media with that UUID" do - expect(driver).to receive(:remove_disk).with("0", "0", "IDE Controller").and_return(true) - expect(driver).to receive(:remove_disk).with("0", "1", "IDE Controller").and_return(true) - - subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) - end - end end end diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index c3dca4f0f..616c51ee3 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -68,7 +68,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) - allow(driver).to receive(:storage_controllers).and_return([controller]) + allow(driver).to receive(:read_storage_controllers).and_return([controller]) allow(driver).to receive(:get_controller).with(controller.storage_bus).and_return(controller) end @@ -119,7 +119,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:controller2) { double("controller2", storage_bus: "SATA", limit: 30) } before do - allow(driver).to receive(:storage_controllers).and_return([controller1, controller2]) + allow(driver).to receive(:read_storage_controllers).and_return([controller1, controller2]) allow(driver).to receive(:get_controller).with(controller1.storage_bus).and_return(controller1) allow(driver).to receive(:get_controller).with(controller2.storage_bus).and_return(controller2) end @@ -332,6 +332,17 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(dsk_info[:port]).to eq("2") expect(dsk_info[:device]).to eq("0") end + + context "with empty IDE controller" do + let(:empty_controller) { double("controller", storage_bus: "IDE", attachments: [], maxportcount: 2) } + + it "attaches to port 0, device 0" do + dsk_info = subject.get_next_port(machine, empty_controller) + expect(dsk_info[:port]).to eq("0") + expect(dsk_info[:device]).to eq("0") + end + end + end describe "#resize_disk" do diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index f441afbf6..027d495fa 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -5,6 +5,7 @@ describe VagrantPlugins::ProviderVirtualBox::Driver::Version_5_0 do include_context "virtualbox" let(:vbox_version) { "5.0.0" } + let(:controller_name) { "controller" } subject { VagrantPlugins::ProviderVirtualBox::Driver::Version_5_0.new(uuid) } @@ -92,18 +93,12 @@ OUTPUT end describe "#attach_disk" do - let(:controller) { double("controller", name: "IDE Controller", storage_bus: "IDE") } - - before do - allow(subject).to receive(:get_controller).with(controller.storage_bus).and_return(controller) - end - - it "attaches a dvddrive device to the IDE controller" do + it "attaches a device to the specified controller" do expect(subject).to receive(:execute) do |*args| storagectl = args[args.index("--storagectl") + 1] - expect(storagectl).to eq("IDE Controller") + expect(storagectl).to eq(controller_name) end - subject.attach_disk(anything, anything, anything, "dvddrive") + subject.attach_disk(anything, anything, anything, "dvddrive", controller_name) end end @@ -111,34 +106,36 @@ OUTPUT it "removes a disk from the specified controller" do expect(subject).to receive(:execute) do |*args| storagectl = args[args.index("--storagectl") + 1] - expect(storagectl).to eq("IDE Controller") + expect(storagectl).to eq(controller_name) end - subject.remove_disk(anything, anything, "IDE Controller") + subject.remove_disk(anything, anything, controller_name) end end - describe "#storage_controllers" do + describe "#read_storage_controllers" do before do allow(subject).to receive(:show_vm_info).and_return( - {"storagecontrollername0" => "SATA Controller", - "storagecontrollertype0" => "IntelAhci", - "storagecontrollermaxportcount0" => "30", - "SATA Controller-ImageUUID-0-0" => "12345", - "SATA Controller-ImageUUID-1-0" => "67890"} + { "storagecontrollername0" => "SATA Controller", + "storagecontrollertype0" => "IntelAhci", + "storagecontrollermaxportcount0" => "30", + "SATA Controller-ImageUUID-0-0" => "12345", + "SATA Controller-ImageUUID-1-0" => "67890" } ) end - it "returns the storage controllers" do - expect(subject.storage_controllers.first.name).to eq("SATA Controller") - expect(subject.storage_controllers.first.type).to eq("IntelAhci") - expect(subject.storage_controllers.first.maxportcount).to eq(30) + it "returns a list of storage controllers" do + storage_controllers = subject.read_storage_controllers + + expect(storage_controllers.first.name).to eq("SATA Controller") + expect(storage_controllers.first.type).to eq("IntelAhci") + expect(storage_controllers.first.maxportcount).to eq(30) end it "includes attachments for each storage controller" do - expect(subject.storage_controllers.first.attachments) - .to include(port: "0", device: "0", uuid: "12345") - expect(subject.storage_controllers.first.attachments) - .to include(port: "1", device: "0", uuid: "67890") + storage_controllers = subject.read_storage_controllers + + expect(storage_controllers.first.attachments).to include(port: "0", device: "0", uuid: "12345") + expect(storage_controllers.first.attachments).to include(port: "1", device: "0", uuid: "67890") end end end From ff53c64fbc86fac843ee5bbb0f5bf49e6e4b8b9e Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 17 Jun 2020 13:50:06 -0400 Subject: [PATCH 23/42] Fix test, add test .recover_from_resize --- .../virtualbox/cap/configure_disks.rb | 10 +++--- .../virtualbox/cap/configure_disks_test.rb | 33 +++++++++++++++---- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index cb00dcb4b..124c04e45 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -306,11 +306,12 @@ module VagrantPlugins def self.resize_disk(machine, disk_config, defined_disk, controller) machine.ui.detail(I18n.t("vagrant.cap.configure_disks.resize_disk", name: disk_config.name), prefix: true) + # grab disk to be resized port and device number + disk_info = machine.provider.driver.get_port_and_device(defined_disk["UUID"]) + if defined_disk["Storage format"] == "VMDK" LOGGER.warn("Disk type VMDK cannot be resized in VirtualBox. Vagrant will convert disk to VDI format to resize first, and then convert resized disk back to VMDK format") - # grab disk to be resized port and device number - disk_info = machine.provider.driver.get_port_and_device(defined_disk["UUID"]) # original disk information in case anything goes wrong during clone/resize original_disk = defined_disk backup_disk_location = "#{original_disk["Location"]}.backup" @@ -345,7 +346,6 @@ module VagrantPlugins location: original_disk["Location"], name: machine.name)) recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file, controller) - raise ensure # Remove backup disk file if all goes well @@ -360,7 +360,6 @@ module VagrantPlugins defined_disk = new_disk_info else machine.provider.driver.resize_disk(defined_disk["Location"], disk_config.size.to_i) - disk_info = machine.provider.driver.get_port_and_device(defined_disk["UUID"]) end disk_metadata = { uuid: defined_disk["UUID"], name: disk_config.name, controller: controller.name, @@ -379,7 +378,8 @@ module VagrantPlugins # @param [String] backup_disk_location - The place on disk where vagrant made a backup of the original disk being resized # @param [Hash] original_disk - The disk information from VirtualBox # @param [String] vdi_disk_file - The place on disk where vagrant made a clone of the original disk being resized - def self.recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file) + # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - the storage controller to use + def self.recover_from_resize(machine, disk_info, backup_disk_location, original_disk, vdi_disk_file, controller) begin # move backup to original name FileUtils.mv(backup_disk_location, original_disk["Location"], force: true) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 616c51ee3..94c88c7db 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -108,7 +108,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do context "with over the disk limit for a given device" do let(:defined_disks) { (1..controller.limit).map { |i| double("disk-#{i}", type: :disk, primary: false) }.to_a } - it "raises an exception if the disks defined exceed the limit for a SATA Controller" do + it "raises an exception if the disks defined exceed the limit" do expect{subject.configure_disks(machine, defined_disks)}. to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) end @@ -264,6 +264,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:attach_disk).with((disk_info[:port].to_i + 1).to_s, disk_info[:device], all_disks[1]["Location"], + "hdd", controller.name) subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) @@ -320,6 +321,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:attach_disk).with(port_and_device[:port], port_and_device[:device], disk_file, + "hdd", controller.name) subject.create_disk(machine, disk_config, controller) @@ -403,12 +405,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(driver).to receive(:vdi_to_vmdk).and_raise(StandardError) - expect(FileUtils).to receive(:mv).with("#{vmdk_disk_file}.backup", vmdk_disk_file, force: true). - and_return(true) - - expect(driver).to receive(:attach_disk). - with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller).and_return(true) - expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) + expect(subject).to receive(:recover_from_resize).with(machine, attach_info, "#{vmdk_disk_file}.backup", all_disks[0], vdi_disk_file, controller) expect{subject.resize_disk(machine, disk_config, all_disks[0], controller)}.to raise_error(Exception) end @@ -439,6 +436,28 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end + describe ".recover_from_resize" do + let(:disk_config) { double("disk", name: "vagrant_primary", size: 1073741824.0, + primary: false, type: :disk, disk_ext: "vmdk", + provider_config: nil) } + let(:attach_info) { {port: "0", device: "0"} } + let(:vdi_disk_file) { "/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vdi" } + let(:vmdk_disk_file) { "/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk" } + let(:vmdk_backup_file) { "/home/vagrant/VirtualBox VMs/ubuntu-18.04-amd64-disk001.vmdk.backup" } + + it "reattaches the original disk file and closes the cloned medium" do + expect(FileUtils).to receive(:mv).with(vmdk_backup_file, vmdk_disk_file, force: true). + and_return(true) + + expect(driver).to receive(:attach_disk). + with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller.name).and_return(true) + + expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) + + subject.recover_from_resize(machine, attach_info, vmdk_backup_file, all_disks[0], vdi_disk_file, controller) + end + end + describe ".handle_configure_dvd" do let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } From f72ae72aaa73edba20491abd9af9645385766632 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 17 Jun 2020 15:53:34 -0400 Subject: [PATCH 24/42] Change method signature This commit changes the method signature of the VirtualBox driver methods so they mirror the underlying VBoxManage command. --- .../providers/virtualbox/cap/cleanup_disks.rb | 4 +-- .../virtualbox/cap/configure_disks.rb | 28 +++++++++---------- .../virtualbox/driver/version_5_0.rb | 10 +++---- .../virtualbox/cap/cleanup_disks_test.rb | 4 +-- .../virtualbox/cap/configure_disks_test.rb | 22 +++++++-------- .../virtualbox/driver/version_5_0_test.rb | 4 +-- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index f081dc7fe..04d506eb4 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -58,7 +58,7 @@ module VagrantPlugins if disk_info.nil? LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else - machine.provider.driver.remove_disk(d["port"], d["device"], d["controller"]) + machine.provider.driver.remove_disk(d["controller"], d["port"], d["device"]) end machine.provider.driver.close_medium(d["uuid"]) @@ -79,7 +79,7 @@ module VagrantPlugins else LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - machine.provider.driver.remove_disk(d["port"], d["device"], d["controller"]) + machine.provider.driver.remove_disk(d["controller"], d["port"], d["device"]) end end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 124c04e45..3d2d8d57e 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -138,11 +138,11 @@ module VagrantPlugins if disk_info.empty? LOGGER.warn("Disk '#{disk.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect disk to guest") dsk_info = get_next_port(machine, controller) - machine.provider.driver.attach_disk(dsk_info[:port], + machine.provider.driver.attach_disk(controller.name, + dsk_info[:port], dsk_info[:device], - current_disk["Location"], "hdd", - controller.name) + current_disk["Location"]) disk_metadata[:port] = dsk_info[:port] disk_metadata[:device] = dsk_info[:device] else @@ -171,7 +171,7 @@ module VagrantPlugins port = disk_info[:port] device = disk_info[:device] - machine.provider.driver.attach_disk(port, device, dvd.file, "dvddrive", controller.name) + machine.provider.driver.attach_disk(controller.name, port, device, "dvddrive", dvd.file) # Refresh the controller information controller = machine.provider.driver.get_controller(controller.storage_bus) @@ -225,11 +225,11 @@ module VagrantPlugins disk_var = machine.provider.driver.create_disk(disk_file, disk_config.size, disk_ext.upcase) dsk_controller_info = get_next_port(machine, controller) - machine.provider.driver.attach_disk(dsk_controller_info[:port], + machine.provider.driver.attach_disk(controller.name, + dsk_controller_info[:port], dsk_controller_info[:device], - disk_file, "hdd", - controller.name) + disk_file) disk_metadata = { uuid: disk_var.split(":").last.strip, name: disk_config.name, controller: controller.name, port: dsk_controller_info[:port], @@ -324,7 +324,7 @@ module VagrantPlugins begin # Danger Zone # remove and close original volume - machine.provider.driver.remove_disk(disk_info[:port], disk_info[:device], controller.name) + machine.provider.driver.remove_disk(controller.name, disk_info[:port], disk_info[:device]) # Create a backup of the original disk if something goes wrong LOGGER.warn("Making a backup of the original disk at #{defined_disk["Location"]}") FileUtils.mv(defined_disk["Location"], backup_disk_location) @@ -335,11 +335,11 @@ module VagrantPlugins # clone back to original vmdk format and attach resized disk vmdk_disk_file = machine.provider.driver.vdi_to_vmdk(vdi_disk_file) - machine.provider.driver.attach_disk(disk_info[:port], + machine.provider.driver.attach_disk(controller.name, + disk_info[:port], disk_info[:device], - vmdk_disk_file, "hdd", - controller.name) + vmdk_disk_file) rescue ScriptError, SignalException, StandardError LOGGER.warn("Vagrant encountered an error while trying to resize a disk. Vagrant will now attempt to reattach and preserve the original disk...") machine.ui.error(I18n.t("vagrant.cap.configure_disks.recovery_from_resize", @@ -384,11 +384,11 @@ module VagrantPlugins # move backup to original name FileUtils.mv(backup_disk_location, original_disk["Location"], force: true) # Attach disk - machine.provider.driver.attach_disk(disk_info[:port], + machine.provider.driver.attach_disk(controller.name, + disk_info[:port], disk_info[:device], - original_disk["Location"], "hdd", - controller.name) + original_disk["Location"]) # Remove cloned disk if still hanging around if vdi_disk_file diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 69da6c022..7ba91516a 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -22,13 +22,13 @@ module VagrantPlugins # - Port: 0 # - Device: 0 # + # @param [String] controller_name - name of storage controller to attach disk to # @param [String] port - port on device to attach disk to # @param [String] device - device on controller for disk - # @param [String] file - disk file path # @param [String] type - type of disk to attach - # @param [String] controller_name - name of storage controller to attach disk to + # @param [String] file - disk file path # @param [Hash] opts - additional options - def attach_disk(port, device, file, type, controller_name, **opts) + def attach_disk(controller_name, port, device, type, file, **opts) comment = "This disk is managed externally by Vagrant. Removing or adjusting settings could potentially cause issues with Vagrant." execute('storageattach', @uuid, @@ -226,10 +226,10 @@ module VagrantPlugins raise end + # @param [String] controller_name - controller name to remove disk from # @param [String] port - port on device to attach disk to # @param [String] device - device on controller for disk - # @param [String] controller_name - controller name to remove disk from - def remove_disk(port, device, controller_name) + def remove_disk(controller_name, port, device) execute('storageattach', @uuid, '--storagectl', controller_name, '--port', port.to_s, diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 6bdbf8c3b..1cc11f061 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -85,7 +85,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:device_info) { {port: "1", device: "0"} } it "removes and closes medium from guest" do - expect(driver).to receive(:remove_disk).with("1", "0", "controller").and_return(true) + expect(driver).to receive(:remove_disk).with("controller", "1", "0").and_return(true) expect(driver).to receive(:close_medium).with("67890").and_return(true) subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) @@ -110,7 +110,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:defined_disks) { [] } it "removes the medium from guest" do - expect(driver).to receive(:remove_disk).with("0", "0", "controller").and_return(true) + expect(driver).to receive(:remove_disk).with("controller", "0", "0").and_return(true) subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) end diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 94c88c7db..497516c06 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -261,11 +261,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:get_port_and_device).with("67890"). and_return({}) - expect(driver).to receive(:attach_disk).with((disk_info[:port].to_i + 1).to_s, + expect(driver).to receive(:attach_disk).with(controller.name, + (disk_info[:port].to_i + 1).to_s, disk_info[:device], - all_disks[1]["Location"], "hdd", - controller.name) + all_disks[1]["Location"]) subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) end @@ -318,11 +318,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(subject).to receive(:get_next_port).with(machine, controller). and_return(port_and_device) - expect(driver).to receive(:attach_disk).with(port_and_device[:port], + expect(driver).to receive(:attach_disk).with(controller.name, + port_and_device[:port], port_and_device[:device], - disk_file, "hdd", - controller.name) + disk_file) subject.create_disk(machine, disk_config, controller) end @@ -368,7 +368,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], controller.name). + expect(driver).to receive(:remove_disk).with(controller.name, attach_info[:port], attach_info[:device]). and_return(true) expect(driver).to receive(:close_medium).with("12345") @@ -376,7 +376,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do and_return(vmdk_disk_file) expect(driver).to receive(:attach_disk). - with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller.name).and_return(true) + with(controller.name, attach_info[:port], attach_info[:device], "hdd", vmdk_disk_file).and_return(true) expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) expect(driver).to receive(:list_hdds).and_return(all_disks) @@ -399,7 +399,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:resize_disk).with(vdi_disk_file, disk_config.size.to_i).and_return(true) - expect(driver).to receive(:remove_disk).with(attach_info[:port], attach_info[:device], controller.name). + expect(driver).to receive(:remove_disk).with(controller.name, attach_info[:port], attach_info[:device]). and_return(true) expect(driver).to receive(:close_medium).with("12345") @@ -450,7 +450,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do and_return(true) expect(driver).to receive(:attach_disk). - with(attach_info[:port], attach_info[:device], vmdk_disk_file, "hdd", controller.name).and_return(true) + with(controller.name, attach_info[:port], attach_info[:device], "hdd", vmdk_disk_file).and_return(true) expect(driver).to receive(:close_medium).with(vdi_disk_file).and_return(true) @@ -470,7 +470,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end it "includes disk attachment info in metadata" do - expect(driver).to receive(:attach_disk).with("0", "0", "/tmp/untitled.iso", "dvddrive", controller.name) + expect(driver).to receive(:attach_disk).with(controller.name, "0", "0", "dvddrive", "/tmp/untitled.iso") dvd_metadata = subject.handle_configure_dvd(machine, dvd_config, controller) expect(dvd_metadata[:uuid]).to eq("12345") diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index 027d495fa..5a44fc423 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -98,7 +98,7 @@ OUTPUT storagectl = args[args.index("--storagectl") + 1] expect(storagectl).to eq(controller_name) end - subject.attach_disk(anything, anything, anything, "dvddrive", controller_name) + subject.attach_disk(controller_name, anything, anything, anything, anything) end end @@ -108,7 +108,7 @@ OUTPUT storagectl = args[args.index("--storagectl") + 1] expect(storagectl).to eq(controller_name) end - subject.remove_disk(anything, anything, controller_name) + subject.remove_disk(controller_name, anything, anything) end end From a4a082e70efa848bd737076b47e4d11ec7ec9aab Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 17 Jun 2020 16:20:35 -0400 Subject: [PATCH 25/42] Prevent multiple calls to #read_storage_controllers --- .../providers/virtualbox/cap/cleanup_disks.rb | 2 +- .../providers/virtualbox/cap/configure_disks.rb | 15 ++++++++++++--- .../providers/virtualbox/driver/version_5_0.rb | 16 ---------------- .../virtualbox/cap/cleanup_disks_test.rb | 14 +++++++++++++- .../virtualbox/cap/configure_disks_test.rb | 3 --- 5 files changed, 26 insertions(+), 24 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 04d506eb4..b03883bd2 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -32,7 +32,7 @@ module VagrantPlugins else primary_controller = storage_controllers.detect { |c| c.storage_bus == "SATA" } if primary_controller.nil? - # raise exception + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA" end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 3d2d8d57e..d354423dc 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -43,7 +43,11 @@ module VagrantPlugins else disks_defined = defined_disks.select { |d| d.type == :disk } if disks_defined.any? - disk_controller = machine.provider.driver.get_controller("SATA") + disk_controller = storage_controllers.detect { |c| c.storage_bus == "SATA" } + if disk_controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA" + end + if (disks_defined.any? { |d| d.primary } && disks_defined.size > disk_controller.limit) || disks_defined.size > disk_controller.limit - 1 raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, @@ -54,7 +58,11 @@ module VagrantPlugins dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? - dvd_controller = machine.provider.driver.get_controller("IDE") + dvd_controller = storage_controllers.detect { |c| c.storage_bus == "IDE" } + if dvd_controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "IDE" + end + if disks_defined.size > dvd_controller.limit raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: dvd_controller.limit, @@ -174,7 +182,8 @@ module VagrantPlugins machine.provider.driver.attach_disk(controller.name, port, device, "dvddrive", dvd.file) # Refresh the controller information - controller = machine.provider.driver.get_controller(controller.storage_bus) + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.detect { |c| c.name == controller.name } attachment = controller.attachments.detect { |a| a[:port] == port && a[:device] == device } if attachment diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 7ba91516a..78d5d0384 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -954,22 +954,6 @@ module VagrantPlugins end end - # Get the controller that uses the specified storage bus. - # - # A VirtualBoxDisksControllerNotFound error is raised if a compatible - # storage controller cannot be found. - # - # @param [String] storage_bus - for example, 'IDE' or 'SATA' - # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - def get_controller(storage_bus) - storage_controllers = read_storage_controllers - controller = storage_controllers.detect { |c| c.storage_bus == storage_bus } - if controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: storage_bus - end - controller - end - protected def valid_ip_address?(ip) diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 1cc11f061..2ee853c13 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -35,10 +35,12 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } + let(:storage_controllers) { [controller] } + before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) - allow(driver).to receive(:read_storage_controllers).and_return([controller]) + allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) end describe "#cleanup_disks" do @@ -100,6 +102,16 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) end end + + context "with multiple storage controllers" do + let(:storage_controllers) { [ double("controller1", storage_bus: "IDE"), + double("controller2", storage_bus: "SCSI") ] } + + it "assumes that disks will be attached to the SATA controller" do + expect { subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end end describe "#handle_cleanup_dvd" do diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 497516c06..b99c4bc4f 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -69,7 +69,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) allow(driver).to receive(:read_storage_controllers).and_return([controller]) - allow(driver).to receive(:get_controller).with(controller.storage_bus).and_return(controller) end describe "#configure_disks" do @@ -120,8 +119,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(driver).to receive(:read_storage_controllers).and_return([controller1, controller2]) - allow(driver).to receive(:get_controller).with(controller1.storage_bus).and_return(controller1) - allow(driver).to receive(:get_controller).with(controller2.storage_bus).and_return(controller2) end it "attaches disks to the SATA controller" do From d71a5bf45323f2354257410c004633b2d325f0aa Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 17 Jun 2020 16:54:05 -0400 Subject: [PATCH 26/42] Fall back on finding disk attachment by UUID --- .../providers/virtualbox/cap/cleanup_disks.rb | 26 +++++++++++++++---- .../virtualbox/cap/cleanup_disks_test.rb | 26 ++++++++++++++++--- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index b03883bd2..707aa90e2 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -52,13 +52,18 @@ module VagrantPlugins machine.ui.warn(I18n.t("vagrant.cap.cleanup_disks.disk_cleanup", name: d["name"]), prefix: true) controller = storage_controllers.detect { |c| c.name == d["controller"] } - disk_info = controller.attachments.detect { |a| a[:port] == d["port"] && - a[:device] == d["device"] } + attachment = controller.attachments.detect { |a| a[:port] == d["port"] && + a[:device] == d["device"] } - if disk_info.nil? + # Retry lookup by UUID + if attachment.nil? + attachment = controller.attachments.detect { |a| a[:uuid] == d["uuid"] } + end + + if attachment.nil? LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else - machine.provider.driver.remove_disk(d["controller"], d["port"], d["device"]) + machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end machine.provider.driver.close_medium(d["uuid"]) @@ -79,7 +84,18 @@ module VagrantPlugins else LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - machine.provider.driver.remove_disk(d["controller"], d["port"], d["device"]) + + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.detect { |c| c.name == d["controller"] } + attachment = controller.attachments.detect { |a| a[:port] == d["port"] && + a[:device] == d["device"] } + + # Retry lookup by UUID + if attachment.nil? + attachment = controller.attachments.detect { |a| a[:uuid] == d["uuid"] } + end + + machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end end end diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 2ee853c13..8280acc50 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -84,7 +84,6 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:disk_meta_file) { { disk: [{ "uuid" => "67890", "name" => "storage", "controller" => "controller", "port" => "1", "device" => "0" }], floppy: [], dvd: [] } } let(:defined_disks) { [] } - let(:device_info) { {port: "1", device: "0"} } it "removes and closes medium from guest" do expect(driver).to receive(:remove_disk).with("controller", "1", "0").and_return(true) @@ -112,12 +111,23 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) end end + + context "when attachment is not found at the expected device" do + let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, + {port: "2", device: "0", uuid: "67890"}] } + + it "removes the disk from the correct device" do + expect(driver).to receive(:remove_disk).with("controller", "2", "0").and_return(true) + expect(driver).to receive(:close_medium).with("67890").and_return(true) + + subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) + end + end end describe "#handle_cleanup_dvd" do - let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } - let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso", "port" => "0", "device" => "0", "controller" => "controller" }]} } + let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } let(:defined_disks) { [] } @@ -126,5 +136,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) end + + context "when attachment is not found at the expected device" do + let(:attachments) { [{port: "0", device: "1", uuid: "1234"}] } + + it "removes the disk from the correct device" do + expect(driver).to receive(:remove_disk).with("controller", "0", "1").and_return(true) + + subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) + end + end end end From 3c0021aac3e57d9ba8dc942820a5a7e4f5524f46 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 17 Jun 2020 16:55:45 -0400 Subject: [PATCH 27/42] Use #read_storage_controllers for #get_port_and_device This preserves the existing behavior of the method but changes the implementation to use #read_storage_controllers. The caps that call a mix of #get_port_and_device and #read_storage_controllers may be a candidate for further refactoring, but this makes sure that we're fetching storage attachments consistently. --- .../virtualbox/driver/version_5_0.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 78d5d0384..1e01014c0 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -393,20 +393,21 @@ module VagrantPlugins # Returns port and device for an attached disk given a disk uuid. Returns # empty hash if disk is not attachd to guest # - # @param [Hash] vm_info - A guests information from vboxmanage # @param [String] disk_uuid - the UUID for the disk we are searching for # @return [Hash] disk_info - Contains a device and port number def get_port_and_device(disk_uuid) - vm_info = show_vm_info - disk = {} - disk_info_key = vm_info.key(disk_uuid) - return disk if !disk_info_key - disk_info = disk_info_key.split("-") - - disk[:port] = disk_info[2] - disk[:device] = disk_info[3] + storage_controllers = read_storage_controllers + storage_controllers.each do |controller| + controller.attachments.each do |attachment| + if disk_uuid == attachment[:uuid] + disk[:port] = attachment[:port] + disk[:device] = attachment[:device] + return disk + end + end + end return disk end From 724687a6012cd210fbe5c038031c14f1886c536b Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 24 Jun 2020 16:52:55 -0400 Subject: [PATCH 28/42] Fix stale controller state This was causing multiple dvds to be attached to the same port/device. --- .../virtualbox/cap/configure_disks.rb | 24 +++++---- plugins/providers/virtualbox/driver/meta.rb | 2 +- .../virtualbox/driver/version_5_0.rb | 13 +++++ templates/locales/en.yml | 7 ++- .../virtualbox/cap/configure_disks_test.rb | 52 ++++++++++++++----- .../virtualbox/driver/version_5_0_test.rb | 25 +++++++++ 6 files changed, 95 insertions(+), 28 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index d354423dc..f2fec894f 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -63,7 +63,7 @@ module VagrantPlugins raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "IDE" end - if disks_defined.size > dvd_controller.limit + if dvds_defined.size > dvd_controller.limit raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, limit: dvd_controller.limit, name: dvd_controller.name @@ -77,13 +77,14 @@ module VagrantPlugins defined_disks.each do |disk| if disk.type == :disk - disk_data = handle_configure_disk(machine, disk, current_disks, disk_controller) + disk_data = handle_configure_disk(machine, disk, current_disks, disk_controller.name) 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 - dvd_data = handle_configure_dvd(machine, disk, dvd_controller) + # refresh controller state here + dvd_data = handle_configure_dvd(machine, disk, dvd_controller.name) configured_disks[:dvd] << dvd_data unless dvd_data.empty? end end @@ -124,10 +125,11 @@ module VagrantPlugins # @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 - # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - - # the storage controller to use + # @param [String] controller_name - the name of the storage controller to use # @return [Hash] - disk_metadata - def self.handle_configure_disk(machine, disk, all_disks, controller) + def self.handle_configure_disk(machine, disk, all_disks, controller_name) + controller = machine.provider.driver.get_storage_controller(controller_name) + disk_metadata = {} # Grab the existing configured disk, if it exists @@ -171,10 +173,11 @@ module VagrantPlugins # # @param [Vagrant::Machine] machine - the current machine # @param [Config::Disk] dvd - the current disk to configure - # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - - # the storage controller to use + # @param [String] controller_name - the name of the storage controller to use # @return [Hash] - dvd_metadata - def self.handle_configure_dvd(machine, dvd, controller) + def self.handle_configure_dvd(machine, dvd, controller_name) + controller = machine.provider.driver.get_storage_controller(controller_name) + disk_info = get_next_port(machine, controller) port = disk_info[:port] device = disk_info[:device] @@ -182,8 +185,7 @@ module VagrantPlugins machine.provider.driver.attach_disk(controller.name, port, device, "dvddrive", dvd.file) # Refresh the controller information - storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.detect { |c| c.name == controller.name } + controller = machine.provider.driver.get_storage_controller(controller.name) attachment = controller.attachments.detect { |a| a[:port] == port && a[:device] == device } if attachment diff --git a/plugins/providers/virtualbox/driver/meta.rb b/plugins/providers/virtualbox/driver/meta.rb index c5f0104d5..54e817a17 100644 --- a/plugins/providers/virtualbox/driver/meta.rb +++ b/plugins/providers/virtualbox/driver/meta.rb @@ -116,8 +116,8 @@ module VagrantPlugins :execute_command, :export, :forward_ports, - :get_controller, :get_port_and_device, + :get_storage_controller, :halt, :import, :list_snapshots, diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index 1e01014c0..a37af4c49 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -955,6 +955,19 @@ module VagrantPlugins end end + # Gets the storage controller matching the specified name. Raises an + # error if the storage controller can't be found. + # + # @param [String] name - storage controller name + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] + def get_storage_controller(name) + controller = read_storage_controllers.detect { |c| c.name == name } + if !controller + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: name + end + controller + end + protected def valid_ip_address?(ip) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 902b54337..87f6690ff 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1679,10 +1679,9 @@ en: Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade VirtualBox. virtualbox_disks_controller_not_found: |- - The current disk configuration requires a controller with storage bus - '%{storage_bus}', but no such controller was found. Please add the - proper controller to your guest using provider-specific - customizations. + Vagrant was not able to find a storage controller called '%{name}'. + If you have changed or removed any storage controllers, please restore + them to their prior configuration. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to %{limit} disks to be attached to the storage controller '%{name}'. Please remove some disks from your disk diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index b99c4bc4f..b50356a71 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -69,6 +69,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) allow(driver).to receive(:read_storage_controllers).and_return([controller]) + allow(driver).to receive(:get_storage_controller).with(controller.name).and_return(controller) end describe "#configure_disks" do @@ -80,7 +81,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end it "configures disks and returns the disks defined" do - expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller). + expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller.name). exactly(4).and_return(dsk_data) subject.configure_disks(machine, defined_disks) end @@ -88,7 +89,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "configures dvd and returns the disks defined" do defined_disks = [ dvd ] - expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller). + expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller.name). and_return({}) subject.configure_disks(machine, defined_disks) end @@ -113,16 +114,17 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end end + # hashicorp/bionic64 context "with more than one storage controller" do - let(:controller1) { double("controller1", storage_bus: "IDE", limit: 4) } - let(:controller2) { double("controller2", storage_bus: "SATA", limit: 30) } + let(:controller1) { double("controller1", name: "IDE Controller", storage_bus: "IDE", limit: 4) } + let(:controller2) { double("controller2", name: "SATA Controller", storage_bus: "SATA", limit: 30) } before do allow(driver).to receive(:read_storage_controllers).and_return([controller1, controller2]) end it "attaches disks to the SATA controller" do - expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller2). + expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller2.name). exactly(4).and_return(dsk_data) subject.configure_disks(machine, defined_disks) end @@ -130,10 +132,36 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "attaches dvds to the IDE controller" do defined_disks = [ dvd ] - expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller1). + expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller1.name). and_return({}) subject.configure_disks(machine, defined_disks) end + + it "raises an error if there are more than 4 dvds configured" do + defined_disks = [ + double("dvd", name: "installer1", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer2", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer3", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer4", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer5", type: :dvd, file: "installer.iso") + ] + + expect { subject.configure_disks(machine, defined_disks) }. + to raise_error(Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit) + end + + it "attaches multiple dvds" do + defined_disks = [ + double("dvd", name: "installer1", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer2", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer3", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer4", type: :dvd, file: "installer.iso"), + ] + + expect(subject).to receive(:handle_configure_dvd).exactly(4).times.and_return({}) + + subject.configure_disks(machine, defined_disks) + end end end @@ -178,14 +206,14 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller) .and_return(disk_meta) - subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller.name) end it "includes disk attachment info in metadata" do expect(subject).to receive(:create_disk).with(machine, defined_disks[1], controller) .and_return(disk_meta) - disk_metadata = subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + disk_metadata = subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller.name) expect(disk_metadata).to have_key(:controller) expect(disk_metadata).to have_key(:port) expect(disk_metadata).to have_key(:device) @@ -222,7 +250,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(subject).to receive(:resize_disk). with(machine, defined_disks[1], all_disks[1], controller).and_return({}) - subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller.name) end end @@ -264,7 +292,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do "hdd", all_disks[1]["Location"]) - subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller.name) end it "does nothing if all disks are properly configured" do @@ -277,7 +305,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(driver).to receive(:get_port_and_device).with("67890"). and_return(disk_info) - subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller) + subject.handle_configure_disk(machine, defined_disks[1], all_disks, controller.name) end end end @@ -469,7 +497,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "includes disk attachment info in metadata" do expect(driver).to receive(:attach_disk).with(controller.name, "0", "0", "dvddrive", "/tmp/untitled.iso") - dvd_metadata = subject.handle_configure_dvd(machine, dvd_config, controller) + dvd_metadata = subject.handle_configure_dvd(machine, dvd_config, controller.name) expect(dvd_metadata[:uuid]).to eq("12345") expect(dvd_metadata).to have_key(:controller) expect(dvd_metadata).to have_key(:port) diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index 5a44fc423..e44ecca10 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -138,4 +138,29 @@ OUTPUT expect(storage_controllers.first.attachments).to include(port: "1", device: "0", uuid: "67890") end end + + describe "#get_storage_controller" do + let(:storage_controllers) { [double("controller", name: "IDE Controller")] } + + before do + allow(subject).to receive(:read_storage_controllers).and_return(storage_controllers) + end + + it "refreshes the storage controller state" do + expect(subject).to receive(:read_storage_controllers) + + subject.get_storage_controller("IDE Controller") + end + + it "returns the storage controller matching the specified name" do + controller = subject.get_storage_controller("IDE Controller") + + expect(controller.name).to eq("IDE Controller") + end + + it "raises an exception if the storage controller can't be found" do + expect { subject.get_storage_controller("SCSI Controller") }. + to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end end From 84d2c38cc275de57852c0315f1b6320c7702cd2a Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 25 Jun 2020 11:08:51 -0400 Subject: [PATCH 29/42] Disallow multiple copies of the same ISO This makes disk/dvd behavior more consistent, and makes it easier to determine whether a dvd is already attached. --- plugins/kernel_v2/config/vm.rb | 4 +- .../virtualbox/cap/configure_disks.rb | 44 +++++++++++++------ .../virtualbox/driver/version_5_0.rb | 7 ++- test/unit/plugins/kernel_v2/config/vm_test.rb | 8 ---- .../virtualbox/cap/configure_disks_test.rb | 2 + .../virtualbox/driver/version_5_0_test.rb | 6 ++- 6 files changed, 45 insertions(+), 26 deletions(-) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index cd9e2e4d9..2f5a46fd8 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -900,7 +900,7 @@ module VagrantPlugins end # Validate disks - # Check if there is more than one primrary disk defined and throw an error + # Check if there is more than one primary disk defined and throw an error primary_disks = @disks.select { |d| d.primary && d.type == :disk } if primary_disks.size > 1 errors << I18n.t("vagrant.config.vm.multiple_primary_disks_error", @@ -914,7 +914,7 @@ module VagrantPlugins name: duplicate_names) end - disk_files = @disks.select { |d| d.type == :disk }.map { |d| d.file } + disk_files = @disks.map { |d| d.file } duplicate_files = disk_files.detect { |d| disk_files.count(d) > 1 } if duplicate_files && duplicate_files.size errors << I18n.t("vagrant.config.vm.multiple_disk_files_error", diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index f2fec894f..f6ae4b0f5 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -83,7 +83,6 @@ module VagrantPlugins # TODO: Write me machine.ui.info(I18n.t("vagrant.cap.configure_disks.floppy_not_supported", name: disk.name)) elsif disk.type == :dvd - # refresh controller state here dvd_data = handle_configure_dvd(machine, disk, dvd_controller.name) configured_disks[:dvd] << dvd_data unless dvd_data.empty? end @@ -176,23 +175,42 @@ module VagrantPlugins # @param [String] controller_name - the name of the storage controller to use # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd, controller_name) + dvd_metadata = {} + controller = machine.provider.driver.get_storage_controller(controller_name) - disk_info = get_next_port(machine, controller) - port = disk_info[:port] - device = disk_info[:device] + dvd_location = File.expand_path(dvd.file) + dvd_attached = controller.attachments.detect { |a| a[:location] == dvd_location } - machine.provider.driver.attach_disk(controller.name, port, device, "dvddrive", dvd.file) - - # Refresh the controller information - controller = machine.provider.driver.get_storage_controller(controller.name) - attachment = controller.attachments.detect { |a| a[:port] == port && a[:device] == device } - - if attachment - { uuid: attachment[:uuid], name: dvd.name, controller: controller.name, port: port, device: device } + if dvd_attached + LOGGER.info("No further configuration required for dvd '#{dvd.name}'") + dvd_metadata[:name] = dvd.name + dvd_metadata[:port] = dvd_attached[:port] + dvd_metadata[:device] = dvd_attached[:device] + dvd_metadata[:uuid] = dvd_attached[:uuid] + dvd_metadata[:controller] = controller.name else - {} + LOGGER.warn("DVD '#{dvd.name}' is not connected to guest '#{machine.name}', Vagrant will attempt to connect dvd to guest") + dsk_info = get_next_port(machine, controller) + machine.provider.driver.attach_disk(controller.name, + dsk_info[:port], + dsk_info[:device], + "dvddrive", + dvd.file) + + # Refresh the controller information + controller = machine.provider.driver.get_storage_controller(controller.name) + attachment = controller.attachments.detect { |a| a[:port] == dsk_info[:port] && + a[:device] == dsk_info[:device] } + + dvd_metadata[:name] = dvd.name + dvd_metadata[:port] = dsk_info[:port] + dvd_metadata[:device] = dsk_info[:device] + dvd_metadata[:uuid] = attachment[:uuid] + dvd_metadata[:controller] = controller.name end + + dvd_metadata end # Check to see if current disk is configured based on defined_disks diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index a37af4c49..ec075329c 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -947,7 +947,12 @@ module VagrantPlugins attachments = [] vm_info.each do |k, v| if /^#{name}-ImageUUID-(\d+)-(\d+)$/ =~ k - attachments << {port: $1.to_s, device: $2.to_s, uuid: v} + port = $1.to_s + device = $2.to_s + uuid = v + location = vm_info["#{name}-#{port}-#{device}"] + + attachments << {port: port, device: device, uuid: uuid, location: location} 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 952fc2d61..cf41addcb 100644 --- a/test/unit/plugins/kernel_v2/config/vm_test.rb +++ b/test/unit/plugins/kernel_v2/config/vm_test.rb @@ -630,14 +630,6 @@ describe VagrantPlugins::Kernel_V2::VMConfig do assert_invalid end - it "does not raise an error with duplicate dvd files" do - allow(File).to receive(:file?).with("bar.iso").and_return(true) - subject.disk(:dvd, name: "foo1", file: "bar.iso") - subject.disk(:dvd, name: "foo2", file: "bar.iso") - subject.finalize! - assert_valid - end - it "does not merge duplicate disks" do subject.disk(:disk, size: 1000, primary: false, name: "storage") subject.disk(:disk, size: 1000, primary: false, name: "backup") diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index b50356a71..f20e44da0 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -217,6 +217,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(disk_metadata).to have_key(:controller) expect(disk_metadata).to have_key(:port) expect(disk_metadata).to have_key(:device) + expect(disk_metadata).to have_key(:name) end end @@ -502,6 +503,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(dvd_metadata).to have_key(:controller) expect(dvd_metadata).to have_key(:port) expect(dvd_metadata).to have_key(:device) + expect(dvd_metadata).to have_key(:name) end end end diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index e44ecca10..573e08f1e 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -118,7 +118,9 @@ OUTPUT { "storagecontrollername0" => "SATA Controller", "storagecontrollertype0" => "IntelAhci", "storagecontrollermaxportcount0" => "30", + "SATA Controller-0-0" => "/tmp/primary.vdi", "SATA Controller-ImageUUID-0-0" => "12345", + "SATA Controller-1-0" => "/tmp/secondary.vdi", "SATA Controller-ImageUUID-1-0" => "67890" } ) end @@ -134,8 +136,8 @@ OUTPUT it "includes attachments for each storage controller" do storage_controllers = subject.read_storage_controllers - expect(storage_controllers.first.attachments).to include(port: "0", device: "0", uuid: "12345") - expect(storage_controllers.first.attachments).to include(port: "1", device: "0", uuid: "67890") + expect(storage_controllers.first.attachments).to include(port: "0", device: "0", uuid: "12345", location: "/tmp/primary.vdi") + expect(storage_controllers.first.attachments).to include(port: "1", device: "0", uuid: "67890", location: "/tmp/secondary.vdi") end end From 423e212f5876d681a883bf3c9c934b1a5ee78faa Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 26 Jun 2020 15:42:36 -0400 Subject: [PATCH 30/42] Create Storage Controller array This is an array-like object that's useful for answer questions about the storage controllers arragement as a whole. --- .../providers/virtualbox/cap/cleanup_disks.rb | 39 ++++------ .../virtualbox/cap/configure_disks.rb | 24 +++---- .../virtualbox/driver/version_5_0.rb | 10 ++- .../virtualbox/model/storage_controller.rb | 29 +++++--- .../model/storage_controller_array.rb | 58 +++++++++++++++ plugins/providers/virtualbox/plugin.rb | 1 + .../virtualbox/cap/cleanup_disks_test.rb | 32 +++------ .../virtualbox/cap/configure_disks_test.rb | 43 +++++++---- .../model/storage_controller_array_test.rb | 72 +++++++++++++++++++ .../storage_controller_test.rb | 0 10 files changed, 221 insertions(+), 87 deletions(-) create mode 100644 plugins/providers/virtualbox/model/storage_controller_array.rb create mode 100644 test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb rename test/unit/plugins/providers/virtualbox/{models => model}/storage_controller_test.rb (100%) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 707aa90e2..33e0efc10 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -27,17 +27,10 @@ module VagrantPlugins # @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) storage_controllers = machine.provider.driver.read_storage_controllers - if storage_controllers.size == 1 - primary_controller = storage_controllers.first - else - primary_controller = storage_controllers.detect { |c| c.storage_bus == "SATA" } - if primary_controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA" - end - end + primary_controller = storage_controllers.get_primary_controller - primary = primary_controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } - if primary.nil? + primary = primary_controller.get_attachment(port: "0", device: "0") + if !primary raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound end primary_uuid = primary[:uuid] @@ -51,16 +44,10 @@ module VagrantPlugins 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) - controller = storage_controllers.detect { |c| c.name == d["controller"] } - attachment = controller.attachments.detect { |a| a[:port] == d["port"] && - a[:device] == d["device"] } + controller = storage_controllers.get_controller!(name: d["controller"]) + attachment = controller.get_attachment(uuid: d["uuid"]) - # Retry lookup by UUID - if attachment.nil? - attachment = controller.attachments.detect { |a| a[:uuid] == d["uuid"] } - end - - if attachment.nil? + if !attachment LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") else machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) @@ -86,16 +73,14 @@ module VagrantPlugins machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.detect { |c| c.name == d["controller"] } - attachment = controller.attachments.detect { |a| a[:port] == d["port"] && - a[:device] == d["device"] } + controller = storage_controllers.get_controller!(name: d["controller"]) + attachment = controller.get_attachment(uuid: d["uuid"]) - # Retry lookup by UUID - if attachment.nil? - attachment = controller.attachments.detect { |a| a[:uuid] == d["uuid"] } + if !attachment + LOGGER.warn("DVD '#{d["name"]}' not attached to guest, but still exists.") + else + machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end - - machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end end end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index f6ae4b0f5..e9cd73870 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -43,10 +43,7 @@ module VagrantPlugins else disks_defined = defined_disks.select { |d| d.type == :disk } if disks_defined.any? - disk_controller = storage_controllers.detect { |c| c.storage_bus == "SATA" } - if disk_controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "SATA" - end + disk_controller = storage_controllers.get_primary_controller if (disks_defined.any? { |d| d.primary } && disks_defined.size > disk_controller.limit) || disks_defined.size > disk_controller.limit - 1 @@ -58,10 +55,7 @@ module VagrantPlugins dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? - dvd_controller = storage_controllers.detect { |c| c.storage_bus == "IDE" } - if dvd_controller.nil? - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, storage_bus: "IDE" - end + dvd_controller = storage_controllers.get_controller!(storage_bus: "IDE") if dvds_defined.size > dvd_controller.limit raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, @@ -106,7 +100,7 @@ module VagrantPlugins # We can't rely on the order of `all_disks`, as they will not # always come in port order, but primary is always Port 0 Device 0. primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } - if primary.nil? + if !primary raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound end primary_uuid = primary[:uuid] @@ -127,7 +121,8 @@ module VagrantPlugins # @param [String] controller_name - the name of the storage controller to use # @return [Hash] - disk_metadata def self.handle_configure_disk(machine, disk, all_disks, controller_name) - controller = machine.provider.driver.get_storage_controller(controller_name) + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.get_controller!(name: controller_name) disk_metadata = {} @@ -175,9 +170,10 @@ module VagrantPlugins # @param [String] controller_name - the name of the storage controller to use # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd, controller_name) - dvd_metadata = {} + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.get_controller!(name: controller_name) - controller = machine.provider.driver.get_storage_controller(controller_name) + dvd_metadata = {} dvd_location = File.expand_path(dvd.file) dvd_attached = controller.attachments.detect { |a| a[:location] == dvd_location } @@ -199,7 +195,9 @@ module VagrantPlugins dvd.file) # Refresh the controller information - controller = machine.provider.driver.get_storage_controller(controller.name) + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.get_controller!(name: controller_name) + attachment = controller.attachments.detect { |a| a[:port] == dsk_info[:port] && a[:device] == dsk_info[:device] } diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index ec075329c..f3ff3a160 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -932,12 +932,14 @@ module VagrantPlugins # Helper method to get a list of storage controllers added to the # current VM # - # @return [Array] + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray] def read_storage_controllers vm_info = show_vm_info count = vm_info.count { |key, value| key.match(/^storagecontrollername\d+$/) } - (0..count - 1).map do |n| + storage_controllers = Model::StorageControllerArray.new + + (0..count - 1).each do |n| # basic controller metadata name = vm_info["storagecontrollername#{n}"] type = vm_info["storagecontrollertype#{n}"] @@ -956,8 +958,10 @@ module VagrantPlugins end end - Model::StorageController.new(name, type, maxportcount, attachments) + storage_controllers << Model::StorageController.new(name, type, maxportcount, attachments) end + + storage_controllers end # Gets the storage controller matching the specified name. Raises an diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index abd4b8c7d..777caa0c5 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -4,7 +4,6 @@ module VagrantPlugins # Represents a storage controller for VirtualBox. Storage controllers # have a type, a name, and can have hard disks or optical drives attached. class StorageController - SATA_CONTROLLER_TYPES = ["IntelAhci"].map(&:freeze).freeze IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].map(&:freeze).freeze @@ -43,26 +42,40 @@ module VagrantPlugins attr_reader :attachments def initialize(name, type, maxportcount, attachments) - @name = name - @type = type + @name = name + @type = type if SATA_CONTROLLER_TYPES.include?(@type) - @storage_bus = 'SATA' + @storage_bus = "SATA" elsif IDE_CONTROLLER_TYPES.include?(@type) - @storage_bus = 'IDE' + @storage_bus = "IDE" else - @storage_bus = 'Unknown' + @storage_bus = "Unknown" end @maxportcount = maxportcount.to_i - if @storage_bus == 'IDE' + if @storage_bus == "IDE" @limit = @maxportcount * 2 else @limit = @maxportcount end attachments ||= [] - @attachments = attachments + @attachments = attachments + end + + # Get a single storage device, either by port/device address or by + # UUID. + # + # @param [Hash] opts - A hash of options to match + # @return [Hash] attachment - Attachment information + def get_attachment(opts = {}) + if opts[:port] && opts[:device] + @attachments.detect { |a| a[:port] == opts[:port] && + a[:device] == opts[:device] } + elsif opts[:uuid] + @attachments.detect { |a| a[:uuid] == opts[:uuid] } + end end end end diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb new file mode 100644 index 000000000..032b0bb70 --- /dev/null +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -0,0 +1,58 @@ +module VagrantPlugins + module ProviderVirtualBox + module Model + # A collection of storage controllers. Includes finder methods to look + # up a storage controller by given attributes. + class StorageControllerArray < Array + # Get a single controller matching the given options. + # + # @param [Hash] opts - A hash of attributes to match. + def get_controller(opts = {}) + if opts[:name] + detect { |c| c.name == opts[:name] } + elsif opts[:storage_bus] + detect { |c| c.storage_bus == opts[:storage_bus] } + end + end + + # Get a single controller matching the given options. Raise an + # exception if a matching controller can't be found. + # + # @param [Hash] opts - A hash of attributes to match. + def get_controller!(opts = {}) + controller = get_controller(opts) + if controller.nil? + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: opts[:name] + end + controller + end + + # Find the controller containing the primary disk (i.e. the boot disk). + def get_primary_controller + primary = nil + + if length == 1 + primary = first + else + ide_controller = get_controller(storage_bus: "IDE") + if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } + primary = ide_controller + else + primary = get_controller!(storage_bus: "SATA") + end + end + + primary + end + + private + + def hdd?(attachment) + ext = File.extname(attachment[:location]) + # TODO: hook into ValidateDiskExt capability + [".vdi", ".vmdk", ".vhd"].include?(ext) + end + end + end + end +end diff --git a/plugins/providers/virtualbox/plugin.rb b/plugins/providers/virtualbox/plugin.rb index eb62b009c..8a44e613e 100644 --- a/plugins/providers/virtualbox/plugin.rb +++ b/plugins/providers/virtualbox/plugin.rb @@ -90,6 +90,7 @@ module VagrantPlugins module Model autoload :ForwardedPort, File.expand_path("../model/forwarded_port", __FILE__) autoload :StorageController, File.expand_path("../model/storage_controller", __FILE__) + autoload :StorageControllerArray, File.expand_path("../model/storage_controller_array", __FILE__) end module Util diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 8280acc50..dcbf95274 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -35,11 +35,15 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } - let(:storage_controllers) { [controller] } + let(:storage_controllers) { double("storage controllers") } before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) - allow(controller).to receive(:attachments).and_return(attachments) + allow(controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(attachments[0]) + allow(controller).to receive(:get_attachment).with(uuid: "12345").and_return(attachments[0]) + allow(controller).to receive(:get_attachment).with(uuid: "67890").and_return(attachments[1]) + allow(storage_controllers).to receive(:get_controller!).and_return(controller) + allow(storage_controllers).to receive(:get_primary_controller).and_return(controller) allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) end @@ -61,7 +65,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end it "raises an error if primary disk can't be found" do - allow(controller).to receive(:attachments).and_return([]) + allow(controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(nil) expect { subject.cleanup_disks(machine, defined_disks, disk_meta_file) }. to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end @@ -93,30 +97,17 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end context "when the disk isn't attached to a guest" do - let(:attachments) { [{port: "0", device: "0", uuid: "12345"}] } - it "only closes the medium" do + allow(controller).to receive(:get_attachment).with(uuid: "67890").and_return(nil) expect(driver).to receive(:close_medium).with("67890").and_return(true) subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) end end - context "with multiple storage controllers" do - let(:storage_controllers) { [ double("controller1", storage_bus: "IDE"), - double("controller2", storage_bus: "SCSI") ] } - - it "assumes that disks will be attached to the SATA controller" do - expect { subject.handle_cleanup_disk(machine, defined_disks, disk_meta_file[:disk]) }. - to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) - end - end - context "when attachment is not found at the expected device" do - let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, - {port: "2", device: "0", uuid: "67890"}] } - it "removes the disk from the correct device" do + allow(controller).to receive(:get_attachment).with(uuid: "67890").and_return(port: "2", device: "0") expect(driver).to receive(:remove_disk).with("controller", "2", "0").and_return(true) expect(driver).to receive(:close_medium).with("67890").and_return(true) @@ -127,20 +118,19 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do describe "#handle_cleanup_dvd" do let(:disk_meta_file) { {dvd: [{"uuid" => "1234", "name" => "iso", "port" => "0", "device" => "0", "controller" => "controller" }]} } - let(:attachments) { [{port: "0", device: "0", uuid: "1234"}] } let(:defined_disks) { [] } it "removes the medium from guest" do + allow(controller).to receive(:get_attachment).with(uuid: "1234").and_return(port: "0", device: "0") expect(driver).to receive(:remove_disk).with("controller", "0", "0").and_return(true) subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) end context "when attachment is not found at the expected device" do - let(:attachments) { [{port: "0", device: "1", uuid: "1234"}] } - it "removes the disk from the correct device" do + allow(controller).to receive(:get_attachment).with(uuid: "1234").and_return(port: "0", device: "1") expect(driver).to receive(:remove_disk).with("controller", "0", "1").and_return(true) subject.handle_cleanup_dvd(machine, defined_disks, disk_meta_file[:dvd]) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index f20e44da0..c283021d1 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -25,6 +25,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do double(:state) end + let(:storage_controllers) { double("storage controllers") } + let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, @@ -68,8 +70,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) - allow(driver).to receive(:read_storage_controllers).and_return([controller]) - allow(driver).to receive(:get_storage_controller).with(controller.name).and_return(controller) + allow(storage_controllers).to receive(:get_controller!).with(name: controller.name).and_return(controller) + allow(storage_controllers).to receive(:first).and_return(controller) + allow(storage_controllers).to receive(:size).and_return(1) + allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) end describe "#configure_disks" do @@ -120,16 +124,25 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:controller2) { double("controller2", name: "SATA Controller", storage_bus: "SATA", limit: 30) } before do - allow(driver).to receive(:read_storage_controllers).and_return([controller1, controller2]) + allow(storage_controllers).to receive(:size).and_return(2) + allow(storage_controllers).to receive(:get_controller!).with(name: controller1.name). + and_return(controller1) + allow(storage_controllers).to receive(:get_controller!).with(name: controller2.name). + and_return(controller2) + allow(storage_controllers).to receive(:get_controller!).with(storage_bus: controller1.storage_bus). + and_return(controller1) + allow(storage_controllers).to receive(:get_controller!).with(storage_bus: controller2.storage_bus). + and_return(controller2) + allow(storage_controllers).to receive(:get_primary_controller).and_return(controller2) end - it "attaches disks to the SATA controller" do + it "attaches disks to the primary controller" do expect(subject).to receive(:handle_configure_disk).with(machine, anything, [], controller2.name). exactly(4).and_return(dsk_data) subject.configure_disks(machine, defined_disks) end - it "attaches dvds to the IDE controller" do + it "attaches dvds to the secondary controller" do defined_disks = [ dvd ] expect(subject).to receive(:handle_configure_dvd).with(machine, dvd, controller1.name). @@ -139,11 +152,11 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "raises an error if there are more than 4 dvds configured" do defined_disks = [ - double("dvd", name: "installer1", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer2", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer3", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer4", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer5", type: :dvd, file: "installer.iso") + double("dvd", name: "installer1", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer2", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer3", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer4", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer5", type: :dvd, file: "installer.iso", primary: false) ] expect { subject.configure_disks(machine, defined_disks) }. @@ -152,10 +165,10 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "attaches multiple dvds" do defined_disks = [ - double("dvd", name: "installer1", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer2", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer3", type: :dvd, file: "installer.iso"), - double("dvd", name: "installer4", type: :dvd, file: "installer.iso"), + double("dvd", name: "installer1", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer2", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer3", type: :dvd, file: "installer.iso", primary: false), + double("dvd", name: "installer4", type: :dvd, file: "installer.iso", primary: false), ] expect(subject).to receive(:handle_configure_dvd).exactly(4).times.and_return({}) @@ -485,7 +498,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end describe ".handle_configure_dvd" do - let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1") } + let(:dvd_config) { double("dvd", file: "/tmp/untitled.iso", name: "dvd1", primary: false) } before do allow(subject).to receive(:get_next_port).with(machine, controller). diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb new file mode 100644 index 000000000..0df469389 --- /dev/null +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb @@ -0,0 +1,72 @@ +require File.expand_path("../../base", __FILE__) + +describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do + include_context "unit" + + let(:controller1) { double("ide_controller", name: "IDE Controller", storage_bus: "IDE") } + let(:controller2) { double("sata_controller", name: "SATA Controller", storage_bus: "SATA") } + + let(:primary_disk) { double("attachment", location: "/tmp/primary.vdi") } + + before do + subject << controller1 + subject << controller2 + end + + describe "#get_controller" do + it "gets a controller by name" do + expect(subject.get_controller(name: "IDE Controller")).to eq(controller1) + end + + it "gets a controller by storage bus" do + expect(subject.get_controller(storage_bus: "SATA")).to eq(controller2) + end + end + + describe "#get_controller!" do + it "gets a controller if it exists" do + expect(subject.get_controller!(name: "IDE Controller")).to eq(controller1) + end + + it "raises an exception if a matching storage controller can't be found" do + expect { subject.get_controller!(name: "Foo Controller") }. + to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end + + describe "#get_primary_controller" do + context "with one controller" do + before do + subject.replace([controller1]) + end + + it "returns the controller" do + expect(subject.get_primary_controller).to eq(controller1) + end + end + + context "with multiple controllers" do + before do + allow(controller1).to receive(:attachments).and_return([]) + allow(controller2).to receive(:attachments).and_return([]) + end + + it "returns the SATA controller by default" do + expect(subject.get_primary_controller).to eq(controller2) + end + + it "returns the IDE controller if it has a hdd attached" do + allow(controller1).to receive(:attachments).and_return([primary_disk]) + allow(subject).to receive(:hdd?).with(primary_disk).and_return(true) + + expect(subject.get_primary_controller).to eq(controller1) + end + + it "raises an error if the machine doesn't have a SATA or an IDE controller" do + subject.replace([]) + + expect { subject.get_primary_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + end + end + end +end diff --git a/test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb similarity index 100% rename from test/unit/plugins/providers/virtualbox/models/storage_controller_test.rb rename to test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb From c316d18e35997bbf44d8d722cfa0fc3b2e5876ab Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Fri, 26 Jun 2020 15:59:06 -0400 Subject: [PATCH 31/42] Apply suggestions from code review Co-authored-by: Brian Cain Co-authored-by: Sophia Castellarin --- plugins/providers/virtualbox/cap/configure_disks.rb | 2 +- website/pages/docs/disks/configuration.mdx | 6 +++--- website/pages/docs/disks/usage.mdx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index e9cd73870..6f0045cdf 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -107,7 +107,7 @@ module VagrantPlugins current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first else - current_disk = all_disks.select { |d| d["Disk Name"] == disk.name }.first + current_disk = all_disks.detect { |d| d["Disk Name"] == disk.name } end current_disk diff --git a/website/pages/docs/disks/configuration.mdx b/website/pages/docs/disks/configuration.mdx index ec6cb9a01..f0e657512 100644 --- a/website/pages/docs/disks/configuration.mdx +++ b/website/pages/docs/disks/configuration.mdx @@ -16,12 +16,12 @@ Vagrant Disks has several options that allow users to define and attach disks to supported disk extensions, please check the specific provider being used. Not used for type `:dvd.` -- `file` (string) - Ffor type `:dvd`, this is a required argument that should - point to an .iso file on the host machine. For type `:disk`, this is an +- `file` (string) - For type `:dvd`, this is a required argument that should + point to an `.iso` file on the host machine. For type `:disk`, this is an optional argument that can point to the location of a disk file that already exists. -- `name` (string) - Required option to give the disk a name. This name will be +- `name` (string) - Required option to give the disk a name. This name will also be used as the filename when creating a virtual hard disk. - `primary` (boolean) - Optional argument that configures a given disk to be diff --git a/website/pages/docs/disks/usage.mdx b/website/pages/docs/disks/usage.mdx index 4a3fdd49a..6bb033173 100644 --- a/website/pages/docs/disks/usage.mdx +++ b/website/pages/docs/disks/usage.mdx @@ -92,7 +92,7 @@ result in a Vagrant error. ### Attaching optical drives -Vagrant can attach .iso files as optical drives using the VirtualBox provider. +Vagrant can attach `.iso` files as optical drives using the VirtualBox provider. An example of attaching an optical drive to a guest can be found below: ```ruby From 63e168386a37778526f5847232dd4c8f6ff23638 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 29 Jun 2020 13:38:06 -0400 Subject: [PATCH 32/42] Clean up disk_meta/dvd_meta in tests - Add a type check for disk_meta/dvd_meta - Fix up some places where metadata keys were using symbol keys instead of quoted names --- .../providers/virtualbox/cap/cleanup_disks.rb | 72 +++++++++---------- .../virtualbox/cap/cleanup_disks_test.rb | 6 +- 2 files changed, 38 insertions(+), 40 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index 33e0efc10..c95a91f42 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -24,8 +24,9 @@ module VagrantPlugins # @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 + # @param [Array] disk_meta - An array of all the previously defined disks from the last configure_disk action def self.handle_cleanup_disk(machine, defined_disks, disk_meta) + raise TypeError, "Expected `Array` but received `#{disk_meta.class}`" if !disk_meta.is_a?(Array) storage_controllers = machine.provider.driver.read_storage_controllers primary_controller = storage_controllers.get_primary_controller @@ -35,52 +36,49 @@ module VagrantPlugins end primary_uuid = primary[:uuid] - if disk_meta - disk_meta.each do |d| - dsk = defined_disks.select { |dk| dk.name == d["name"] } - if !dsk.empty? || d["uuid"] == primary_uuid - next + disk_meta.each do |d| + dsk = defined_disks.select { |dk| dk.name == d["name"] } + if !dsk.empty? || d["uuid"] == primary_uuid + 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) + + controller = storage_controllers.get_controller!(name: d["controller"]) + attachment = controller.get_attachment(uuid: d["uuid"]) + + if !attachment + LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") 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) - - controller = storage_controllers.get_controller!(name: d["controller"]) - attachment = controller.get_attachment(uuid: d["uuid"]) - - if !attachment - LOGGER.warn("Disk '#{d["name"]}' not attached to guest, but still exists.") - else - machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) - end - - machine.provider.driver.close_medium(d["uuid"]) + machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end + + machine.provider.driver.close_medium(d["uuid"]) end end end # @param [Vagrant::Machine] machine # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_dvds - # @param [Hash] dvd_meta - A hash of all the previously defined dvds from the last configure_disk action + # @param [Array] dvd_meta - An array of all the previously defined dvds from the last configure_disk action def self.handle_cleanup_dvd(machine, defined_dvds, dvd_meta) - if dvd_meta - dvd_meta.each do |d| - dsk = defined_dvds.select { |dk| dk.name == d["name"] } - if !dsk.empty? - next + raise TypeError, "Expected `Array` but received `#{dvd_meta.class}`" if !dvd_meta.is_a?(Array) + dvd_meta.each do |d| + dsk = defined_dvds.select { |dk| dk.name == d["name"] } + if !dsk.empty? + next + else + LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") + machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) + + storage_controllers = machine.provider.driver.read_storage_controllers + controller = storage_controllers.get_controller!(name: d["controller"]) + attachment = controller.get_attachment(uuid: d["uuid"]) + + if !attachment + LOGGER.warn("DVD '#{d["name"]}' not attached to guest, but still exists.") else - LOGGER.warn("Found dvd not in Vagrantfile config: '#{d["name"]}'. Removing dvd from guest #{machine.name}") - machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) - - storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.get_controller!(name: d["controller"]) - attachment = controller.get_attachment(uuid: d["uuid"]) - - if !attachment - LOGGER.warn("DVD '#{d["name"]}' not attached to guest, but still exists.") - else - machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) - end + machine.provider.driver.remove_disk(controller.name, attachment[:port], attachment[:device]) end end end diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index dcbf95274..897085af8 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -27,7 +27,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:subject) { described_class } - let(:disk_meta_file) { {disk: [], floppy: [], dvd: []} } + let(:disk_meta_file) { {"disk" => [], "floppy" => [], "dvd" => []} } let(:defined_disks) { {} } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, @@ -54,7 +54,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end context "with disks to clean up" do - let(:disk_meta_file) { {disk: [{uuid: "1234", name: "storage"}], floppy: [], dvd: []} } + let(:disk_meta_file) { {"disk" => [{"uuid" => "1234", "name" => "storage"}], "floppy" => [], "dvd" => []} } it "calls the cleanup method if a disk_meta file is defined" do expect(subject).to receive(:handle_cleanup_disk). @@ -72,7 +72,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end context "with dvd attached" do - let(:disk_meta_file) { {dvd: [{uuid: "12345", name: "iso"}]} } + let(:disk_meta_file) { {"disk" => [], "floppy" => [], "dvd" => [{"uuid" => "12345", "name" => "iso"}] } } it "calls the cleanup method if a disk_meta file is defined" do expect(subject).to receive(:handle_cleanup_dvd). From 8c58e3f6f647082344f1830483877e7dc981011f Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Mon, 29 Jun 2020 18:34:37 -0400 Subject: [PATCH 33/42] Refactor out method for getting the primary disk --- .../providers/virtualbox/cap/cleanup_disks.rb | 6 +- .../virtualbox/cap/configure_disks.rb | 15 ++--- .../virtualbox/driver/version_5_0.rb | 13 ---- .../model/storage_controller_array.rb | 41 +++++++++--- .../virtualbox/cap/cleanup_disks_test.rb | 4 +- .../virtualbox/cap/configure_disks_test.rb | 17 ++--- .../virtualbox/driver/version_5_0_test.rb | 25 -------- .../model/storage_controller_array_test.rb | 63 ++++++++++++++----- 8 files changed, 100 insertions(+), 84 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index c95a91f42..aff49a54f 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -28,12 +28,8 @@ module VagrantPlugins def self.handle_cleanup_disk(machine, defined_disks, disk_meta) raise TypeError, "Expected `Array` but received `#{disk_meta.class}`" if !disk_meta.is_a?(Array) storage_controllers = machine.provider.driver.read_storage_controllers - primary_controller = storage_controllers.get_primary_controller - primary = primary_controller.get_attachment(port: "0", device: "0") - if !primary - raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound - end + primary = storage_controllers.get_primary_attachment primary_uuid = primary[:uuid] disk_meta.each do |d| diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 6f0045cdf..a7c7d16ba 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -90,19 +90,12 @@ module VagrantPlugins # @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 - # @param [VagrantPlugins::ProviderVirtualBox::Model::StorageController] controller - - # the storage controller to use # @return [Hash] current_disk - Returns the current disk. Returns nil if it doesn't exist - def self.get_current_disk(machine, disk, all_disks, controller) + 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 is always Port 0 Device 0. - primary = controller.attachments.detect { |a| a[:port] == "0" && a[:device] == "0" } - if !primary - raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound - end + storage_controllers = machine.provider.driver.read_storage_controllers + primary = storage_controllers.get_primary_attachment primary_uuid = primary[:uuid] current_disk = all_disks.select { |d| d["UUID"] == primary_uuid }.first @@ -127,7 +120,7 @@ module VagrantPlugins disk_metadata = {} # Grab the existing configured disk, if it exists - current_disk = get_current_disk(machine, disk, all_disks, controller) + current_disk = get_current_disk(machine, disk, all_disks) # Configure current disk if !current_disk diff --git a/plugins/providers/virtualbox/driver/version_5_0.rb b/plugins/providers/virtualbox/driver/version_5_0.rb index f3ff3a160..dc01e1f14 100644 --- a/plugins/providers/virtualbox/driver/version_5_0.rb +++ b/plugins/providers/virtualbox/driver/version_5_0.rb @@ -964,19 +964,6 @@ module VagrantPlugins storage_controllers end - # Gets the storage controller matching the specified name. Raises an - # error if the storage controller can't be found. - # - # @param [String] name - storage controller name - # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] - def get_storage_controller(name) - controller = read_storage_controllers.detect { |c| c.name == name } - if !controller - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: name - end - controller - end - protected def valid_ip_address?(ip) diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb index 032b0bb70..bf2388b20 100644 --- a/plugins/providers/virtualbox/model/storage_controller_array.rb +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -7,6 +7,7 @@ module VagrantPlugins # Get a single controller matching the given options. # # @param [Hash] opts - A hash of attributes to match. + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_controller(opts = {}) if opts[:name] detect { |c| c.name == opts[:name] } @@ -19,6 +20,7 @@ module VagrantPlugins # exception if a matching controller can't be found. # # @param [Hash] opts - A hash of attributes to match. + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_controller!(opts = {}) controller = get_controller(opts) if controller.nil? @@ -27,28 +29,53 @@ module VagrantPlugins controller end - # Find the controller containing the primary disk (i.e. the boot disk). + # Find the controller containing the primary disk (i.e. the boot + # disk). This is used to determine which controller virtual disks + # should be attached to. + # + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_primary_controller - primary = nil + controller = nil if length == 1 - primary = first + controller = first else ide_controller = get_controller(storage_bus: "IDE") if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } - primary = ide_controller + controller = ide_controller else - primary = get_controller!(storage_bus: "SATA") + controller = get_controller!(storage_bus: "SATA") end end - primary + controller + end + + # Find the attachment representing the primary disk (i.e. the boot + # disk). We can't rely on the order of #list_hdds, as they will not + # always come in port order, but primary is always Port 0 Device 0. + # + # @return [Hash] attachment - Primary disk attachment information + def get_primary_attachment + attachment = nil + + controller = get_primary_controller + attachment = controller.get_attachment(port: "0", device: "0") + if !attachment + raise Vagrant::Errors::VirtualBoxDisksPrimaryNotFound + end + + attachment end private + # Determine whether the given attachment is a hard disk. + # + # @param [Hash] attachment - Attachment information + # @return [Boolean] def hdd?(attachment) - ext = File.extname(attachment[:location]) + ext = File.extname(attachment[:location].to_s).downcase # TODO: hook into ValidateDiskExt capability [".vdi", ".vmdk", ".vhd"].include?(ext) end diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 897085af8..1deff8c20 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -44,6 +44,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do allow(controller).to receive(:get_attachment).with(uuid: "67890").and_return(attachments[1]) allow(storage_controllers).to receive(:get_controller!).and_return(controller) allow(storage_controllers).to receive(:get_primary_controller).and_return(controller) + allow(storage_controllers).to receive(:get_primary_attachment).and_return(attachments[0]) allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) end @@ -65,7 +66,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do end it "raises an error if primary disk can't be found" do - allow(controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(nil) + allow(storage_controllers).to receive(:get_primary_attachment).and_raise(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) + expect { subject.cleanup_disks(machine, defined_disks, disk_meta_file) }. to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index c283021d1..429653b86 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -180,23 +180,24 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do describe "#get_current_disk" do it "gets primary disk uuid if disk to configure is primary" do - primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks, controller) + allow(storage_controllers).to receive(:get_primary_attachment).and_return(attachments[0]) + primary_disk = subject.get_current_disk(machine, defined_disks.first, all_disks) expect(primary_disk).to eq(all_disks.first) end it "raises an error if primary disk can't be found" do - allow(controller).to receive(:attachments).and_return([]) - expect { subject.get_current_disk(machine, defined_disks.first, all_disks, controller) }. + allow(storage_controllers).to receive(:get_primary_attachment).and_raise(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) + expect { subject.get_current_disk(machine, defined_disks.first, all_disks) }. to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end it "finds the disk to configure" do - disk = subject.get_current_disk(machine, defined_disks[1], all_disks, controller) + 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, controller) + disk = subject.get_current_disk(machine, defined_disks[3], all_disks) expect(disk).to be_nil end end @@ -256,7 +257,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "resizes a disk" do expect(subject).to receive(:get_current_disk). - with(machine, defined_disks[1], all_disks, controller).and_return(all_disks[1]) + 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) @@ -292,7 +293,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "reattaches disk if vagrant defined disk exists but is not attached to guest" do expect(subject).to receive(:get_current_disk). - with(machine, defined_disks[1], all_disks, controller).and_return(all_disks[1]) + 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) @@ -311,7 +312,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do it "does nothing if all disks are properly configured" do expect(subject).to receive(:get_current_disk). - with(machine, defined_disks[1], all_disks, controller).and_return(all_disks[1]) + 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) diff --git a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb index 573e08f1e..d10bfb4bc 100644 --- a/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb +++ b/test/unit/plugins/providers/virtualbox/driver/version_5_0_test.rb @@ -140,29 +140,4 @@ OUTPUT expect(storage_controllers.first.attachments).to include(port: "1", device: "0", uuid: "67890", location: "/tmp/secondary.vdi") end end - - describe "#get_storage_controller" do - let(:storage_controllers) { [double("controller", name: "IDE Controller")] } - - before do - allow(subject).to receive(:read_storage_controllers).and_return(storage_controllers) - end - - it "refreshes the storage controller state" do - expect(subject).to receive(:read_storage_controllers) - - subject.get_storage_controller("IDE Controller") - end - - it "returns the storage controller matching the specified name" do - controller = subject.get_storage_controller("IDE Controller") - - expect(controller.name).to eq("IDE Controller") - end - - it "raises an exception if the storage controller can't be found" do - expect { subject.get_storage_controller("SCSI Controller") }. - to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) - end - end end diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb index 0df469389..f67a07eac 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb @@ -3,29 +3,29 @@ require File.expand_path("../../base", __FILE__) describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do include_context "unit" - let(:controller1) { double("ide_controller", name: "IDE Controller", storage_bus: "IDE") } - let(:controller2) { double("sata_controller", name: "SATA Controller", storage_bus: "SATA") } + let(:ide_controller) { double("ide_controller", name: "IDE Controller", storage_bus: "IDE") } + let(:sata_controller) { double("sata_controller", name: "SATA Controller", storage_bus: "SATA") } let(:primary_disk) { double("attachment", location: "/tmp/primary.vdi") } before do - subject << controller1 - subject << controller2 + subject << ide_controller + subject << sata_controller end describe "#get_controller" do it "gets a controller by name" do - expect(subject.get_controller(name: "IDE Controller")).to eq(controller1) + expect(subject.get_controller(name: "IDE Controller")).to eq(ide_controller) end it "gets a controller by storage bus" do - expect(subject.get_controller(storage_bus: "SATA")).to eq(controller2) + expect(subject.get_controller(storage_bus: "SATA")).to eq(sata_controller) end end describe "#get_controller!" do it "gets a controller if it exists" do - expect(subject.get_controller!(name: "IDE Controller")).to eq(controller1) + expect(subject.get_controller!(name: "IDE Controller")).to eq(ide_controller) end it "raises an exception if a matching storage controller can't be found" do @@ -37,29 +37,29 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do describe "#get_primary_controller" do context "with one controller" do before do - subject.replace([controller1]) + subject.replace([ide_controller]) end it "returns the controller" do - expect(subject.get_primary_controller).to eq(controller1) + expect(subject.get_primary_controller).to eq(ide_controller) end end context "with multiple controllers" do before do - allow(controller1).to receive(:attachments).and_return([]) - allow(controller2).to receive(:attachments).and_return([]) + allow(ide_controller).to receive(:attachments).and_return([]) + allow(sata_controller).to receive(:attachments).and_return([]) end it "returns the SATA controller by default" do - expect(subject.get_primary_controller).to eq(controller2) + expect(subject.get_primary_controller).to eq(sata_controller) end it "returns the IDE controller if it has a hdd attached" do - allow(controller1).to receive(:attachments).and_return([primary_disk]) + allow(ide_controller).to receive(:attachments).and_return([primary_disk]) allow(subject).to receive(:hdd?).with(primary_disk).and_return(true) - expect(subject.get_primary_controller).to eq(controller1) + expect(subject.get_primary_controller).to eq(ide_controller) end it "raises an error if the machine doesn't have a SATA or an IDE controller" do @@ -69,4 +69,39 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do end end end + + describe "#hdd?" do + let(:attachment) { {} } + it "determines whether the given attachment represents a hard disk" do + expect(subject.send(:hdd?, attachment)).to be(false) + end + + it "returns true for disk files ending in compatible extensions" do + attachment[:location] = "/tmp/primary.vdi" + expect(subject.send(:hdd?, attachment)).to be(true) + end + + it "is case insensitive" do + attachment[:location] = "/tmp/PRIMARY.VDI" + expect(subject.send(:hdd?, attachment)).to be(true) + end + end + + describe "#get_primary_attachment" do + let(:attachment) { {location: "/tmp/primary.vdi"} } + + before do + allow(subject).to receive(:get_primary_controller).and_return(sata_controller) + end + + it "returns the first attachment on the primary controller" do + allow(sata_controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(attachment) + expect(subject.get_primary_attachment).to be(attachment) + end + + it "raises an exception if no attachment exists at port 0, device 0" do + allow(sata_controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(nil) + expect { subject.get_primary_attachment }.to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) + end + end end From ff9f9c40e8e7948e0a13fa60564ec32904389f0e Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 30 Jun 2020 13:58:54 -0400 Subject: [PATCH 34/42] Differentiate between controller "not found" errors This commit adds a new error message to be raised if a VM has no supported storage controllers. This lets us differentiate between two different "controller not found" scenarios: 1. If we are looking for a controller that we're expecting to find (i.e. one that was recorded in the disk metadata file) 2. If we are poking around for the *best* controller to use in a configuration task --- lib/vagrant/errors.rb | 4 ++ .../virtualbox/cap/configure_disks.rb | 2 +- .../model/storage_controller_array.rb | 55 +++++++++++++++---- templates/locales/en.yml | 17 ++++-- .../virtualbox/cap/configure_disks_test.rb | 5 +- .../model/storage_controller_array_test.rb | 16 ++++-- 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/lib/vagrant/errors.rb b/lib/vagrant/errors.rb index 52fc1a897..c3fa8595e 100644 --- a/lib/vagrant/errors.rb +++ b/lib/vagrant/errors.rb @@ -936,6 +936,10 @@ module Vagrant error_key(:virtualbox_disks_controller_not_found) end + class VirtualBoxDisksNoSupportedControllers < VagrantError + error_key(:virtualbox_disks_no_supported_controllers) + end + class VirtualBoxDisksPrimaryNotFound < VagrantError error_key(:virtualbox_disks_primary_not_found) end diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index a7c7d16ba..575c0d54a 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -55,7 +55,7 @@ module VagrantPlugins dvds_defined = defined_disks.select { |d| d.type == :dvd } if dvds_defined.any? - dvd_controller = storage_controllers.get_controller!(storage_bus: "IDE") + dvd_controller = storage_controllers.get_dvd_controller if dvds_defined.size > dvd_controller.limit raise Vagrant::Errors::VirtualBoxDisksDefinedExceedLimit, diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb index bf2388b20..bcc93978e 100644 --- a/plugins/providers/virtualbox/model/storage_controller_array.rb +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -4,6 +4,13 @@ module VagrantPlugins # A collection of storage controllers. Includes finder methods to look # up a storage controller by given attributes. class StorageControllerArray < Array + SATA_TYPE = "SATA".freeze + IDE_TYPE = "IDE".freeze + SUPPORTED_TYPES = [SATA_TYPE, IDE_TYPE].freeze + + # TODO: hook into ValidateDiskExt capability + DEFAULT_DISK_EXT = [".vdi", ".vmdk", ".vhd"].map(&:freeze).freeze + # Get a single controller matching the given options. # # @param [Hash] opts - A hash of attributes to match. @@ -23,7 +30,7 @@ module VagrantPlugins # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_controller!(opts = {}) controller = get_controller(opts) - if controller.nil? + if !controller && opts[:name] raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: opts[:name] end controller @@ -33,19 +40,21 @@ module VagrantPlugins # disk). This is used to determine which controller virtual disks # should be attached to. # + # Raises an exception if no supported controllers are found. + # # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_primary_controller controller = nil - if length == 1 - controller = first + if !types.any? { |t| SUPPORTED_TYPES.include?(t) } + raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: SUPPORTED_TYPES + end + + ide_controller = get_controller(storage_bus: IDE_TYPE) + if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } + controller = ide_controller else - ide_controller = get_controller(storage_bus: "IDE") - if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } - controller = ide_controller - else - controller = get_controller!(storage_bus: "SATA") - end + controller = get_controller(storage_bus: SATA_TYPE) end controller @@ -68,6 +77,24 @@ module VagrantPlugins attachment end + # Find a suitable storage controller for attaching dvds. Will raise an + # exception if no suitable controller can be found. + # + # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] + def get_dvd_controller + controller = nil + + if types.include?(IDE_TYPE) + controller = get_controller(storage_bus: IDE_TYPE) + elsif types.include?(SATA_TYPE) + controller = get_controller(storage_bus: SATA_TYPE) + else + raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: SUPPORTED_TYPES + end + + controller + end + private # Determine whether the given attachment is a hard disk. @@ -76,8 +103,14 @@ module VagrantPlugins # @return [Boolean] def hdd?(attachment) ext = File.extname(attachment[:location].to_s).downcase - # TODO: hook into ValidateDiskExt capability - [".vdi", ".vmdk", ".vhd"].include?(ext) + DEFAULT_DISK_EXT.include?(ext) + end + + # List of storage controller types. + # + # @return [Array] types + def types + map { |c| c.storage_bus } end end end diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 87f6690ff..64be91abc 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1679,17 +1679,26 @@ en: Vagrant. VirtualBox 4.2.16+ fixes this problem. Please upgrade VirtualBox. virtualbox_disks_controller_not_found: |- - Vagrant was not able to find a storage controller called '%{name}'. + Vagrant expected to find a storage controller called '%{name}', + but there is no controller with this name attached to the current VM. If you have changed or removed any storage controllers, please restore - them to their prior configuration. + them to their previous configuration. + virtualbox_disks_no_supported_controllers: |- + Vagrant was unable to detect a disk controller with any of the + following supported types: + + {supported_types} + + Please add one of the supported controller types in order to use the + disk configuration feature. virtualbox_disks_defined_exceed_limit: |- VirtualBox only allows up to %{limit} disks to be attached to the storage controller '%{name}'. Please remove some disks from your disk configuration, or attach a new storage controller. virtualbox_disks_primary_not_found: |- - Vagrant was not able to detect a primary disk attached to the SATA + Vagrant was not able to detect a primary disk attached to the main controller. Vagrant Disks requires that the primary disk be attached - to the first port of the SATA controller in order to manage it. + to the first port of the main controller in order to manage it. virtualbox_disks_unsupported_controller: |- An disk operation was attempted on the controller '%{controller_name}', but Vagrant doesn't support this type of disk controller. diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 429653b86..da52c3e15 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -129,10 +129,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do and_return(controller1) allow(storage_controllers).to receive(:get_controller!).with(name: controller2.name). and_return(controller2) - allow(storage_controllers).to receive(:get_controller!).with(storage_bus: controller1.storage_bus). - and_return(controller1) - allow(storage_controllers).to receive(:get_controller!).with(storage_bus: controller2.storage_bus). - and_return(controller2) + allow(storage_controllers).to receive(:get_dvd_controller).and_return(controller1) allow(storage_controllers).to receive(:get_primary_controller).and_return(controller2) end diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb index f67a07eac..354f50767 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb @@ -6,11 +6,10 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do let(:ide_controller) { double("ide_controller", name: "IDE Controller", storage_bus: "IDE") } let(:sata_controller) { double("sata_controller", name: "SATA Controller", storage_bus: "SATA") } - let(:primary_disk) { double("attachment", location: "/tmp/primary.vdi") } + let(:primary_disk) { {location: "/tmp/primary.vdi"} } before do - subject << ide_controller - subject << sata_controller + subject.replace([ide_controller, sata_controller]) end describe "#get_controller" do @@ -35,9 +34,10 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do end describe "#get_primary_controller" do - context "with one controller" do + context "with a single supported controller" do before do subject.replace([ide_controller]) + allow(ide_controller).to receive(:attachments).and_return([primary_disk]) end it "returns the controller" do @@ -65,7 +65,7 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do it "raises an error if the machine doesn't have a SATA or an IDE controller" do subject.replace([]) - expect { subject.get_primary_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) + expect { subject.get_primary_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) end end end @@ -104,4 +104,10 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do expect { subject.get_primary_attachment }.to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end end + + describe "#types" do + it "returns a list of storage controller types" do + expect(subject.send(:types)).to eq(["IDE", "SATA"]) + end + end end From 52c1267b2c582ca07499ac7633ee1f51ba9c0f0c Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 30 Jun 2020 16:17:22 -0400 Subject: [PATCH 35/42] Show duplicate values in error messages This also prevents nil showing up as a duplicate value for disk configs when `file` is undefined. --- plugins/kernel_v2/config/vm.rb | 12 ++++++------ templates/locales/en.yml | 12 +++++++++--- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 2f5a46fd8..1e356f41f 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -908,17 +908,17 @@ module VagrantPlugins end disk_names = @disks.map { |d| d.name } - duplicate_names = disk_names.detect{ |d| disk_names.count(d) > 1 } - if duplicate_names && duplicate_names.size + duplicate_names = disk_names.find_all { |d| disk_names.count(d) > 1 } + if duplicate_names.any? errors << I18n.t("vagrant.config.vm.multiple_disk_names_error", - name: duplicate_names) + names: duplicate_names.uniq.join("\n")) end disk_files = @disks.map { |d| d.file } - duplicate_files = disk_files.detect { |d| disk_files.count(d) > 1 } - if duplicate_files && duplicate_files.size + duplicate_files = disk_files.find_all { |d| d && disk_files.count(d) > 1 } + if duplicate_files.any? errors << I18n.t("vagrant.config.vm.multiple_disk_files_error", - file: duplicate_files) + files: duplicate_files.uniq.join("\n")) end @disks.each do |d| diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 64be91abc..eac32d5be 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1995,13 +1995,19 @@ en: multiple_primary_disks_error: |- There are more than one primary disks defined for guest '%{name}'. Please ensure that only one disk has been defined as a primary disk. multiple_disk_files_error: |- - The following disk file is used multiple times in the disk configuration: + The following disk files are used multiple times in the disk + configuration: - %{file} + %{files} Each disk file may only be attached to a VM once. multiple_disk_names_error: |- - Duplicate disk names defined: '%{name}'. Disk names must be unique. + The following disks names are defined multiple times in the disk + configuration: + + %{names} + + Disk names must be unique. multiple_networks_set_hostname: "Multiple networks have set `:hostname`" name_invalid: |- The sub-VM name '%{name}' is invalid. Please don't use special characters. From 33ef2ca017a7314320475a8ee8cc61f08cf2dfca Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Tue, 30 Jun 2020 16:55:44 -0400 Subject: [PATCH 36/42] Add machine name to error messages --- plugins/kernel_v2/config/vm.rb | 8 +++++--- templates/locales/en.yml | 9 ++++----- test/unit/plugins/kernel_v2/config/vm_test.rb | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/plugins/kernel_v2/config/vm.rb b/plugins/kernel_v2/config/vm.rb index 1e356f41f..3f8e419b3 100644 --- a/plugins/kernel_v2/config/vm.rb +++ b/plugins/kernel_v2/config/vm.rb @@ -911,19 +911,21 @@ module VagrantPlugins duplicate_names = disk_names.find_all { |d| disk_names.count(d) > 1 } if duplicate_names.any? errors << I18n.t("vagrant.config.vm.multiple_disk_names_error", - names: duplicate_names.uniq.join("\n")) + name: machine.name, + disk_names: duplicate_names.uniq.join("\n")) end disk_files = @disks.map { |d| d.file } duplicate_files = disk_files.find_all { |d| d && disk_files.count(d) > 1 } if duplicate_files.any? errors << I18n.t("vagrant.config.vm.multiple_disk_files_error", - files: duplicate_files.uniq.join("\n")) + name: machine.name, + disk_files: duplicate_files.uniq.join("\n")) end @disks.each do |d| error = d.validate(machine) - errors.concat error if !error.empty? + errors.concat(error) if !error.empty? end # Validate clout_init_configs diff --git a/templates/locales/en.yml b/templates/locales/en.yml index eac32d5be..7ba660ea8 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1996,16 +1996,16 @@ en: There are more than one primary disks defined for guest '%{name}'. Please ensure that only one disk has been defined as a primary disk. multiple_disk_files_error: |- The following disk files are used multiple times in the disk - configuration: + configuration for the VM '%{name}': - %{files} + %{disk_files} Each disk file may only be attached to a VM once. multiple_disk_names_error: |- The following disks names are defined multiple times in the disk - configuration: + configuration for the VM '%{name}': - %{names} + %{disk_names} Disk names must be unique. multiple_networks_set_hostname: "Multiple networks have set `:hostname`" @@ -2267,7 +2267,6 @@ en: configure_disks: start: "Configuring storage mediums..." floppy_not_supported: "Floppy disk configuration not yet supported. Skipping disk '%{name}'..." - dvd_not_supported: "DVD disk configuration not yet supported. Skipping disk '%{name}'..." shrink_size_not_supported: |- VirtualBox does not support shrinking disk sizes. Cannot shrink '%{name}' disks size. create_disk: |- diff --git a/test/unit/plugins/kernel_v2/config/vm_test.rb b/test/unit/plugins/kernel_v2/config/vm_test.rb index cf41addcb..914d3c74f 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, provider_name: "provider") } + let(:machine) { double("machine", provider: provider, provider_name: "provider", name: "default") } def assert_invalid errors = subject.validate(machine) From 883e45cc49ca4f58c0543963dbbf07daf5fce9a0 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 1 Jul 2020 10:19:19 -0400 Subject: [PATCH 37/42] Refactor: isolate "storage bus" logic Move all storage bus logic into the storage controller class. Since most of the storage controller interaction only cares about the storage controller name, we can simplify #get_controller and isolate the storage controller detection-type logic in the StorageControllerArray. --- .../providers/virtualbox/cap/cleanup_disks.rb | 4 +- .../virtualbox/cap/configure_disks.rb | 12 ++-- .../virtualbox/model/storage_controller.rb | 51 ++++++++++------ .../model/storage_controller_array.rb | 61 ++++++------------- .../virtualbox/cap/cleanup_disks_test.rb | 4 +- .../virtualbox/cap/configure_disks_test.rb | 14 ++--- .../model/storage_controller_array_test.rb | 27 ++------ .../model/storage_controller_test.rb | 14 ++--- 8 files changed, 82 insertions(+), 105 deletions(-) diff --git a/plugins/providers/virtualbox/cap/cleanup_disks.rb b/plugins/providers/virtualbox/cap/cleanup_disks.rb index aff49a54f..b01a2b5ba 100644 --- a/plugins/providers/virtualbox/cap/cleanup_disks.rb +++ b/plugins/providers/virtualbox/cap/cleanup_disks.rb @@ -40,7 +40,7 @@ module VagrantPlugins 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) - controller = storage_controllers.get_controller!(name: d["controller"]) + controller = storage_controllers.get_controller(d["controller"]) attachment = controller.get_attachment(uuid: d["uuid"]) if !attachment @@ -68,7 +68,7 @@ module VagrantPlugins machine.ui.warn("DVD '#{d["name"]}' no longer exists in Vagrant config. Removing medium from guest...", prefix: true) storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.get_controller!(name: d["controller"]) + controller = storage_controllers.get_controller(d["controller"]) attachment = controller.get_attachment(uuid: d["uuid"]) if !attachment diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index 575c0d54a..aac4a3824 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -115,7 +115,7 @@ module VagrantPlugins # @return [Hash] - disk_metadata def self.handle_configure_disk(machine, disk, all_disks, controller_name) storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.get_controller!(name: controller_name) + controller = storage_controllers.get_controller(controller_name) disk_metadata = {} @@ -164,7 +164,7 @@ module VagrantPlugins # @return [Hash] - dvd_metadata def self.handle_configure_dvd(machine, dvd, controller_name) storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.get_controller!(name: controller_name) + controller = storage_controllers.get_controller(controller_name) dvd_metadata = {} @@ -189,7 +189,7 @@ module VagrantPlugins # Refresh the controller information storage_controllers = machine.provider.driver.read_storage_controllers - controller = storage_controllers.get_controller!(name: controller_name) + controller = storage_controllers.get_controller(controller_name) attachment = controller.attachments.detect { |a| a[:port] == dsk_info[:port] && a[:device] == dsk_info[:device] } @@ -277,19 +277,19 @@ module VagrantPlugins def self.get_next_port(machine, controller) dsk_info = {} - if controller.storage_bus == "SATA" + if controller.sata? used_ports = controller.attachments.map { |a| a[:port].to_i } next_available_port = ((0..(controller.maxportcount - 1)).to_a - used_ports).first dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" - elsif controller.storage_bus == "IDE" + elsif controller.ide? # IDE Controllers have primary/secondary devices, so find the first port # with an empty device (0..(controller.maxportcount - 1)).each do |port| # Skip this port if it's full port_attachments = controller.attachments.select { |a| a[:port] == port.to_s } - next if port_attachments.count == 2 + next if port_attachments.count == controller.devices_per_port dsk_info[:port] = port.to_s diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index 777caa0c5..e378ea081 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -7,6 +7,9 @@ module VagrantPlugins SATA_CONTROLLER_TYPES = ["IntelAhci"].map(&:freeze).freeze IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].map(&:freeze).freeze + SATA_DEVICES_PER_PORT = 1.freeze + IDE_DEVICES_PER_PORT = 2.freeze + # The name of the storage controller. # # @return [String] @@ -17,17 +20,17 @@ module VagrantPlugins # @return [String] attr_reader :type - # The storage bus associated with the storage controller, which can be - # inferred from its specific type. - # - # @return [String] - attr_reader :storage_bus - # The maximum number of avilable ports for the storage controller. # # @return [Integer] attr_reader :maxportcount + # The number of devices that can be attached to each port. For SATA + # controllers, this will usually be 1, and for IDE controllers this + # will usually be 2. + # @return [Integer] + attr_reader :devices_per_port + # The maximum number of individual disks that can be attached to the # storage controller. For SATA controllers, this equals the maximum # number of ports. For IDE controllers, this will be twice the max @@ -45,20 +48,20 @@ module VagrantPlugins @name = name @type = type - if SATA_CONTROLLER_TYPES.include?(@type) - @storage_bus = "SATA" - elsif IDE_CONTROLLER_TYPES.include?(@type) - @storage_bus = "IDE" + @maxportcount = maxportcount.to_i + + if IDE_CONTROLLER_TYPES.include?(@type) + @storage_bus = :ide + @devices_per_port = IDE_DEVICES_PER_PORT + elsif SATA_CONTROLLER_TYPES.include?(@type) + @storage_bus = :sata + @devices_per_port = SATA_DEVICES_PER_PORT else - @storage_bus = "Unknown" + @storage_bus = :unknown + @devices_per_port = 1 end - @maxportcount = maxportcount.to_i - if @storage_bus == "IDE" - @limit = @maxportcount * 2 - else - @limit = @maxportcount - end + @limit = @maxportcount * @devices_per_port attachments ||= [] @attachments = attachments @@ -77,6 +80,20 @@ module VagrantPlugins @attachments.detect { |a| a[:uuid] == opts[:uuid] } end end + + # Returns true if the storage controller is a IDE type controller. + # + # @return [Boolean] + def ide? + @storage_bus == :ide + end + + # Returns true if the storage controller is a SATA type controller. + # + # @return [Boolean] + def sata? + @storage_bus == :sata + end end end end diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb index bcc93978e..63a8eabd1 100644 --- a/plugins/providers/virtualbox/model/storage_controller_array.rb +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -4,34 +4,18 @@ module VagrantPlugins # A collection of storage controllers. Includes finder methods to look # up a storage controller by given attributes. class StorageControllerArray < Array - SATA_TYPE = "SATA".freeze - IDE_TYPE = "IDE".freeze - SUPPORTED_TYPES = [SATA_TYPE, IDE_TYPE].freeze - # TODO: hook into ValidateDiskExt capability DEFAULT_DISK_EXT = [".vdi", ".vmdk", ".vhd"].map(&:freeze).freeze - # Get a single controller matching the given options. - # - # @param [Hash] opts - A hash of attributes to match. - # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] - def get_controller(opts = {}) - if opts[:name] - detect { |c| c.name == opts[:name] } - elsif opts[:storage_bus] - detect { |c| c.storage_bus == opts[:storage_bus] } - end - end - - # Get a single controller matching the given options. Raise an + # Returns a storage controller with the given name. Raises an # exception if a matching controller can't be found. # - # @param [Hash] opts - A hash of attributes to match. + # @param [String] name - The name of the storage controller # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] - def get_controller!(opts = {}) - controller = get_controller(opts) - if !controller && opts[:name] - raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: opts[:name] + def get_controller(name) + controller = detect { |c| c.name == name } + if !controller + raise Vagrant::Errors::VirtualBoxDisksControllerNotFound, name: name end controller end @@ -46,15 +30,17 @@ module VagrantPlugins def get_primary_controller controller = nil - if !types.any? { |t| SUPPORTED_TYPES.include?(t) } - raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: SUPPORTED_TYPES - end - - ide_controller = get_controller(storage_bus: IDE_TYPE) + ide_controller = detect { |c| c.ide? } if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } controller = ide_controller else - controller = get_controller(storage_bus: SATA_TYPE) + controller = detect { |c| c.sata? } + end + + if !controller + supported_types = StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES + raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, + supported_types: supported_types.join(" ,") end controller @@ -82,14 +68,12 @@ module VagrantPlugins # # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_dvd_controller - controller = nil + controller = detect { |c| c.ide? } || detect { |c| c.sata? } - if types.include?(IDE_TYPE) - controller = get_controller(storage_bus: IDE_TYPE) - elsif types.include?(SATA_TYPE) - controller = get_controller(storage_bus: SATA_TYPE) - else - raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: SUPPORTED_TYPES + if !controller + supported_types = StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES + raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, + supported_types: supported_types.join(" ,") end controller @@ -105,13 +89,6 @@ module VagrantPlugins ext = File.extname(attachment[:location].to_s).downcase DEFAULT_DISK_EXT.include?(ext) end - - # List of storage controller types. - # - # @return [Array] types - def types - map { |c| c.storage_bus } - end end end end diff --git a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb index 1deff8c20..6dc9072cc 100644 --- a/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/cleanup_disks_test.rb @@ -33,7 +33,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} - let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } + let(:controller) { double("controller", name: "controller", limit: 30, maxportcount: 30) } let(:storage_controllers) { double("storage controllers") } @@ -42,7 +42,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::CleanupDisks do allow(controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(attachments[0]) allow(controller).to receive(:get_attachment).with(uuid: "12345").and_return(attachments[0]) allow(controller).to receive(:get_attachment).with(uuid: "67890").and_return(attachments[1]) - allow(storage_controllers).to receive(:get_controller!).and_return(controller) + allow(storage_controllers).to receive(:get_controller).and_return(controller) allow(storage_controllers).to receive(:get_primary_controller).and_return(controller) allow(storage_controllers).to receive(:get_primary_attachment).and_return(attachments[0]) allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index da52c3e15..93870baf7 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -27,7 +27,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:storage_controllers) { double("storage controllers") } - let(:controller) { double("controller", name: "controller", limit: 30, storage_bus: "SATA", maxportcount: 30) } + let(:controller) { double("controller", name: "controller", limit: 30, sata?: true, maxportcount: 30) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} @@ -70,7 +70,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do before do allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(controller).to receive(:attachments).and_return(attachments) - allow(storage_controllers).to receive(:get_controller!).with(name: controller.name).and_return(controller) + allow(storage_controllers).to receive(:get_controller).with(controller.name).and_return(controller) allow(storage_controllers).to receive(:first).and_return(controller) allow(storage_controllers).to receive(:size).and_return(1) allow(driver).to receive(:read_storage_controllers).and_return(storage_controllers) @@ -120,14 +120,14 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do # hashicorp/bionic64 context "with more than one storage controller" do - let(:controller1) { double("controller1", name: "IDE Controller", storage_bus: "IDE", limit: 4) } - let(:controller2) { double("controller2", name: "SATA Controller", storage_bus: "SATA", limit: 30) } + let(:controller1) { double("controller1", name: "IDE Controller", limit: 4) } + let(:controller2) { double("controller2", name: "SATA Controller", limit: 30) } before do allow(storage_controllers).to receive(:size).and_return(2) - allow(storage_controllers).to receive(:get_controller!).with(name: controller1.name). + allow(storage_controllers).to receive(:get_controller).with(controller1.name). and_return(controller1) - allow(storage_controllers).to receive(:get_controller!).with(name: controller2.name). + allow(storage_controllers).to receive(:get_controller).with(controller2.name). and_return(controller2) allow(storage_controllers).to receive(:get_dvd_controller).and_return(controller1) allow(storage_controllers).to receive(:get_primary_controller).and_return(controller2) @@ -373,7 +373,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do end context "with empty IDE controller" do - let(:empty_controller) { double("controller", storage_bus: "IDE", attachments: [], maxportcount: 2) } + let(:empty_controller) { double("controller", ide?: true, sata?: false, attachments: [], maxportcount: 2, devices_per_port: 2) } it "attaches to port 0, device 0" do dsk_info = subject.get_next_port(machine, empty_controller) diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb index 354f50767..7db7ab97d 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb @@ -3,8 +3,8 @@ require File.expand_path("../../base", __FILE__) describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do include_context "unit" - let(:ide_controller) { double("ide_controller", name: "IDE Controller", storage_bus: "IDE") } - let(:sata_controller) { double("sata_controller", name: "SATA Controller", storage_bus: "SATA") } + let(:ide_controller) { double("ide_controller", name: "IDE Controller", ide?: true, sata?: false) } + let(:sata_controller) { double("sata_controller", name: "SATA Controller", sata?: true) } let(:primary_disk) { {location: "/tmp/primary.vdi"} } @@ -14,21 +14,11 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do describe "#get_controller" do it "gets a controller by name" do - expect(subject.get_controller(name: "IDE Controller")).to eq(ide_controller) - end - - it "gets a controller by storage bus" do - expect(subject.get_controller(storage_bus: "SATA")).to eq(sata_controller) - end - end - - describe "#get_controller!" do - it "gets a controller if it exists" do - expect(subject.get_controller!(name: "IDE Controller")).to eq(ide_controller) + expect(subject.get_controller("IDE Controller")).to eq(ide_controller) end it "raises an exception if a matching storage controller can't be found" do - expect { subject.get_controller!(name: "Foo Controller") }. + expect { subject.get_controller(name: "Foo Controller") }. to raise_error(Vagrant::Errors::VirtualBoxDisksControllerNotFound) end end @@ -65,7 +55,8 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do it "raises an error if the machine doesn't have a SATA or an IDE controller" do subject.replace([]) - expect { subject.get_primary_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) + expect { subject.get_primary_controller }. + to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) end end end @@ -104,10 +95,4 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do expect { subject.get_primary_attachment }.to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end end - - describe "#types" do - it "returns a list of storage controller types" do - expect(subject.send(:types)).to eq(["IDE", "SATA"]) - end - end end diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb index d25298449..8032687e7 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb @@ -4,19 +4,16 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do include_context "unit" let(:name) {} - let(:type) {} - let(:maxportcount) {} + let(:type) { "IntelAhci" } + let(:maxportcount) { 30 } let(:attachments) {} subject { described_class.new(name, type, maxportcount, attachments) } describe "#initialize" do context "with SATA controller type" do - let(:type) { "IntelAhci" } - let(:maxportcount) { 30 } - it "recognizes a SATA controller" do - expect(subject.storage_bus).to eq('SATA') + expect(subject.sata?).to be(true) end it "calculates the maximum number of attachments" do @@ -29,7 +26,7 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do let(:maxportcount) { 2 } it "recognizes an IDE controller" do - expect(subject.storage_bus).to eq('IDE') + expect(subject.ide?).to be(true) end it "calculates the maximum number of attachments" do @@ -41,7 +38,8 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do let(:type) { "foo" } it "is unknown" do - expect(subject.storage_bus).to eq('Unknown') + expect(subject.ide?).to be(false) + expect(subject.sata?).to be(false) end end end From 21954c29afbda321fdc049a4562abfc03e9ada28 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Wed, 1 Jul 2020 12:12:39 -0400 Subject: [PATCH 38/42] Hook into ValidateDiskExt capability --- plugins/providers/virtualbox/cap/configure_disks.rb | 2 +- .../virtualbox/model/storage_controller_array.rb | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index aac4a3824..b3a739dbf 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -294,7 +294,7 @@ module VagrantPlugins dsk_info[:port] = port.to_s # Check for a free device - if port_attachments.detect { |a| a[:device] == "0" } + if port_attachments.any? { |a| a[:device] == "0" } dsk_info[:device] = "1" else dsk_info[:device] = "0" diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb index 63a8eabd1..0e50aa277 100644 --- a/plugins/providers/virtualbox/model/storage_controller_array.rb +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -1,12 +1,11 @@ +require_relative "../cap/validate_disk_ext" + module VagrantPlugins module ProviderVirtualBox module Model # A collection of storage controllers. Includes finder methods to look # up a storage controller by given attributes. class StorageControllerArray < Array - # TODO: hook into ValidateDiskExt capability - DEFAULT_DISK_EXT = [".vdi", ".vmdk", ".vhd"].map(&:freeze).freeze - # Returns a storage controller with the given name. Raises an # exception if a matching controller can't be found. # @@ -86,8 +85,12 @@ module VagrantPlugins # @param [Hash] attachment - Attachment information # @return [Boolean] def hdd?(attachment) - ext = File.extname(attachment[:location].to_s).downcase - DEFAULT_DISK_EXT.include?(ext) + if !attachment + false + else + ext = File.extname(attachment[:location].to_s).downcase.split('.').last + VagrantPlugins::ProviderVirtualBox::Cap::ValidateDiskExt.validate_disk_ext(nil, ext) + end end end end From 3cb01415e4e676c15af2dc1b78106603bc47e221 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 2 Jul 2020 12:50:12 -0400 Subject: [PATCH 39/42] Add support for SCSI controllers SCSI controllers are a lot like SATA controllers. This commit also changes some controller detection logic to take boot priority into account when selecting an appropriate controller. --- .../virtualbox/cap/configure_disks.rb | 4 +- .../virtualbox/model/storage_controller.rb | 39 ++++++++- .../model/storage_controller_array.rb | 28 ++++--- .../virtualbox/cap/configure_disks_test.rb | 41 ++++++++-- .../model/storage_controller_array_test.rb | 79 +++++++++++++------ .../model/storage_controller_test.rb | 39 +++++++++ 6 files changed, 183 insertions(+), 47 deletions(-) diff --git a/plugins/providers/virtualbox/cap/configure_disks.rb b/plugins/providers/virtualbox/cap/configure_disks.rb index b3a739dbf..790592651 100644 --- a/plugins/providers/virtualbox/cap/configure_disks.rb +++ b/plugins/providers/virtualbox/cap/configure_disks.rb @@ -277,13 +277,13 @@ module VagrantPlugins def self.get_next_port(machine, controller) dsk_info = {} - if controller.sata? + if controller.devices_per_port == 1 used_ports = controller.attachments.map { |a| a[:port].to_i } next_available_port = ((0..(controller.maxportcount - 1)).to_a - used_ports).first dsk_info[:port] = next_available_port.to_s dsk_info[:device] = "0" - elsif controller.ide? + elsif controller.devices_per_port == 2 # IDE Controllers have primary/secondary devices, so find the first port # with an empty device (0..(controller.maxportcount - 1)).each do |port| diff --git a/plugins/providers/virtualbox/model/storage_controller.rb b/plugins/providers/virtualbox/model/storage_controller.rb index e378ea081..bb9adefcb 100644 --- a/plugins/providers/virtualbox/model/storage_controller.rb +++ b/plugins/providers/virtualbox/model/storage_controller.rb @@ -4,11 +4,17 @@ module VagrantPlugins # Represents a storage controller for VirtualBox. Storage controllers # have a type, a name, and can have hard disks or optical drives attached. class StorageController - SATA_CONTROLLER_TYPES = ["IntelAhci"].map(&:freeze).freeze IDE_CONTROLLER_TYPES = ["PIIX4", "PIIX3", "ICH6"].map(&:freeze).freeze + SATA_CONTROLLER_TYPES = ["IntelAhci"].map(&:freeze).freeze + SCSI_CONTROLLER_TYPES = [ "LsiLogic", "BusLogic"].map(&:freeze).freeze - SATA_DEVICES_PER_PORT = 1.freeze IDE_DEVICES_PER_PORT = 2.freeze + SATA_DEVICES_PER_PORT = 1.freeze + SCSI_DEVICES_PER_PORT = 1.freeze + + IDE_BOOT_PRIORITY = 1.freeze + SATA_BOOT_PRIORITY = 2.freeze + SCSI_BOOT_PRIORITY = 3.freeze # The name of the storage controller. # @@ -39,6 +45,15 @@ module VagrantPlugins # @return [Integer] attr_reader :limit + # The boot priority of the storage controller. This does not seem to + # depend on the controller number returned by `showvminfo`. + # Experimentation has determined that VirtualBox will try to boot from + # the first controller it finds with a hard disk, in this order: + # IDE, SATA, SCSI + # + # @return [Integer] + attr_reader :boot_priority + # The list of disks/ISOs attached to each storage controller. # # @return [Array] @@ -53,9 +68,15 @@ module VagrantPlugins if IDE_CONTROLLER_TYPES.include?(@type) @storage_bus = :ide @devices_per_port = IDE_DEVICES_PER_PORT + @boot_priority = IDE_BOOT_PRIORITY elsif SATA_CONTROLLER_TYPES.include?(@type) @storage_bus = :sata @devices_per_port = SATA_DEVICES_PER_PORT + @boot_priority = SATA_BOOT_PRIORITY + elsif SCSI_CONTROLLER_TYPES.include?(@type) + @storage_bus = :scsi + @devices_per_port = SCSI_DEVICES_PER_PORT + @boot_priority = SCSI_BOOT_PRIORITY else @storage_bus = :unknown @devices_per_port = 1 @@ -81,6 +102,13 @@ module VagrantPlugins end end + # Returns true if the storage controller has a supported type. + # + # @return [Boolean] + def supported? + [:ide, :sata, :scsi].include?(@storage_bus) + end + # Returns true if the storage controller is a IDE type controller. # # @return [Boolean] @@ -94,6 +122,13 @@ module VagrantPlugins def sata? @storage_bus == :sata end + + # Returns true if the storage controller is a SCSI type controller. + # + # @return [Boolean] + def scsi? + @storage_bus == :scsi + end end end end diff --git a/plugins/providers/virtualbox/model/storage_controller_array.rb b/plugins/providers/virtualbox/model/storage_controller_array.rb index 0e50aa277..452acf793 100644 --- a/plugins/providers/virtualbox/model/storage_controller_array.rb +++ b/plugins/providers/virtualbox/model/storage_controller_array.rb @@ -27,17 +27,12 @@ module VagrantPlugins # # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_primary_controller - controller = nil - - ide_controller = detect { |c| c.ide? } - if ide_controller && ide_controller.attachments.any? { |a| hdd?(a) } - controller = ide_controller - else - controller = detect { |c| c.sata? } + ordered = sort { |a, b| a.boot_priority <=> b.boot_priority } + controller = ordered.detect do |c| + c.supported? && c.attachments.any? { |a| hdd?(a) } end if !controller - supported_types = StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: supported_types.join(" ,") end @@ -62,15 +57,14 @@ module VagrantPlugins attachment end - # Find a suitable storage controller for attaching dvds. Will raise an - # exception if no suitable controller can be found. + # Returns the first supported storage controller for attaching dvds. + # Will raise an exception if no suitable controller can be found. # # @return [VagrantPlugins::ProviderVirtualBox::Model::StorageController] def get_dvd_controller - controller = detect { |c| c.ide? } || detect { |c| c.sata? } - + ordered = sort { |a, b| a.boot_priority <=> b.boot_priority } + controller = ordered.detect { |c| c.supported? } if !controller - supported_types = StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES raise Vagrant::Errors::VirtualBoxDisksNoSupportedControllers, supported_types: supported_types.join(" ,") end @@ -92,6 +86,14 @@ module VagrantPlugins VagrantPlugins::ProviderVirtualBox::Cap::ValidateDiskExt.validate_disk_ext(nil, ext) end end + + # Returns a list of all the supported controller types. + # + # @return [Array] + def supported_types + StorageController::SATA_CONTROLLER_TYPES + StorageController::IDE_CONTROLLER_TYPES + + StorageController::SCSI_CONTROLLER_TYPES + end end end end diff --git a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb index 93870baf7..c06debc3d 100644 --- a/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb +++ b/test/unit/plugins/providers/virtualbox/cap/configure_disks_test.rb @@ -27,7 +27,7 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do let(:storage_controllers) { double("storage controllers") } - let(:controller) { double("controller", name: "controller", limit: 30, sata?: true, maxportcount: 30) } + let(:controller) { double("controller", name: "controller", maxportcount: 30, devices_per_port: 1, limit: 30) } let(:attachments) { [{port: "0", device: "0", uuid: "12345"}, {port: "1", device: "0", uuid: "67890"}]} @@ -120,8 +120,8 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do # hashicorp/bionic64 context "with more than one storage controller" do - let(:controller1) { double("controller1", name: "IDE Controller", limit: 4) } - let(:controller2) { double("controller2", name: "SATA Controller", limit: 30) } + let(:controller1) { double("controller1", name: "IDE Controller", maxportcount: 2, devices_per_port: 2, limit: 4) } + let(:controller2) { double("controller2", name: "SATA Controller", maxportcount: 30, devices_per_port: 1, limit: 30) } before do allow(storage_controllers).to receive(:size).and_return(2) @@ -372,16 +372,43 @@ describe VagrantPlugins::ProviderVirtualBox::Cap::ConfigureDisks do expect(dsk_info[:device]).to eq("0") end - context "with empty IDE controller" do - let(:empty_controller) { double("controller", ide?: true, sata?: false, attachments: [], maxportcount: 2, devices_per_port: 2) } + context "with IDE controller" do + let(:controller) { + double("controller", name: "IDE", maxportcount: 2, devices_per_port: 2, limit: 4) + } + + let(:attachments) { [] } it "attaches to port 0, device 0" do - dsk_info = subject.get_next_port(machine, empty_controller) + dsk_info = subject.get_next_port(machine, controller) + expect(dsk_info[:port]).to eq("0") + expect(dsk_info[:device]).to eq("0") + end + + context "with 1 device" do + let(:attachments) { [{port:"0", device: "0"}] } + + it "attaches to the next device on that port" do + dsk_info = subject.get_next_port(machine, controller) + expect(dsk_info[:port]).to eq("0") + expect(dsk_info[:device]).to eq("1") + end + end + end + + context "with SCSI controller" do + let(:controller) { + double("controller", name: "SCSI", maxportcount: 16, devices_per_port: 1, limit: 16) + } + + let(:attachments) { [] } + + it "determines the next available port and device to use" do + dsk_info = subject.get_next_port(machine, controller) expect(dsk_info[:port]).to eq("0") expect(dsk_info[:device]).to eq("0") end end - end describe "#resize_disk" do diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb index 7db7ab97d..a0b6d66af 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_array_test.rb @@ -3,18 +3,18 @@ require File.expand_path("../../base", __FILE__) describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do include_context "unit" - let(:ide_controller) { double("ide_controller", name: "IDE Controller", ide?: true, sata?: false) } - let(:sata_controller) { double("sata_controller", name: "SATA Controller", sata?: true) } + let(:controller1) { double("controller1", name: "IDE Controller", supported?: true, boot_priority: 1) } + let(:controller2) { double("controller2", name: "SATA Controller", supported?: true, boot_priority: 2) } let(:primary_disk) { {location: "/tmp/primary.vdi"} } before do - subject.replace([ide_controller, sata_controller]) + subject.replace([controller1, controller2]) end describe "#get_controller" do it "gets a controller by name" do - expect(subject.get_controller("IDE Controller")).to eq(ide_controller) + expect(subject.get_controller("IDE Controller")).to eq(controller1) end it "raises an exception if a matching storage controller can't be found" do @@ -26,34 +26,27 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do describe "#get_primary_controller" do context "with a single supported controller" do before do - subject.replace([ide_controller]) - allow(ide_controller).to receive(:attachments).and_return([primary_disk]) + subject.replace([controller1]) + allow(controller1).to receive(:attachments).and_return([primary_disk]) end it "returns the controller" do - expect(subject.get_primary_controller).to eq(ide_controller) + expect(subject.get_primary_controller).to eq(controller1) end end context "with multiple controllers" do before do - allow(ide_controller).to receive(:attachments).and_return([]) - allow(sata_controller).to receive(:attachments).and_return([]) + allow(controller1).to receive(:attachments).and_return([]) + allow(controller2).to receive(:attachments).and_return([primary_disk]) end - it "returns the SATA controller by default" do - expect(subject.get_primary_controller).to eq(sata_controller) + it "returns the first supported controller with a disk attached" do + expect(subject.get_primary_controller).to eq(controller2) end - it "returns the IDE controller if it has a hdd attached" do - allow(ide_controller).to receive(:attachments).and_return([primary_disk]) - allow(subject).to receive(:hdd?).with(primary_disk).and_return(true) - - expect(subject.get_primary_controller).to eq(ide_controller) - end - - it "raises an error if the machine doesn't have a SATA or an IDE controller" do - subject.replace([]) + it "raises an error if the primary disk is attached to an unsupported controller" do + allow(controller2).to receive(:supported?).and_return(false) expect { subject.get_primary_controller }. to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) @@ -82,17 +75,57 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageControllerArray do let(:attachment) { {location: "/tmp/primary.vdi"} } before do - allow(subject).to receive(:get_primary_controller).and_return(sata_controller) + allow(subject).to receive(:get_primary_controller).and_return(controller2) end it "returns the first attachment on the primary controller" do - allow(sata_controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(attachment) + allow(controller2).to receive(:get_attachment).with(port: "0", device: "0").and_return(attachment) expect(subject.get_primary_attachment).to be(attachment) end it "raises an exception if no attachment exists at port 0, device 0" do - allow(sata_controller).to receive(:get_attachment).with(port: "0", device: "0").and_return(nil) + allow(controller2).to receive(:get_attachment).with(port: "0", device: "0").and_return(nil) expect { subject.get_primary_attachment }.to raise_error(Vagrant::Errors::VirtualBoxDisksPrimaryNotFound) end end + + describe "#get_dvd_controller" do + context "with one controller" do + let(:controller) { double("controller", supported?: true) } + + before do + subject.replace([controller]) + end + + it "returns the controller" do + expect(subject.get_dvd_controller).to be(controller) + end + + it "raises an exception if the controller is unsupported" do + allow(controller).to receive(:supported?).and_return(false) + + expect { subject.get_dvd_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) + end + end + + context "with multiple controllers" do + let(:controller1) { double("controller", supported?: true, boot_priority: 2) } + let(:controller2) { double("controller", supported?: true, boot_priority: 1) } + + before do + subject.replace([controller1, controller2]) + end + + it "returns the first supported controller" do + expect(subject.get_dvd_controller).to be(controller2) + end + + it "raises an exception if no controllers are supported" do + allow(controller1).to receive(:supported?).and_return(false) + allow(controller2).to receive(:supported?).and_return(false) + + expect { subject.get_dvd_controller }.to raise_error(Vagrant::Errors::VirtualBoxDisksNoSupportedControllers) + end + end + end end diff --git a/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb b/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb index 8032687e7..918f4815b 100644 --- a/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb +++ b/test/unit/plugins/providers/virtualbox/model/storage_controller_test.rb @@ -19,6 +19,10 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do it "calculates the maximum number of attachments" do expect(subject.limit).to eq(30) end + + it "sets the boot priority" do + expect(subject.boot_priority).to eq(2) + end end context "with IDE controller type" do @@ -32,6 +36,27 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do it "calculates the maximum number of attachments" do expect(subject.limit).to eq(4) end + + it "sets the boot priority" do + expect(subject.boot_priority).to eq(1) + end + end + + context "with SCSI controller type" do + let(:type) { "LsiLogic" } + let(:maxportcount) { 16 } + + it "recognizes an SCSI controller" do + expect(subject.scsi?).to be(true) + end + + it "calculates the maximum number of attachments" do + expect(subject.limit).to eq(16) + end + + it "sets the boot priority" do + expect(subject.boot_priority).to eq(3) + end end context "with some other type" do @@ -43,4 +68,18 @@ describe VagrantPlugins::ProviderVirtualBox::Model::StorageController do end end end + + describe "#supported?" do + it "returns true if the controller type is supported" do + expect(subject.supported?).to be(true) + end + + context "with unsupported type" do + let(:type) { "foo" } + + it "returns false" do + expect(subject.supported?).to be(false) + end + end + end end From 8c90eb7b0533c3f23fd906fb51c1bfcf67c5e162 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 2 Jul 2020 13:22:09 -0400 Subject: [PATCH 40/42] Updated attachment logic --- website/pages/docs/disks/usage.mdx | 11 ++++++----- .../docs/disks/virtualbox/common-issues.mdx | 19 +++++++++++++++---- website/pages/docs/disks/virtualbox/index.mdx | 8 +++++--- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/website/pages/docs/disks/usage.mdx b/website/pages/docs/disks/usage.mdx index 6bb033173..8e78204af 100644 --- a/website/pages/docs/disks/usage.mdx +++ b/website/pages/docs/disks/usage.mdx @@ -86,9 +86,10 @@ Vagrant.configure("2") do |config| end ``` -Note: VirtualBox only allows for up to 30 disks to be attached to a given SATA Controller, -and this number includes the primary disk! Attempting to configure more than 30 will -result in a Vagrant error. +Note: VirtualBox has a hard limit on the number of disks that can be attached +to a given storage controller, which is defined by the controller type. +Attempting to configure more disks than are supported by the primary +controller will result in a Vagrant error. ### Attaching optical drives @@ -106,8 +107,8 @@ Vagrant.configure("2") do |config| end ``` -Note: VirtualBox only allows for up to 4 optical drives to be attached to a -given IDE controller. +As with hard disks, configuring more disks than are supported by your VM's +storage controller arrangement will result in a Vagrant error. ### Removing Disks diff --git a/website/pages/docs/disks/virtualbox/common-issues.mdx b/website/pages/docs/disks/virtualbox/common-issues.mdx index db8a6dd7e..fdcd8aab5 100644 --- a/website/pages/docs/disks/virtualbox/common-issues.mdx +++ b/website/pages/docs/disks/virtualbox/common-issues.mdx @@ -21,10 +21,21 @@ you should see a list of disks attached to your guest. ## How many disks can I attach? -Vagrant attaches all new disks defined to a guests SATA Controller. As of VirtualBox 6.1.x, -SATA Controllers can only support up to **30 disks** per guest. Therefore if you try -to define and attach more than 30, it will result in an error. This number _includes_ -the primary disk for the guest. +Vagrant attaches all new disks defined to guest's primary controller. As of +VirtualBox 6.1.x, storage controllers have the following limits to the number +of disks that are supported per guest: + +- IDE Controllers: 4 +- SATA Controllers: 30 +- SCSI Controllers: 16 + +Therefore if your primary disk is attached to a SATA Controller and you try to +define and attach more than 30, it will result in an error. This number +_includes_ the primary disk for the guest. + +DVD attachments are subject to the same limits. Optical disk attachments will +be attached to the storage controller with the highest boot priority (usually +the IDE controller). ## Resizing VMDK format disks diff --git a/website/pages/docs/disks/virtualbox/index.mdx b/website/pages/docs/disks/virtualbox/index.mdx index 974efe76f..d6e71af95 100644 --- a/website/pages/docs/disks/virtualbox/index.mdx +++ b/website/pages/docs/disks/virtualbox/index.mdx @@ -34,9 +34,11 @@ to be applied. When new disks are defined to be attached to a guest, Vagrant will attach disks to a particular storage controller based on the type of disk configured: -- For the `:disk` type, Vagrant will attach the disk to a guest's SATA - controller. It will also create the disk if necessary. -- For the `:dvd` type, Vagrant will attach the disk to a guest's IDE +- For the `:disk` type, Vagrant will use the storage controller containing the + boot disk. It will also create the disk if necessary. +- For the `:dvd` type, Vagrant will attach the disk to the storage controller + that comes earliest in the machine's boot order. For example, if a VM has a + SATA controller and an IDE controller, the disk will be attached to the IDE controller. Vagrant will not be able to configure disks of a given type if the associated From 1d46cd38827e71fb9b26210ef4076d88c9ae83ec Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 2 Jul 2020 12:54:54 -0400 Subject: [PATCH 41/42] Update templates/locales/en.yml Co-authored-by: Sophia Castellarin --- templates/locales/en.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates/locales/en.yml b/templates/locales/en.yml index 7ba660ea8..6bdb40967 100644 --- a/templates/locales/en.yml +++ b/templates/locales/en.yml @@ -1687,7 +1687,7 @@ en: Vagrant was unable to detect a disk controller with any of the following supported types: - {supported_types} + %{supported_types} Please add one of the supported controller types in order to use the disk configuration feature. From de461fa47cae3c3a663468d34463347e7dbbeb33 Mon Sep 17 00:00:00 2001 From: Jeff Bonhag Date: Thu, 9 Jul 2020 15:33:19 -0400 Subject: [PATCH 42/42] Fix a couple of tests Use subject instead of invalid_subject because the validation assertions test the subject double. This also adds an additional check when validating the `size` attribute because it is only required for disks of type `:disk`. --- plugins/kernel_v2/config/disk.rb | 2 +- test/unit/plugins/kernel_v2/config/disk_test.rb | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/plugins/kernel_v2/config/disk.rb b/plugins/kernel_v2/config/disk.rb index 41cd65cdd..dcce4d5df 100644 --- a/plugins/kernel_v2/config/disk.rb +++ b/plugins/kernel_v2/config/disk.rb @@ -172,7 +172,7 @@ module VagrantPlugins end end - if !@size + if !@size && type == :disk errors << I18n.t("vagrant.config.disk.invalid_size", name: @name, machine: machine.name) end diff --git a/test/unit/plugins/kernel_v2/config/disk_test.rb b/test/unit/plugins/kernel_v2/config/disk_test.rb index e986bdae0..48bec3b78 100644 --- a/test/unit/plugins/kernel_v2/config/disk_test.rb +++ b/test/unit/plugins/kernel_v2/config/disk_test.rb @@ -32,8 +32,6 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do before do env = double("env") - subject.name = "foo" - 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) @@ -41,6 +39,11 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do end describe "with defaults" do + before do + subject.name = "foo" + subject.size = 100 + end + it "is valid with test defaults" do subject.finalize! assert_valid @@ -58,18 +61,24 @@ describe VagrantPlugins::Kernel_V2::VagrantConfigDisk do end describe "with an invalid config" do - let(:invalid_subject) { described_class.new(type) } + before do + subject.name = "bar" + end 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 + subject.size = 100 + subject.disk_ext = "fake" + 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) + allow(provider).to receive(:capability?).with(:default_disk_exts).and_return(true) + allow(provider).to receive(:capability).with(:default_disk_exts).and_return(["vdi", "vmdk"]) end it "raises an error" do