vagrant-qemu 0.6.0 → 0.6.1
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 +4 -4
- data/CHANGELOG.md +17 -0
- data/lib/vagrant-qemu/action/cloud_init_network.rb +19 -4
- data/lib/vagrant-qemu/cap/disk.rb +14 -4
- data/lib/vagrant-qemu/driver.rb +8 -0
- data/lib/vagrant-qemu/version.rb +1 -1
- data/spec/e2e/disk_spec.rb +72 -0
- data/spec/spec_helper.rb +1 -0
- data/spec/unit/action/cloud_init_network_spec.rb +17 -2
- data/spec/unit/cap/disk_spec.rb +104 -0
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a7af3fcc9e9a9210bea30f44da695c87fbd15c24eed79de64540bb01ec5d4842
|
|
4
|
+
data.tar.gz: 805730ceb9574de982d6c20f4b8a7e04c37b13fbdc47ca902ddde6fcf844fef5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d9613c43e9ea63da345bf0745b3825578cb9d32a8e9465b91a9beea36c571e085d9a325675d28e1a98fe655fbabffb0b29f8e6107e90a13dc504ba83e1b007c8
|
|
7
|
+
data.tar.gz: 96a64e28147f0a7c6c7994695d8b423088f7f3eb80c18288d2652120e24c5479d88d9d407455f10e0ab252a7cacbf0fb2f0c0ef2a22a403c625d3826d5002ec5
|
data/CHANGELOG.md
CHANGED
|
@@ -169,3 +169,20 @@
|
|
|
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.
|
|
@@ -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
|
-
|
|
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:
|
|
75
|
+
file_destination: iso_path)
|
|
76
|
+
|
|
77
|
+
return if existing
|
|
63
78
|
|
|
64
|
-
machine.config.vm.disk :dvd, file: iso_path.to_s, name:
|
|
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
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
data/lib/vagrant-qemu/driver.rb
CHANGED
|
@@ -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
|
data/lib/vagrant-qemu/version.rb
CHANGED
data/spec/e2e/disk_spec.rb
CHANGED
|
@@ -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
|
-
|
|
37
|
-
|
|
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
|
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.
|
|
4
|
+
version: 0.6.1
|
|
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-
|
|
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
|