vagrant-qemu 0.6.0 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00ee57a8b6cb0315b36c0084239e92ed69633698845945e6290ba289d71e2889
4
- data.tar.gz: 99985f04eabced2eabae1914eacb19f053f13613a4f18d40047ac57440bd4a52
3
+ metadata.gz: 440c288ae31987a57f90398d4b8c621ff32377ca8e6479a64e74d221fde483b3
4
+ data.tar.gz: 1dbd3b2b5fcb3d0bd68f384fd9cb6bde0700552c7960e0ae79d536b6ed6b5841
5
5
  SHA512:
6
- metadata.gz: ffc285799ee3f7be527da85b03d539c6f1f6b9278bc474c00b63eef41ea937bd8d5bf17aae125d801d3e1a9404967b1df9f0fa2b3173f3db452ed0fc60a12727
7
- data.tar.gz: 43601507f85d2bb8e884e24795cdbb64c4d2d03f3842c1a8feb5cefe437241cdc0bcbbc531da4036540b3e19f6aaf831f9b35cad6c91c29c32da42be5f90abb0
6
+ metadata.gz: e067f314f5c759d43ba2a3b49980c9610847abdfbd360dfa2007e65b9eaee0d7d8f0bfd6f0a9c3e393f8b7f238499eb672376c1a8550a60e97d81d6c6f1278c4
7
+ data.tar.gz: c8576beb1bbb8da421f5c4e8d4eabd4e77b536bcca3abae4b1c39d3fe47d5817589452dda06b0af492d0776133c3780f7da03004dd058e68b3358ba797bc8044
data/CHANGELOG.md CHANGED
@@ -169,3 +169,29 @@
169
169
  binary. New config: `socket_vmnet_socket` and `socket_vmnet_client` (default
170
170
  to the Homebrew prefix). The static IP must fall within the daemon's subnet
171
171
  (`--vmnet-gateway`, default `192.168.105.1/24`); see the README. (#41)
172
+
173
+ # 0.6.1 (2026-07-22)
174
+
175
+ * Fix `vagrant reload` (and any other same-process halt-then-start, e.g. the
176
+ `vagrant-reload` plugin) failing with a QEMU image write-lock error when a
177
+ custom disk (`config.vm.disk`) was configured. `configure_disks` re-runs on
178
+ every `action_start`, but the driver's attached-disk list only ever
179
+ accumulated, so a reload attached each custom disk twice on the same qemu
180
+ command line. (#41)
181
+ * Fix `qemu-img create` unconditionally recreating an already-existing custom
182
+ disk on every `configure_disks` call, which silently wiped the disk's
183
+ contents on every reload (independent of the bug above — resizing an
184
+ existing disk to a new configured size still isn't supported).
185
+ * Fix the cloud-init network seed ISO (used by `advanced_network` without a
186
+ core `cloud_init` user-data config, i.e. the `net_mode` examples above)
187
+ being re-registered as a duplicate disk on every same-process reload,
188
+ hitting the same QEMU write-lock error via a different disk.
189
+
190
+ # 0.6.2 (2026-07-22)
191
+
192
+ * Fix the default QEMU args crashing the VM on distros whose qemu-kvm build
193
+ doesn't include the legacy parallel port device (e.g. Rocky Linux, and
194
+ likely RHEL and other derivatives). `-parallel null` still instantiates
195
+ that device, just with its output discarded; `-parallel none` — now the
196
+ default — skips creating it entirely, matching how the other default args
197
+ (`-monitor`/`-display`/`-vga`) already disable their optional devices. (#80)
@@ -24,6 +24,10 @@ module VagrantPlugins
24
24
  # user-data seed.
25
25
  CORE_SEED_DISK_NAME = "vagrant-cloud_init-disk".freeze
26
26
 
27
+ # Disk name this action gives its own network-only seed (no core
28
+ # user-data seed present).
29
+ OWN_SEED_DISK_NAME = "vagrant-qemu-network-disk".freeze
30
+
27
31
  def initialize(app, env)
28
32
  @app = app
29
33
  @logger = Log4r::Logger.new("vagrant_qemu::action::cloud_init_network")
@@ -55,13 +59,24 @@ module VagrantPlugins
55
59
  private
56
60
 
57
61
  # No core cloud-init seed: build our own network-only seed and attach
58
- # it as a fresh :dvd disk.
62
+ # it as a fresh :dvd disk. This re-runs on every action_start
63
+ # (including a same-process reload), so a disk already registered
64
+ # from an earlier run is rebuilt in place rather than re-registered
65
+ # -- machine.config.vm.disks has no dedup of its own.
59
66
  def attach_network_seed(machine, env, pn)
60
- iso_path = build_seed(machine, env, pn,
67
+ existing = machine.config.vm.disks.find do |d|
68
+ d.type == :dvd && d.name == OWN_SEED_DISK_NAME
69
+ end
70
+
71
+ iso_path = existing ? Pathname.new(existing.file) : machine.data_dir.join("vagrant-qemu-network.iso")
72
+
73
+ build_seed(machine, env, pn,
61
74
  user_data: "#cloud-config\n",
62
- file_destination: machine.data_dir.join("vagrant-qemu-network.iso"))
75
+ file_destination: iso_path)
76
+
77
+ return if existing
63
78
 
64
- machine.config.vm.disk :dvd, file: iso_path.to_s, name: "vagrant-qemu-network-disk"
79
+ machine.config.vm.disk :dvd, file: iso_path.to_s, name: OWN_SEED_DISK_NAME
65
80
  machine.config.vm.disks.each do |d|
66
81
  d.finalize! if d.type == :dvd && d.file == iso_path.to_s
67
82
  end
@@ -32,6 +32,11 @@ module VagrantPlugins
32
32
  # @param [VagrantPlugins::Kernel_V2::VagrantConfigDisk] defined_disks
33
33
  # @return [Hash] configured_disks - A hash of all the current configured disks
34
34
  def self.configure_disks(machine, defined_disks)
35
+ # Vagrant re-invokes this on every action_start (including a
36
+ # same-process reload), always passing the complete current disk
37
+ # list -- reset before the empty check so a disk removed from the
38
+ # Vagrantfile doesn't leave a stale entry attached either.
39
+ machine.provider.driver.reset_attached_drives!
35
40
  return {} if defined_disks.empty?
36
41
 
37
42
  configured_disks = {disk: [], floppy: [], dvd: []}
@@ -79,12 +84,17 @@ module VagrantPlugins
79
84
  def self.setup_disk(machine, disk)
80
85
  disk_dir = machine.provider.driver.disk_dir
81
86
  disk_path = disk_dir.join("#{disk.name}.#{disk.disk_ext}")
82
- args = ["create", "-f", "qcow2"]
83
87
 
84
88
  disk_provider_config = disk.provider_config[:qemu] if disk.provider_config
85
- args.push(disk_path.to_s)
86
- args.push("#{disk.size}")
87
- machine.provider.driver.execute("qemu-img", *args)
89
+
90
+ # configure_disks re-runs on every action_start (including a
91
+ # same-process reload); qemu-img create truncates an existing file
92
+ # unconditionally, so skip it once the disk already exists instead
93
+ # of silently wiping it. Resizing an existing disk isn't handled.
94
+ if !disk_path.file?
95
+ args = ["create", "-f", "qcow2", disk_path.to_s, "#{disk.size}"]
96
+ machine.provider.driver.execute("qemu-img", *args)
97
+ end
88
98
 
89
99
  {UUID: disk.id, Name: disk.name, Path: disk_path.to_s, primary: !!disk.primary}
90
100
  end
@@ -120,7 +120,7 @@ module VagrantPlugins
120
120
  @debug_port = nil if @debug_port == UNSET_VALUE
121
121
  @no_daemonize = false if @no_daemonize == UNSET_VALUE
122
122
  @firmware_format = "raw" if @firmware_format == UNSET_VALUE
123
- @other_default = %W(-parallel null -monitor none -display none -vga none) if @other_default == UNSET_VALUE
123
+ @other_default = %W(-parallel none -monitor none -display none -vga none) if @other_default == UNSET_VALUE
124
124
  @extra_image_opts = nil if @extra_image_opts == UNSET_VALUE
125
125
  @graceful_timeout = 60 if @graceful_timeout == UNSET_VALUE
126
126
  @advanced_network = false if @advanced_network == UNSET_VALUE
@@ -472,6 +472,14 @@ module VagrantPlugins
472
472
  end
473
473
  end
474
474
 
475
+ # Vagrant's Disk middleware always passes the *complete* current disk
476
+ # list and re-runs on every action_start (including a same-process
477
+ # reload, which reuses this Driver instance) -- so attach_disk/attach_dvd
478
+ # must be rebuilt from a clean slate each time, not accumulated across calls.
479
+ def reset_attached_drives!
480
+ @attached_drives = {disk: [], floppy: [], dvd: []}
481
+ end
482
+
475
483
  def attach_dvd(disk)
476
484
  @attached_drives[:dvd] << disk
477
485
  end
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
2
  module QEMU
3
- VERSION = '0.6.0'
3
+ VERSION = '0.6.2'
4
4
  end
5
5
  end
@@ -32,4 +32,76 @@ describe "extra disk attachment end-to-end", :requires_qemu do
32
32
  expect(ssh[:exit_code]).to eq 0
33
33
  expect(ssh[:stdout].lines.last.to_s.strip.to_i).to be >= 2
34
34
  end
35
+
36
+ it "reload with an extra disk does not hit a QEMU image-lock error" do
37
+ File.write(@work_dir.join("Vagrantfile"), <<~RUBY)
38
+ Vagrant.configure("2") do |config|
39
+ config.vm.box = "#{test_box}"
40
+ config.vm.box_check_update = false
41
+ config.vm.synced_folder ".", "/vagrant", disabled: true
42
+ config.vm.disk :disk, name: "extra", size: "1GB"
43
+ config.vm.provider "qemu" do |qe|
44
+ qe.memory = "2G"
45
+ end
46
+ end
47
+ RUBY
48
+
49
+ result = vagrant_up(@work_dir, timeout: 600)
50
+ expect(result[:exit_code]).to eq 0
51
+
52
+ # This is the exact same-process halt->start path a same-process reload
53
+ # takes (issue #41: reload used to re-attach the extra disk a second
54
+ # time, so QEMU refused its own duplicate -drive with a write-lock error).
55
+ reload = vagrant_reload(@work_dir, timeout: 600)
56
+ expect(reload[:exit_code]).to eq 0
57
+ expect(reload[:stderr]).not_to match(/Failed to get "write" lock/)
58
+
59
+ ssh = vagrant_ssh(@work_dir, command: %q{lsblk -d -n -o TYPE | grep -c '^disk$'})
60
+ expect(ssh[:exit_code]).to eq 0
61
+ expect(ssh[:stdout].lines.last.to_s.strip.to_i).to be >= 2
62
+ end
63
+
64
+ it "reload does not wipe data already written to an extra disk" do
65
+ File.write(@work_dir.join("Vagrantfile"), <<~RUBY)
66
+ Vagrant.configure("2") do |config|
67
+ config.vm.box = "#{test_box}"
68
+ config.vm.box_check_update = false
69
+ config.vm.synced_folder ".", "/vagrant", disabled: true
70
+ config.vm.disk :disk, name: "extra", size: "1GB"
71
+ config.vm.provider "qemu" do |qe|
72
+ qe.memory = "2G"
73
+ end
74
+ end
75
+ RUBY
76
+
77
+ result = vagrant_up(@work_dir, timeout: 600)
78
+ expect(result[:exit_code]).to eq 0
79
+
80
+ setup = vagrant_ssh(@work_dir, command: <<~SH)
81
+ set -e
82
+ dev=$(lsblk -d -n -o NAME,TYPE | awk '$2=="disk"{print $1}' | grep -v vda | head -1)
83
+ sudo mkfs.ext4 -F /dev/$dev
84
+ sudo mkdir -p /mnt/extra
85
+ sudo mount /dev/$dev /mnt/extra
86
+ echo marker-should-survive-reload | sudo tee /mnt/extra/marker.txt
87
+ sudo umount /mnt/extra
88
+ SH
89
+ expect(setup[:exit_code]).to eq 0
90
+
91
+ # configure_disks unconditionally ran `qemu-img create` on every
92
+ # action_start (including this same-process reload), silently
93
+ # truncating the extra disk every time -- this is the regression test.
94
+ reload = vagrant_reload(@work_dir, timeout: 600)
95
+ expect(reload[:exit_code]).to eq 0
96
+
97
+ check = vagrant_ssh(@work_dir, command: <<~SH)
98
+ set -e
99
+ dev=$(lsblk -d -n -o NAME,TYPE | awk '$2=="disk"{print $1}' | grep -v vda | head -1)
100
+ sudo mkdir -p /mnt/extra
101
+ sudo mount /dev/$dev /mnt/extra
102
+ cat /mnt/extra/marker.txt
103
+ SH
104
+ expect(check[:exit_code]).to eq 0
105
+ expect(check[:stdout]).to include("marker-should-survive-reload")
106
+ end
35
107
  end
data/spec/spec_helper.rb CHANGED
@@ -20,6 +20,7 @@ require "vagrant-qemu/action/start_instance"
20
20
  require "vagrant-qemu/action/read_state"
21
21
  require "vagrant-qemu/action/stop_instance"
22
22
  require "vagrant-qemu/action/prepare_forwarded_port_collision_params"
23
+ require "vagrant-qemu/cap/disk"
23
24
 
24
25
  RSpec.configure do |config|
25
26
  config.expect_with :rspec do |expectations|
@@ -33,8 +33,15 @@ describe VagrantPlugins::QEMU::Action::CloudInitNetwork do
33
33
 
34
34
  allow(vm_config).to receive(:networks).and_return(networks)
35
35
  allow(vm_config).to receive(:cloud_init_configs).and_return([])
36
- allow(vm_config).to receive(:disk)
37
- allow(vm_config).to receive(:disks).and_return([])
36
+
37
+ # Stateful, unlike a plain double: .disk actually appends, so a test can
38
+ # call the action twice (simulating a same-process reload re-running
39
+ # action_start) and observe whether registration is idempotent.
40
+ @disks_registered = []
41
+ allow(vm_config).to receive(:disks) { @disks_registered }
42
+ allow(vm_config).to receive(:disk) do |type, **opts|
43
+ @disks_registered << double("disk_config", type: type, name: opts[:name], file: opts[:file], finalize!: nil)
44
+ end
38
45
 
39
46
  allow(host).to receive(:capability?).with(:create_iso).and_return(true)
40
47
 
@@ -129,6 +136,14 @@ describe VagrantPlugins::QEMU::Action::CloudInitNetwork do
129
136
  end
130
137
  end
131
138
 
139
+ it "does not register a second seed disk on a repeated call (same-process reload)" do
140
+ run_action
141
+ expect(vm_config.disks.length).to eq(1)
142
+
143
+ run_action
144
+ expect(vm_config.disks.length).to eq(1)
145
+ end
146
+
132
147
  it "raises when the host cannot build ISOs" do
133
148
  allow(host).to receive(:capability?).with(:create_iso).and_return(false)
134
149
  expect { run_action }.to raise_error(Vagrant::Errors::CreateIsoHostCapNotFound)
@@ -0,0 +1,104 @@
1
+ require "spec_helper"
2
+
3
+ describe VagrantPlugins::QEMU::Cap::Disk, ".configure_disks" do
4
+ let(:vm_id) { "vq_testid123" }
5
+
6
+ around(:each) do |example|
7
+ with_temp_dir do |dir|
8
+ @data_dir = dir.join("data")
9
+ @tmp_base = dir.join("tmp")
10
+ FileUtils.mkdir_p(@data_dir)
11
+ FileUtils.mkdir_p(@tmp_base)
12
+ FileUtils.mkdir_p(@data_dir.join(vm_id))
13
+ example.run
14
+ end
15
+ end
16
+
17
+ # A real Driver (not a double) -- attached_drives accumulation is the exact
18
+ # mechanism under test, so it must be the production object.
19
+ let(:driver) { VagrantPlugins::QEMU::Driver.new(vm_id, @data_dir, @tmp_base) }
20
+
21
+ let(:machine) do
22
+ provider = double("provider", driver: driver)
23
+ double("machine", provider: provider)
24
+ end
25
+
26
+ let(:disk) do
27
+ double("disk",
28
+ type: :disk, name: "disk1", disk_ext: "qcow2", size: "10G",
29
+ id: "disk1-uuid", primary: false, provider_config: nil)
30
+ end
31
+
32
+ before do
33
+ # Avoid actually shelling out to qemu-img.
34
+ allow(driver).to receive(:execute)
35
+ end
36
+
37
+ it "reflects one entry per disk after a single configure_disks call" do
38
+ described_class.configure_disks(machine, [disk])
39
+
40
+ expect(driver.attached_drives[:disk].length).to eq(1)
41
+ end
42
+
43
+ it "does not duplicate a disk across a second configure_disks call on the same driver (reload)" do
44
+ described_class.configure_disks(machine, [disk])
45
+ # action_start (and its Disk middleware) re-runs on the same Driver
46
+ # instance during a same-process reload (action_halt -> action_start).
47
+ described_class.configure_disks(machine, [disk])
48
+
49
+ expect(driver.attached_drives[:disk].length).to eq(1)
50
+ end
51
+
52
+ it "does not accumulate across repeated reloads" do
53
+ 3.times { described_class.configure_disks(machine, [disk]) }
54
+
55
+ expect(driver.attached_drives[:disk].length).to eq(1)
56
+ end
57
+
58
+ it "clears a stale disk once it is removed from the Vagrantfile" do
59
+ described_class.configure_disks(machine, [disk])
60
+ expect(driver.attached_drives[:disk].length).to eq(1)
61
+
62
+ described_class.configure_disks(machine, [])
63
+
64
+ expect(driver.attached_drives[:disk]).to be_empty
65
+ end
66
+
67
+ describe "disk file idempotency (reload must not wipe existing disk data)" do
68
+ let(:disk_path) { @data_dir.join(vm_id).join("disk1.qcow2") }
69
+
70
+ it "does not recreate the qcow2 file when it already exists" do
71
+ FileUtils.touch(disk_path)
72
+
73
+ expect(driver).not_to receive(:execute)
74
+ .with("qemu-img", "create", "-f", "qcow2", disk_path.to_s, "10G")
75
+
76
+ described_class.configure_disks(machine, [disk])
77
+ end
78
+
79
+ it "does not recreate on a second configure_disks call (reload)" do
80
+ # Simulate the real qemu-img side effect (the outer stub is a no-op),
81
+ # so the second call's existence check reflects what actually happens.
82
+ allow(driver).to receive(:execute) do |*args|
83
+ FileUtils.touch(disk_path) if args[0, 2] == ["qemu-img", "create"]
84
+ end
85
+
86
+ described_class.configure_disks(machine, [disk])
87
+ expect(File.exist?(disk_path)).to be true
88
+
89
+ expect(driver).not_to receive(:execute)
90
+ .with("qemu-img", "create", "-f", "qcow2", disk_path.to_s, "10G")
91
+
92
+ described_class.configure_disks(machine, [disk])
93
+ end
94
+
95
+ it "creates the qcow2 file when it does not exist yet" do
96
+ expect(File.exist?(disk_path)).to be false
97
+
98
+ expect(driver).to receive(:execute)
99
+ .with("qemu-img", "create", "-f", "qcow2", disk_path.to_s, "10G")
100
+
101
+ described_class.configure_disks(machine, [disk])
102
+ end
103
+ end
104
+ end
@@ -50,6 +50,15 @@ describe VagrantPlugins::QEMU::Config do
50
50
  it "ssh_port stays 50022 regardless of host" do
51
51
  expect(finalized(host_arch: "x86_64", host_accel: "kvm").ssh_port).to eq 50022
52
52
  end
53
+
54
+ it "disables the parallel port instead of attaching a null-backed device" do
55
+ # "-parallel null" still instantiates the isa-parallel device (guest sees
56
+ # it, output discarded); some distro qemu-kvm builds (e.g. Rocky/RHEL)
57
+ # don't compile that device in, so instantiating it crashes. "none" skips
58
+ # device creation entirely and works everywhere.
59
+ expect(finalized(host_arch: "x86_64", host_accel: "kvm").other_default)
60
+ .to eq %w(-parallel none -monitor none -display none -vga none)
61
+ end
53
62
  end
54
63
 
55
64
  describe "#default_qemu_dir" do
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-qemu
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.6.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - ppggff
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-07-21 00:00:00.000000000 Z
11
+ date: 2026-07-22 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Enables Vagrant to manage machines with QEMU.
14
14
  email: pgf00a@gmail.com
@@ -83,6 +83,7 @@ files:
83
83
  - spec/unit/action/start_instance_port_spec.rb
84
84
  - spec/unit/action/start_instance_spec.rb
85
85
  - spec/unit/action/warn_networks_spec.rb
86
+ - spec/unit/cap/disk_spec.rb
86
87
  - spec/unit/config_spec.rb
87
88
  - spec/unit/driver/delete_spec.rb
88
89
  - spec/unit/driver/options_yaml_spec.rb