diff --git a/lib/vagrant-vmware-desktop/action.rb b/lib/vagrant-vmware-desktop/action.rb index 9402b13..76eae8f 100644 --- a/lib/vagrant-vmware-desktop/action.rb +++ b/lib/vagrant-vmware-desktop/action.rb @@ -361,10 +361,8 @@ def self.action_start b3.use SetHostname end - Vagrant::Util::Experimental.guard_with(:disks) do - b3.use CleanupDisks - b3.use Disk - end + b3.use CleanupDisks + b3.use Disk b3.use VMXModify b3.use PrepareForwardedPortCollisionParams b3.use HandleForwardedPortCollisions diff --git a/lib/vagrant-vmware-desktop/cap/disk.rb b/lib/vagrant-vmware-desktop/cap/disk.rb index 1aefc02..fa5ad4c 100644 --- a/lib/vagrant-vmware-desktop/cap/disk.rb +++ b/lib/vagrant-vmware-desktop/cap/disk.rb @@ -28,7 +28,7 @@ module Disk # 5 : compressed disk optimized for streaming # 6 : thin provisioned virtual disk - ESX 3.x and above DEFAULT_DISK_TYPE = 0.freeze - PRIMARY_DISK_SLOT = "scsi0:0".freeze + PRIMARY_DISK_SLOTS = ["scsi0:0", "sata0:0", "ide0:0"].map(&:freeze).freeze def self.set_default_disk_ext(machine) DEFAULT_DISK_EXT @@ -112,7 +112,11 @@ def self.cleanup_disks(machine, defined_disks, disk_meta) # @return [Hash, nil] - A hash of the current disk, nil if not found def self.get_disk(disk, all_disks) if disk.primary - return all_disks[PRIMARY_DISK_SLOT] + PRIMARY_DISK_SLOTS.each do |primary_slot| + disk_info = all_disks[primary_slot] + @@logger.debug("disk info for primary slot #{primary_slot} - #{disk_info}") + return disk_info if disk_info["present"].to_s.upcase == "TRUE" + end else if disk.type == :dvd all_disks.values.detect { |v| v["filename"] == disk.file } @@ -153,7 +157,12 @@ def self.setup_disk(machine, disk, attached_disks) # disk.size is in bytes if disk.size > machine.provider.driver.get_disk_size(disk_path) - grow_disk(machine, disk_path, disk) + if disk.primary && machine.provider.driver.is_linked_clone? + machine.env.ui.warn(I18n.t("hashicorp.vagrant_vmware_desktop.disk_not_growing_linked_primary")) + @@logger.warn("Not growing primary disk - guest is linked clone") + else + grow_disk(machine, disk_path, disk) + end elsif disk.size < machine.provider.driver.get_disk_size(disk_path) machine.env.ui.warn(I18n.t("hashicorp.vagrant_vmware_desktop.disk_not_shrinking", path: disk.name)) @@logger.warn("Not shrinking disk #{disk.name}") diff --git a/lib/vagrant-vmware-desktop/driver/base.rb b/lib/vagrant-vmware-desktop/driver/base.rb index d849adc..d26141c 100644 --- a/lib/vagrant-vmware-desktop/driver/base.rb +++ b/lib/vagrant-vmware-desktop/driver/base.rb @@ -1189,6 +1189,32 @@ def get_disk_size(disk_path) disk_size end + # @return [Boolean] Instance is a linked clone + def is_linked_clone? + if @vmsd_path.nil? + @vm_dir.children(true).each do |child| + if child.basename.to_s.match(/\.vmsd$/) + @vmsd_path = child + end + end + end + + return false if @vmsd_path.nil? + + if @is_clone.nil? + @is_clone = false + + File.readlines(@vmsd_path).each do |line| + if line.start_with?("cloneOf") + @is_clone = true + break + end + end + end + + @is_clone + end + protected # This reads the latest DHCP lease for a MAC address on the diff --git a/lib/vagrant-vmware-desktop/plugin.rb b/lib/vagrant-vmware-desktop/plugin.rb index dda3135..4f8dce3 100644 --- a/lib/vagrant-vmware-desktop/plugin.rb +++ b/lib/vagrant-vmware-desktop/plugin.rb @@ -99,26 +99,24 @@ def self.provider_options Cap::Provider end - Vagrant::Util::Experimental.guard_with(:disks) do - provider_capability(p_name, :set_default_disk_ext) do - require File.expand_path("../cap/disk", __FILE__) - Cap::Disk - end - - provider_capability(p_name, :default_disk_exts) do - require File.expand_path("../cap/disk", __FILE__) - Cap::Disk - end - - provider_capability(p_name, :configure_disks) do - require File.expand_path("../cap/disk", __FILE__) - Cap::Disk - end - - provider_capability(p_name, :cleanup_disks) do - require File.expand_path("../cap/disk", __FILE__) - Cap::Disk - end + provider_capability(p_name, :set_default_disk_ext) do + require File.expand_path("../cap/disk", __FILE__) + Cap::Disk + end + + provider_capability(p_name, :default_disk_exts) do + require File.expand_path("../cap/disk", __FILE__) + Cap::Disk + end + + provider_capability(p_name, :configure_disks) do + require File.expand_path("../cap/disk", __FILE__) + Cap::Disk + end + + provider_capability(p_name, :cleanup_disks) do + require File.expand_path("../cap/disk", __FILE__) + Cap::Disk end end diff --git a/locales/en.yml b/locales/en.yml index 2c22859..66e8638 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -16,6 +16,9 @@ en: Configuring network adapters within the VM... destroying: |- Deleting the VM... + disk_not_growing_linked_primary: |- + Increasing the size of the primary disk is not allowed for linked + clones. Primary disk of the guest remains unchanged. disk_not_shrinking: |- Shrinking disks is not supported. Not shrinking disk %{path} discarding_suspended_state: |- diff --git a/spec/vagrant-vmware-desktop/cap/disk_spec.rb b/spec/vagrant-vmware-desktop/cap/disk_spec.rb index da56ee6..7071a48 100644 --- a/spec/vagrant-vmware-desktop/cap/disk_spec.rb +++ b/spec/vagrant-vmware-desktop/cap/disk_spec.rb @@ -7,7 +7,8 @@ require "vagrant-vmware-desktop/cap/disk" describe HashiCorp::VagrantVMwareDesktop::Cap::Disk do - let(:driver) { double("driver") } + let(:driver) { double("driver", 'is_linked_clone?': linked_clone) } + let(:linked_clone) { false } let(:ui) { double("ui") } let(:machine) { double("machine", provider: double("provider", driver: driver), env: double("env", ui: ui))} @@ -44,7 +45,6 @@ ]} before do - allow(Vagrant::Util::Experimental).to receive(:feature_enabled?).and_return(true) allow(driver).to receive(:vmx_path).and_return(vmx_path) end @@ -196,6 +196,29 @@ }.to raise_error(HashiCorp::VagrantVMwareDesktop::Errors::DiskNotResizedSnapshot) end + context "when guest is a linked clone" do + let(:linked_clone) { true } + + before do + allow(ui).to receive(:warn) + allow(driver).to receive(:grow_disk) + allow(driver).to receive(:snapshot_list).and_return([]) + end + + it "should warn it will not grow linked clone" do + expect(ui).to receive(:warn).with(/Primary disk of the guest remains/) + described_class.configure_disks(machine, defined_disks) + end + + it "should not grow the primary disk" do + expect(driver).to receive(:grow_disk).once + expect(described_class).to_not receive(:create_disk) + expect(driver).to_not receive(:add_disk_to_vmx) + configured_disks = described_class.configure_disks(machine, defined_disks) + expect(configured_disks[:disk].map { |d| d.flatten.any?(nil) }.any?(true)).to be(false) + end + end + context "vmx pointing to not root metadata disk" do let(:defined_disks) { [ double("disk", id: "abcde", name: "disk", size: 196608, primary: true, diff --git a/spec/vagrant-vmware-desktop/driver_spec.rb b/spec/vagrant-vmware-desktop/driver_spec.rb index fad65cd..1c8f608 100644 --- a/spec/vagrant-vmware-desktop/driver_spec.rb +++ b/spec/vagrant-vmware-desktop/driver_spec.rb @@ -847,165 +847,166 @@ end end end + end - describe "#get_disks" do - let(:vmx_contents) do - "random0:0.maprootshare = \"TRUE\"\n" \ - "ide0:0.devicetype = \"cdrom-raw\"\n" \ - "ide0:0.filename = \"auto detect\"\n" \ - "ide0:0.present = \"TRUE\"\n" \ - "scsi0.present = \"TRUE\"\n" \ - "scsi0:0.filename = \"disk-000018.vmdk\"\n" \ - "scsi0:0.present = \"TRUE\"\n" \ - "scsi0:1.filename = \"another_one.vmdk\"\n" \ - "scsi0:1.present = \"TRUE\"\n" - end - - it "returns all the devices of given type" do - expected = { - "random0:0"=>{"maprootshare"=>"TRUE"}, - "scsi0:0"=>{"filename"=>"disk-000018.vmdk", "present"=>"TRUE"}, - "scsi0:1"=>{"filename"=>"another_one.vmdk", "present"=>"TRUE"} - } - expect(instance.get_disks(["random", "scsi"])).to eq(expected) - end + describe "#get_disks" do + let(:vmx_contents) do + "random0:0.maprootshare = \"TRUE\"\n" \ + "ide0:0.devicetype = \"cdrom-raw\"\n" \ + "ide0:0.filename = \"auto detect\"\n" \ + "ide0:0.present = \"TRUE\"\n" \ + "scsi0.present = \"TRUE\"\n" \ + "scsi0:0.filename = \"disk-000018.vmdk\"\n" \ + "scsi0:0.present = \"TRUE\"\n" \ + "scsi0:1.filename = \"another_one.vmdk\"\n" \ + "scsi0:1.present = \"TRUE\"\n" + end + + it "returns all the devices of given type" do + expected = { + "random0:0"=>{"maprootshare"=>"TRUE"}, + "scsi0:0"=>{"filename"=>"disk-000018.vmdk", "present"=>"TRUE"}, + "scsi0:1"=>{"filename"=>"another_one.vmdk", "present"=>"TRUE"} + } + expect(instance.get_disks(["random", "scsi"])).to eq(expected) end + end - describe "#remove_disk" do - let(:vmx) { double("vmx") } - - let(:vmx_contents) do - "random0:0.maprootshare = \"TRUE\"\n" \ - "ide0:0.devicetype = \"cdrom-raw\"\n" \ - "ide0:0.filename = \"auto detect\"\n" \ - "ide0:0.present = \"TRUE\"\n" \ - "scsi0.present = \"TRUE\"\n" \ - "scsi0:0.filename = \"disk-000018.vmdk\"\n" \ - "scsi0:0.present = \"TRUE\"\n" \ - "scsi0:1.filename = \"another_one.vmdk\"\n" \ - "scsi0:1.present = \"TRUE\"\n" - end - - before do - allow(vmx).to receive(:[]=) - allow(vmx).to receive(:keys).and_return([]) - allow(instance).to receive(:vmx_modify).and_yield(vmx) - end - - it "removes a disk" do - allow(File).to receive(:exist?).and_return(true) - expect(instance).to receive(:vdiskmanager) - expect(vmx).to receive(:delete).with("scsi0:1.filename") - expect(vmx).to receive(:delete).with("scsi0:1.present") - instance.remove_disk("another_one.vmdk") - end + describe "#remove_disk" do + let(:vmx) { double("vmx") } - it "gracefully handles non existent disk" do - allow(File).to receive(:exist?).and_return(false) - expect(instance).not_to receive(:vdiskmanager) - expect(vmx).not_to receive(:delete) - instance.remove_disk("oops.vmdk") - end + let(:vmx_contents) do + "random0:0.maprootshare = \"TRUE\"\n" \ + "ide0:0.devicetype = \"cdrom-raw\"\n" \ + "ide0:0.filename = \"auto detect\"\n" \ + "ide0:0.present = \"TRUE\"\n" \ + "scsi0.present = \"TRUE\"\n" \ + "scsi0:0.filename = \"disk-000018.vmdk\"\n" \ + "scsi0:0.present = \"TRUE\"\n" \ + "scsi0:1.filename = \"another_one.vmdk\"\n" \ + "scsi0:1.present = \"TRUE\"\n" end - describe "#get_disk_size" do - before do - allow(File).to receive(:exist?).and_return(true) - end + before do + allow(vmx).to receive(:[]=) + allow(vmx).to receive(:keys).and_return([]) + allow(instance).to receive(:vmx_modify).and_yield(vmx) + end - it "extracts disk size" do - allow(File).to receive(:foreach) - .and_yield("createType=\"monolithicSparse\"\n") - .and_yield("\n") - .and_yield("# Extent description\n") - .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") - .and_yield("\n") - .and_yield("# The Disk Data Base\n") - expect(instance.get_disk_size("/some/path.vmdk")).to eq(2147483648) - end + it "removes a disk" do + allow(File).to receive(:exist?).and_return(true) + expect(instance).to receive(:vdiskmanager) + expect(vmx).to receive(:delete).with("scsi0:1.filename") + expect(vmx).to receive(:delete).with("scsi0:1.present") + instance.remove_disk("another_one.vmdk") + end - it "sums disks size" do - allow(File).to receive(:foreach) - .and_yield("createType=\"monolithicSparse\"\n") - .and_yield("\n") - .and_yield("# Extent description\n") - .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") - .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") - .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") - .and_yield("\n") - .and_yield("# The Disk Data Base\n") - expect(instance.get_disk_size("/some/path.vmdk")).to eq(6442450944) - end + it "gracefully handles non existent disk" do + allow(File).to receive(:exist?).and_return(false) + expect(instance).not_to receive(:vdiskmanager) + expect(vmx).not_to receive(:delete) + instance.remove_disk("oops.vmdk") + end + end - it "gracefully handles non existent disk" do - allow(File).to receive(:exist?).and_return(false) - expect(instance.get_disk_size("/some/path/doesnt/exist.vmdk")).to eq(nil) - end + describe "#get_disk_size" do + before do + allow(File).to receive(:exist?).and_return(true) + end + + it "extracts disk size" do + allow(File).to receive(:foreach) + .and_yield("createType=\"monolithicSparse\"\n") + .and_yield("\n") + .and_yield("# Extent description\n") + .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") + .and_yield("\n") + .and_yield("# The Disk Data Base\n") + expect(instance.get_disk_size("/some/path.vmdk")).to eq(2147483648) + end + + it "sums disks size" do + allow(File).to receive(:foreach) + .and_yield("createType=\"monolithicSparse\"\n") + .and_yield("\n") + .and_yield("# Extent description\n") + .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") + .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") + .and_yield("RW 4194304 SPARSE \"another_one.vmdk\"\n") + .and_yield("\n") + .and_yield("# The Disk Data Base\n") + expect(instance.get_disk_size("/some/path.vmdk")).to eq(6442450944) + end + + it "gracefully handles non existent disk" do + allow(File).to receive(:exist?).and_return(false) + expect(instance.get_disk_size("/some/path/doesnt/exist.vmdk")).to eq(nil) end + end - describe "#add_disk_to_vmx" do - let(:vmx) { double("vmx") } + describe "#add_disk_to_vmx" do + let(:vmx) { double("vmx") } - before do - allow(vmx).to receive(:[]=) - allow(vmx).to receive(:keys).and_return([]) - allow(instance).to receive(:vmx_modify).and_yield(vmx) - end + before do + allow(vmx).to receive(:[]=) + allow(vmx).to receive(:keys).and_return([]) + allow(instance).to receive(:vmx_modify).and_yield(vmx) + end - it "adds config to vmx file" do - expect(vmx).to receive(:[]=).with("ide0.present", "TRUE") - expect(vmx).to receive(:[]=).with("ide0:1.foo", "bar") - expect(vmx).to receive(:[]=).with("ide0:1.baz", "goo") - expect(vmx).to receive(:[]=).with("ide0:1.filename", "/some/file.txt") - expect(vmx).to receive(:[]=).with("ide0:1.present", "TRUE") - instance.add_disk_to_vmx("/some/file.txt", "ide0:1", {"foo"=>"bar", "baz"=>"goo"}) - end + it "adds config to vmx file" do + expect(vmx).to receive(:[]=).with("ide0.present", "TRUE") + expect(vmx).to receive(:[]=).with("ide0:1.foo", "bar") + expect(vmx).to receive(:[]=).with("ide0:1.baz", "goo") + expect(vmx).to receive(:[]=).with("ide0:1.filename", "/some/file.txt") + expect(vmx).to receive(:[]=).with("ide0:1.present", "TRUE") + instance.add_disk_to_vmx("/some/file.txt", "ide0:1", {"foo"=>"bar", "baz"=>"goo"}) end + end - describe "#remove_disk_from_vmx" do - let(:vmx) { double("vmx") } + describe "#remove_disk_from_vmx" do + let(:vmx) { double("vmx") } - let(:vmx_contents) do - "ide0:1.filename = \"/some/file.txt\"\n" - end + let(:vmx_contents) do + "ide0:1.filename = \"/some/file.txt\"\n" + end - before do - allow(vmx).to receive(:[]=) - allow(vmx).to receive(:keys).and_return([]) - allow(instance).to receive(:vmx_modify).and_yield(vmx) - end + before do + allow(vmx).to receive(:[]=) + allow(vmx).to receive(:keys).and_return([]) + allow(instance).to receive(:vmx_modify).and_yield(vmx) + end - it "adds config to vmx file" do - expect(vmx).to receive(:delete).with("ide0:1.foo") - expect(vmx).to receive(:delete).with("ide0:1.baz") - expect(vmx).to receive(:delete).with("ide0:1.filename") - expect(vmx).to receive(:delete).with("ide0:1.present") - instance.remove_disk_from_vmx("/some/file.txt", ["foo", "baz"]) - end + it "adds config to vmx file" do + expect(vmx).to receive(:delete).with("ide0:1.foo") + expect(vmx).to receive(:delete).with("ide0:1.baz") + expect(vmx).to receive(:delete).with("ide0:1.filename") + expect(vmx).to receive(:delete).with("ide0:1.present") + instance.remove_disk_from_vmx("/some/file.txt", ["foo", "baz"]) end + end - describe "#snapshot_tree" do - let(:vmx) { double("vmx") } + describe "#snapshot_tree" do + let(:vmx) { double("vmx") } - context "with a simple hierarchy of snapshots" do - let(:vmrun_result){ double(stdout: """Total snapshots: 10 + context "with a simple hierarchy of snapshots" do + let(:vmrun_result){ double(stdout: """Total snapshots: 10 Snapshot \tSnapshot 2 \t\tSnapshot 3 """) } - before do - expect(instance).to receive(:vmrun).with("listSnapshots", vmx_file.to_s, "showTree").and_return(vmrun_result) - end + before do + expect(instance).to receive(:vmrun).with("listSnapshots", vmx_file.to_s, "showTree").and_return(vmrun_result) + end - it "builds a snapshot tree" do - result = instance.snapshot_tree - expected_result = ["Snapshot", "Snapshot/Snapshot2", "Snapshot/Snapshot2/Snapshot3",] - expect(result == expected_result).to be_truthy - end + it "builds a snapshot tree" do + result = instance.snapshot_tree + expected_result = ["Snapshot", "Snapshot/Snapshot2", "Snapshot/Snapshot2/Snapshot3",] + expect(result == expected_result).to be_truthy end - - context "with a complicated hierarchy of snapshots" do - let(:vmrun_result){ double(stdout: """Total snapshots: 10 + end + + context "with a complicated hierarchy of snapshots" do + let(:vmrun_result){ double(stdout: """Total snapshots: 10 Snapshot \tSnapshot 2 \t\tSnapshot 3 @@ -1016,18 +1017,53 @@ \t\t\tSnapshot 8 \t\t\t\tSnapshot 10 \tSnapshot 9""") } - before do - expect(instance).to receive(:vmrun).with("listSnapshots", vmx_file.to_s, "showTree").and_return(vmrun_result) + before do + expect(instance).to receive(:vmrun).with("listSnapshots", vmx_file.to_s, "showTree").and_return(vmrun_result) + end + + it "builds a snapshot tree" do + result = instance.snapshot_tree + expected_result = ["Snapshot", "Snapshot/Snapshot2", "Snapshot/Snapshot2/Snapshot3", + "Snapshot/Snapshot2/Snapshot3/Snapshot6", "Snapshot/Snapshot2/Snapshot4", + "Snapshot/Snapshot5", "Snapshot/Snapshot5/Snapshot7", "Snapshot/Snapshot5/Snapshot7/Snapshot8", + "Snapshot/Snapshot5/Snapshot7/Snapshot8/Snapshot10", "Snapshot/Snapshot9" + ] + expect(result == expected_result).to be_truthy + end + end + end + + describe "#is_linked_clone?" do + context "when disk metadata file does not exist" do + it "should return false" do + expect(instance.is_linked_clone?).to eq(false) + end + end + + context "when disk metadata file exists" do + let(:disk_info_contents) { "" } + + before do + File.write(@vmx_dir.join("testing.vmsd"), disk_info_contents) + end + + context "when guest is not a linked clone" do + it "should return false" do + expect(instance.is_linked_clone?).to eq(false) end + end + + context "when guest is a linked clone" do + let(:disk_info_contents) { + '.encoding = "UTF-8" +cloneOf0 = "/dev/null/disk.vmdk" +numCloneOf = "1" +sentinel0 = "disk-cl1.vmdk" +numSentinels = "1"' + } - it "builds a snapshot tree" do - result = instance.snapshot_tree - expected_result = ["Snapshot", "Snapshot/Snapshot2", "Snapshot/Snapshot2/Snapshot3", - "Snapshot/Snapshot2/Snapshot3/Snapshot6", "Snapshot/Snapshot2/Snapshot4", - "Snapshot/Snapshot5", "Snapshot/Snapshot5/Snapshot7", "Snapshot/Snapshot5/Snapshot7/Snapshot8", - "Snapshot/Snapshot5/Snapshot7/Snapshot8/Snapshot10", "Snapshot/Snapshot9" - ] - expect(result == expected_result).to be_truthy + it "should return true" do + expect(instance.is_linked_clone?).to eq(true) end end end