vagrant-qemu 0.4.0 → 0.4.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 +8 -0
- data/README.md +1 -1
- data/lib/vagrant-qemu/driver.rb +36 -17
- data/lib/vagrant-qemu/version.rb +1 -1
- data/spec/e2e/halt_spec.rb +48 -0
- data/spec/unit/driver/stop_spec.rb +35 -28
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4cd40c0eae22a6a6d4f284004a86de09e0584e8887cc273e9acd4c0fa2f33e69
|
|
4
|
+
data.tar.gz: 4caaa2706bbb8f221dcc67e5164e7b38879e3cb2d709d8414fc1f0a0ed0de45b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dd592ca7bf0a75c82b58db4ed8194f8a3e31b44834538ea0fe7ae7124032871422b9b4f2f6b49122964fefc0692f02d1be4a018bae2e4948f3f8aeeb9385eaaf
|
|
7
|
+
data.tar.gz: 906b02b551b9f3c5082a3967edc33c656f9b642ae0f6bbabcd8bb40b944fa0e2d67f10c5b6b34af3291516c218d0d7657c47d9cf495d1dabceb63fa5acb4ac77
|
data/CHANGELOG.md
CHANGED
|
@@ -101,6 +101,14 @@
|
|
|
101
101
|
* Add support for cloud-init and disks
|
|
102
102
|
* Add support for resizing disk on vm setup
|
|
103
103
|
|
|
104
|
+
# 0.4.1 (2026-06-22)
|
|
105
|
+
|
|
106
|
+
* `vagrant halt` now reaps QEMU even when the guest was already halted from
|
|
107
|
+
inside (e.g. `sudo systemctl halt`), where the ACPI `system_powerdown` is a
|
|
108
|
+
no-op. Halt escalates: `system_powerdown` -> wait `graceful_timeout` -> QEMU
|
|
109
|
+
`quit` monitor command (clean: flushes and closes the disk images) -> wait
|
|
110
|
+
`graceful_timeout` -> SIGKILL as a last resort (#79)
|
|
111
|
+
|
|
104
112
|
# 0.4.0 (2026-06-12)
|
|
105
113
|
|
|
106
114
|
* Advanced networking (opt-in, `advanced_network = true`): dual-NIC `private_network`
|
data/README.md
CHANGED
|
@@ -105,7 +105,7 @@ This provider exposes a few provider-specific configuration options:
|
|
|
105
105
|
* `firmware_format` - The format of aarch64 firmware images (`edk2-aarch64-code.fd` and `edk2-arm-vars.fd`) loaded from `qemu_dir`, default: `raw`
|
|
106
106
|
* `other_default` - The other default arguments used by this plugin, default: `%W(-parallel null -monitor none -display none -vga none)`
|
|
107
107
|
* `extra_image_opts` - Options passed via `-o` to `qemu-img` when the base qcow2 images are created, default: `[]`
|
|
108
|
-
* `graceful_timeout` - Seconds to wait
|
|
108
|
+
* `graceful_timeout` - Seconds to wait at each `vagrant halt` stage before escalating, default: `60`. Halt sends ACPI `system_powerdown`, waits up to this long, then sends QEMU's `quit` monitor command (a clean shutdown that flushes and closes the disk images), waits again, and finally SIGKILLs QEMU as a last resort — so halt still completes even when the guest was already halted from inside (e.g. `sudo systemctl halt`), where `system_powerdown` is a no-op
|
|
109
109
|
* advanced networking (requires `advanced_network = true`)
|
|
110
110
|
* `advanced_network` - Enable dual-NIC advanced networking with `private_network` support, default: `false`
|
|
111
111
|
* `net_mode` - Network backend: `:auto` (detect by platform), `:vmnet_shared`, `:vmnet_host`, `:vmnet_bridged` (macOS), `:tap` (Linux), `:socket` (QEMU `socket` netdev — multicast or point-to-point, see `socket_opts`), default: `:auto`
|
data/lib/vagrant-qemu/driver.rb
CHANGED
|
@@ -212,10 +212,27 @@ module VagrantPlugins
|
|
|
212
212
|
end
|
|
213
213
|
|
|
214
214
|
def stop(options)
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
215
|
+
return unless running?
|
|
216
|
+
|
|
217
|
+
opts = with_persisted_control_port(options)
|
|
218
|
+
timeout = options[:graceful_timeout] || 60
|
|
219
|
+
|
|
220
|
+
# 1. ACPI power button (graceful). Only works if a live guest OS is
|
|
221
|
+
# there to act on it -- e.g. NOT after `systemctl halt`, which leaves
|
|
222
|
+
# QEMU running with a halted, unresponsive guest.
|
|
223
|
+
send_monitor(opts, "system_powerdown")
|
|
224
|
+
return unless still_running_after?(timeout)
|
|
225
|
+
|
|
226
|
+
# 2. Powerdown didn't take. Ask QEMU itself to quit: it stops the VM,
|
|
227
|
+
# flushes and closes the qcow2 images, then exits cleanly. Does not
|
|
228
|
+
# depend on the guest, only on the monitor being reachable.
|
|
229
|
+
@logger.warn("VM did not power down within #{timeout}s; sending 'quit' to QEMU")
|
|
230
|
+
send_monitor(opts, "quit")
|
|
231
|
+
return unless still_running_after?(timeout)
|
|
232
|
+
|
|
233
|
+
# 3. Last resort: SIGKILL the QEMU process (no flush/cleanup).
|
|
234
|
+
@logger.warn("VM still running after 'quit'; forcing kill")
|
|
235
|
+
force_kill
|
|
219
236
|
end
|
|
220
237
|
|
|
221
238
|
private
|
|
@@ -232,35 +249,37 @@ module VagrantPlugins
|
|
|
232
249
|
options.merge(:control_port => persisted[:control_port])
|
|
233
250
|
end
|
|
234
251
|
|
|
235
|
-
|
|
252
|
+
# Send a single QEMU monitor command over the control channel (TCP
|
|
253
|
+
# control_port if set, else the unix monitor socket). Best-effort: the
|
|
254
|
+
# socket may already be gone (e.g. QEMU exited from a prior command), so
|
|
255
|
+
# connection errors are swallowed.
|
|
256
|
+
def send_monitor(options, command)
|
|
236
257
|
if !options[:control_port].nil?
|
|
237
258
|
Socket.tcp("localhost", options[:control_port], connect_timeout: 5) do |sock|
|
|
238
|
-
sock.print "
|
|
259
|
+
sock.print "#{command}\n"
|
|
239
260
|
sock.close_write
|
|
240
261
|
sock.read rescue nil
|
|
241
262
|
end
|
|
242
263
|
else
|
|
243
|
-
|
|
244
|
-
unix_socket_path = id_tmp_dir.join("qemu_socket").to_s
|
|
264
|
+
unix_socket_path = @tmp_dir.join(@vm_id).join("qemu_socket").to_s
|
|
245
265
|
Socket.unix(unix_socket_path) do |sock|
|
|
246
|
-
sock.print "
|
|
266
|
+
sock.print "#{command}\n"
|
|
247
267
|
sock.close_write
|
|
248
268
|
sock.read rescue nil
|
|
249
269
|
end
|
|
250
270
|
end
|
|
271
|
+
rescue => e
|
|
272
|
+
@logger.debug("monitor command '#{command}' failed: #{e}") if @logger
|
|
251
273
|
end
|
|
252
274
|
|
|
253
|
-
|
|
275
|
+
# Poll for up to `timeout` seconds. Return false as soon as the VM has
|
|
276
|
+
# stopped; return true if it is still running when the timeout elapses.
|
|
277
|
+
def still_running_after?(timeout)
|
|
254
278
|
timeout.times do
|
|
255
|
-
return unless running?
|
|
279
|
+
return false unless running?
|
|
256
280
|
sleep 1
|
|
257
281
|
end
|
|
258
|
-
|
|
259
|
-
# Still running after timeout, force kill
|
|
260
|
-
if running?
|
|
261
|
-
@logger.warn("VM did not shut down within #{timeout}s, forcing kill")
|
|
262
|
-
force_kill
|
|
263
|
-
end
|
|
282
|
+
running?
|
|
264
283
|
end
|
|
265
284
|
|
|
266
285
|
def force_kill
|
data/lib/vagrant-qemu/version.rb
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
require_relative "helper"
|
|
2
|
+
|
|
3
|
+
# Regression for #79: `vagrant halt` must actually reap QEMU even when the
|
|
4
|
+
# guest OS was already halted from inside (e.g. `sudo systemctl halt`). In that
|
|
5
|
+
# state the ACPI `system_powerdown` is a no-op (no live guest to act on it), so
|
|
6
|
+
# halt has to escalate to QEMU's `quit` monitor command and, failing that, to
|
|
7
|
+
# SIGKILL. A short graceful_timeout keeps the escalation fast.
|
|
8
|
+
describe "halt reaps QEMU when the guest is already halted (#79)", :requires_qemu do
|
|
9
|
+
around(:each) do |example|
|
|
10
|
+
with_temp_dir do |dir|
|
|
11
|
+
@work_dir = dir.join("project")
|
|
12
|
+
FileUtils.mkdir_p(@work_dir)
|
|
13
|
+
|
|
14
|
+
File.write(@work_dir.join("Vagrantfile"), <<~RUBY)
|
|
15
|
+
Vagrant.configure("2") do |config|
|
|
16
|
+
config.vm.box = "#{test_box}"
|
|
17
|
+
config.vm.box_check_update = false
|
|
18
|
+
config.vm.synced_folder ".", "/vagrant", disabled: true
|
|
19
|
+
config.vm.provider "qemu" do |qe|
|
|
20
|
+
qe.memory = "2G"
|
|
21
|
+
qe.graceful_timeout = 5
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
RUBY
|
|
25
|
+
|
|
26
|
+
example.run
|
|
27
|
+
|
|
28
|
+
vagrant_destroy(@work_dir) rescue nil
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it "stops the VM after the guest was halted from inside" do
|
|
33
|
+
vagrant_up(@work_dir)
|
|
34
|
+
|
|
35
|
+
# Halt the guest OS from inside; the SSH connection drops as it goes down,
|
|
36
|
+
# so ignore the result. After this, system_powerdown can no longer work.
|
|
37
|
+
vagrant_ssh(@work_dir, command: "sudo systemctl halt", timeout: 30) rescue nil
|
|
38
|
+
sleep 3
|
|
39
|
+
|
|
40
|
+
# Without the escalation, QEMU would linger and the VM would still report
|
|
41
|
+
# running; with it, halt reaps the process within ~graceful_timeout.
|
|
42
|
+
result = vagrant_halt(@work_dir, timeout: 60)
|
|
43
|
+
expect(result[:exit_code]).to eq 0
|
|
44
|
+
|
|
45
|
+
status = vagrant_status(@work_dir)
|
|
46
|
+
expect(status[:stdout]).to include(",state,stopped")
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -16,47 +16,55 @@ describe VagrantPlugins::QEMU::Driver, "#stop" do
|
|
|
16
16
|
subject { described_class.new(vm_id, @data_dir, @tmp_base) }
|
|
17
17
|
|
|
18
18
|
it "does nothing when not running" do
|
|
19
|
-
|
|
19
|
+
allow(subject).to receive(:running?).and_return(false)
|
|
20
|
+
expect(subject).not_to receive(:send_monitor)
|
|
20
21
|
subject.stop(control_port: nil)
|
|
21
22
|
end
|
|
22
23
|
|
|
23
|
-
it "sends
|
|
24
|
+
it "sends system_powerdown first and stops there when the guest powers off" do
|
|
25
|
+
# running on the initial check, stopped on the first poll
|
|
24
26
|
allow(subject).to receive(:running?).and_return(true, false)
|
|
25
|
-
allow(subject).to receive(:
|
|
27
|
+
allow(subject).to receive(:send_monitor)
|
|
26
28
|
|
|
27
|
-
subject.stop(control_port: nil)
|
|
29
|
+
subject.stop(control_port: nil, graceful_timeout: 5)
|
|
28
30
|
|
|
29
|
-
expect(subject).to have_received(:
|
|
31
|
+
expect(subject).to have_received(:send_monitor).with(anything, "system_powerdown")
|
|
32
|
+
expect(subject).not_to have_received(:send_monitor).with(anything, "quit")
|
|
30
33
|
end
|
|
31
34
|
|
|
32
|
-
it "
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
allow(subject).to receive(:send_powerdown)
|
|
35
|
+
it "escalates to 'quit' when powerdown does not stop the VM" do
|
|
36
|
+
allow(subject).to receive(:sleep)
|
|
37
|
+
# stays up through the powerdown wait, then stops during the quit wait
|
|
38
|
+
allow(subject).to receive(:running?).and_return(true, true, true, false)
|
|
39
|
+
allow(subject).to receive(:send_monitor)
|
|
40
|
+
expect(subject).not_to receive(:force_kill)
|
|
39
41
|
|
|
40
|
-
subject.stop(control_port: nil, graceful_timeout:
|
|
42
|
+
subject.stop(control_port: nil, graceful_timeout: 1)
|
|
43
|
+
|
|
44
|
+
expect(subject).to have_received(:send_monitor).with(anything, "system_powerdown").ordered
|
|
45
|
+
expect(subject).to have_received(:send_monitor).with(anything, "quit").ordered
|
|
41
46
|
end
|
|
42
47
|
|
|
43
|
-
it "force kills
|
|
44
|
-
allow(subject).to receive(:running?).and_return(true)
|
|
45
|
-
allow(subject).to receive(:send_powerdown)
|
|
48
|
+
it "force kills only after both powerdown and 'quit' fail" do
|
|
46
49
|
allow(subject).to receive(:sleep)
|
|
50
|
+
allow(subject).to receive(:running?).and_return(true) # never stops
|
|
51
|
+
allow(subject).to receive(:send_monitor)
|
|
47
52
|
|
|
48
53
|
pid_dir = @tmp_base.join("vagrant-qemu", vm_id)
|
|
49
54
|
FileUtils.mkdir_p(pid_dir)
|
|
50
55
|
File.write(pid_dir.join("qemu.pid"), "999999999")
|
|
51
|
-
|
|
52
56
|
allow(Process).to receive(:kill).with("KILL", 999999999).and_raise(Errno::ESRCH)
|
|
53
57
|
|
|
54
|
-
|
|
58
|
+
subject.stop(control_port: nil, graceful_timeout: 1)
|
|
59
|
+
|
|
60
|
+
expect(subject).to have_received(:send_monitor).with(anything, "system_powerdown")
|
|
61
|
+
expect(subject).to have_received(:send_monitor).with(anything, "quit")
|
|
62
|
+
expect(Process).to have_received(:kill).with("KILL", 999999999)
|
|
55
63
|
end
|
|
56
64
|
|
|
57
65
|
it "prefers the persisted control_port over the configured one" do
|
|
58
66
|
allow(subject).to receive(:running?).and_return(true, false)
|
|
59
|
-
allow(subject).to receive(:
|
|
67
|
+
allow(subject).to receive(:send_monitor)
|
|
60
68
|
|
|
61
69
|
opts_dir = @tmp_base.join("vagrant-qemu", vm_id)
|
|
62
70
|
FileUtils.mkdir_p(opts_dir)
|
|
@@ -64,29 +72,28 @@ describe VagrantPlugins::QEMU::Driver, "#stop" do
|
|
|
64
72
|
|
|
65
73
|
subject.stop(control_port: 33333)
|
|
66
74
|
|
|
67
|
-
expect(subject).to have_received(:
|
|
68
|
-
.with(hash_including(control_port: 44444))
|
|
75
|
+
expect(subject).to have_received(:send_monitor)
|
|
76
|
+
.with(hash_including(control_port: 44444), "system_powerdown")
|
|
69
77
|
end
|
|
70
78
|
|
|
71
79
|
it "keeps the configured control_port when nothing is persisted" do
|
|
72
80
|
allow(subject).to receive(:running?).and_return(true, false)
|
|
73
|
-
allow(subject).to receive(:
|
|
81
|
+
allow(subject).to receive(:send_monitor)
|
|
74
82
|
|
|
75
83
|
subject.stop(control_port: 33333)
|
|
76
84
|
|
|
77
|
-
expect(subject).to have_received(:
|
|
78
|
-
.with(hash_including(control_port: 33333))
|
|
85
|
+
expect(subject).to have_received(:send_monitor)
|
|
86
|
+
.with(hash_including(control_port: 33333), "system_powerdown")
|
|
79
87
|
end
|
|
80
88
|
|
|
81
|
-
it "swallows ESRCH on force_kill when process already gone" do
|
|
82
|
-
allow(subject).to receive(:running?).and_return(true)
|
|
83
|
-
allow(subject).to receive(:send_powerdown)
|
|
89
|
+
it "swallows ESRCH on force_kill when the process is already gone" do
|
|
84
90
|
allow(subject).to receive(:sleep)
|
|
91
|
+
allow(subject).to receive(:running?).and_return(true)
|
|
92
|
+
allow(subject).to receive(:send_monitor)
|
|
85
93
|
|
|
86
94
|
pid_dir = @tmp_base.join("vagrant-qemu", vm_id)
|
|
87
95
|
FileUtils.mkdir_p(pid_dir)
|
|
88
96
|
File.write(pid_dir.join("qemu.pid"), "999999999")
|
|
89
|
-
|
|
90
97
|
allow(Process).to receive(:kill).and_raise(Errno::ESRCH)
|
|
91
98
|
|
|
92
99
|
expect { subject.stop(control_port: nil, graceful_timeout: 1) }.not_to raise_error
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: vagrant-qemu
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ppggff
|
|
@@ -59,6 +59,7 @@ files:
|
|
|
59
59
|
- spec/e2e/advanced_network_spec.rb
|
|
60
60
|
- spec/e2e/disk_spec.rb
|
|
61
61
|
- spec/e2e/forwarded_port_spec.rb
|
|
62
|
+
- spec/e2e/halt_spec.rb
|
|
62
63
|
- spec/e2e/helper.rb
|
|
63
64
|
- spec/e2e/provision_spec.rb
|
|
64
65
|
- spec/e2e/reload_spec.rb
|