Skip to content

Commit d90f6ef

Browse files
committed
Merge #1246
#1246 backends/qemu: Create/destroy Process as needed to avoid stale args (Fixes #1243)
1 parent b016102 commit d90f6ef

File tree

3 files changed

+135
-103
lines changed

3 files changed

+135
-103
lines changed

src/platform/backends/qemu/qemu_virtual_machine.cpp

Lines changed: 117 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
#include <multipass/process.h>
2828
#include <multipass/ssh/ssh_session.h>
2929
#include <multipass/utils.h>
30-
#include <multipass/virtual_machine_description.h>
3130
#include <multipass/vm_status_monitor.h>
3231

3332
#include <multipass/format.h>
@@ -193,103 +192,12 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc
193192
DNSMasqServer& dnsmasq_server, VMStatusMonitor& monitor)
194193
: VirtualMachine{instance_image_has_snapshot(desc.image.image_path) ? State::suspended : State::off, desc.vm_name},
195194
tap_device_name{tap_device_name},
196-
vm_process{make_qemu_process(
197-
desc, ((state == State::suspended) ? mp::make_optional(monitor.retrieve_metadata_for(vm_name)) : mp::nullopt),
198-
tap_device_name)},
195+
desc{desc},
199196
mac_addr{desc.mac_addr},
200197
username{desc.ssh_username},
201198
dnsmasq_server{&dnsmasq_server},
202199
monitor{&monitor}
203200
{
204-
QObject::connect(vm_process.get(), &Process::started, [this]() {
205-
mpl::log(mpl::Level::info, vm_name, "process started");
206-
on_started();
207-
});
208-
QObject::connect(vm_process.get(), &Process::ready_read_standard_output, [this]() {
209-
auto qmp_output = vm_process->read_all_standard_output();
210-
mpl::log(mpl::Level::debug, vm_name, fmt::format("QMP: {}", qmp_output));
211-
auto qmp_object = QJsonDocument::fromJson(qmp_output.split('\n').first()).object();
212-
auto event = qmp_object["event"];
213-
214-
if (!event.isNull())
215-
{
216-
if (event.toString() == "RESET" && state != State::restarting)
217-
{
218-
mpl::log(mpl::Level::info, vm_name, "VM restarting");
219-
on_restart();
220-
}
221-
else if (event.toString() == "POWERDOWN")
222-
{
223-
mpl::log(mpl::Level::info, vm_name, "VM powering down");
224-
}
225-
else if (event.toString() == "SHUTDOWN")
226-
{
227-
mpl::log(mpl::Level::info, vm_name, "VM shut down");
228-
}
229-
else if (event.toString() == "STOP")
230-
{
231-
mpl::log(mpl::Level::info, vm_name, "VM suspending");
232-
}
233-
else if (event.toString() == "RESUME")
234-
{
235-
mpl::log(mpl::Level::info, vm_name, "VM suspended");
236-
if (state == State::suspending || state == State::running)
237-
{
238-
vm_process->kill();
239-
on_suspend();
240-
}
241-
}
242-
}
243-
});
244-
245-
QObject::connect(vm_process.get(), &Process::ready_read_standard_error, [this]() {
246-
saved_error_msg = vm_process->read_all_standard_error().data();
247-
mpl::log(mpl::Level::warning, vm_name, saved_error_msg);
248-
});
249-
250-
QObject::connect(vm_process.get(), &Process::state_changed, [this](QProcess::ProcessState newState) {
251-
mpl::log(mpl::Level::info, vm_name,
252-
fmt::format("process state changed to {}", utils::qenum_to_string(newState)));
253-
});
254-
255-
QObject::connect(
256-
vm_process.get(), &Process::error_occurred, [this](QProcess::ProcessError error, QString error_string) {
257-
// We just kill the process when suspending, so we don't want to print
258-
// out any scary error messages for this state
259-
if (update_shutdown_status)
260-
{
261-
mpl::log(mpl::Level::error, vm_name,
262-
fmt::format("process error occurred {} {}", utils::qenum_to_string(error), error_string));
263-
on_error();
264-
}
265-
});
266-
267-
QObject::connect(vm_process.get(), &Process::finished, [this](ProcessState process_state) {
268-
if (process_state.exit_code)
269-
{
270-
mpl::log(mpl::Level::info, vm_name,
271-
fmt::format("process finished with exit code {}", process_state.exit_code.value()));
272-
}
273-
if (process_state.error)
274-
{
275-
if (process_state.error->state == QProcess::Crashed &&
276-
(state == State::suspending || state == State::suspended))
277-
{
278-
// when suspending, we ask Qemu to savevm. Once it confirms that's done, we kill it. Catch the "crash"
279-
mpl::log(mpl::Level::debug, vm_name, "Suspended VM successfully stopped");
280-
}
281-
else
282-
{
283-
mpl::log(mpl::Level::error, vm_name, fmt::format("error: {}", process_state.error->message));
284-
}
285-
}
286-
287-
if (update_shutdown_status || state == State::starting)
288-
{
289-
on_shutdown();
290-
}
291-
});
292-
293201
QObject::connect(this, &QemuVirtualMachine::on_delete_memory_snapshot, this,
294202
[this] {
295203
mpl::log(mpl::Level::debug, vm_name, fmt::format("Deleted memory snapshot"));
@@ -301,14 +209,23 @@ mp::QemuVirtualMachine::QemuVirtualMachine(const VirtualMachineDescription& desc
301209

302210
mp::QemuVirtualMachine::~QemuVirtualMachine()
303211
{
304-
update_shutdown_status = false;
305-
if (state == State::running)
306-
suspend();
307-
else
308-
shutdown();
212+
if (vm_process)
213+
{
214+
update_shutdown_status = false;
215+
216+
if (state == State::running)
217+
{
218+
suspend();
219+
}
220+
else
221+
{
222+
shutdown();
223+
}
224+
225+
vm_process->wait_for_finished();
226+
}
309227

310228
remove_tap_device(QString::fromStdString(tap_device_name));
311-
vm_process->wait_for_finished();
312229
}
313230

314231
void mp::QemuVirtualMachine::start()
@@ -319,12 +236,12 @@ void mp::QemuVirtualMachine::start()
319236
if (state == State::suspending)
320237
throw std::runtime_error("cannot start the instance while suspending");
321238

239+
initialize_vm_process();
240+
322241
if (state == State::suspended)
323242
{
324243
mpl::log(mpl::Level::info, vm_name, fmt::format("Resuming from a suspended state"));
325244

326-
auto metadata = monitor->retrieve_metadata_for(vm_name);
327-
328245
update_shutdown_status = true;
329246
delete_memory_snapshot = true;
330247
}
@@ -396,6 +313,7 @@ void mp::QemuVirtualMachine::suspend()
396313

397314
update_shutdown_status = false;
398315
vm_process->wait_for_finished();
316+
vm_process.reset(nullptr);
399317
}
400318
}
401319
else if (state == State::off || state == State::suspended)
@@ -448,6 +366,7 @@ void mp::QemuVirtualMachine::on_shutdown()
448366

449367
ip = nullopt;
450368
update_state();
369+
vm_process.reset(nullptr);
451370
lock.unlock();
452371
monitor->on_shutdown();
453372
}
@@ -538,3 +457,100 @@ void mp::QemuVirtualMachine::wait_until_ssh_up(std::chrono::milliseconds timeout
538457
emit on_delete_memory_snapshot();
539458
}
540459
}
460+
461+
void mp::QemuVirtualMachine::initialize_vm_process()
462+
{
463+
vm_process = make_qemu_process(
464+
desc, ((state == State::suspended) ? mp::make_optional(monitor->retrieve_metadata_for(vm_name)) : mp::nullopt),
465+
tap_device_name);
466+
467+
QObject::connect(vm_process.get(), &Process::started, [this]() {
468+
mpl::log(mpl::Level::info, vm_name, "process started");
469+
on_started();
470+
});
471+
472+
QObject::connect(vm_process.get(), &Process::ready_read_standard_output, [this]() {
473+
auto qmp_output = vm_process->read_all_standard_output();
474+
mpl::log(mpl::Level::debug, vm_name, fmt::format("QMP: {}", qmp_output));
475+
auto qmp_object = QJsonDocument::fromJson(qmp_output.split('\n').first()).object();
476+
auto event = qmp_object["event"];
477+
478+
if (!event.isNull())
479+
{
480+
if (event.toString() == "RESET" && state != State::restarting)
481+
{
482+
mpl::log(mpl::Level::info, vm_name, "VM restarting");
483+
on_restart();
484+
}
485+
else if (event.toString() == "POWERDOWN")
486+
{
487+
mpl::log(mpl::Level::info, vm_name, "VM powering down");
488+
}
489+
else if (event.toString() == "SHUTDOWN")
490+
{
491+
mpl::log(mpl::Level::info, vm_name, "VM shut down");
492+
}
493+
else if (event.toString() == "STOP")
494+
{
495+
mpl::log(mpl::Level::info, vm_name, "VM suspending");
496+
}
497+
else if (event.toString() == "RESUME")
498+
{
499+
mpl::log(mpl::Level::info, vm_name, "VM suspended");
500+
if (state == State::suspending || state == State::running)
501+
{
502+
vm_process->kill();
503+
on_suspend();
504+
}
505+
}
506+
}
507+
});
508+
509+
QObject::connect(vm_process.get(), &Process::ready_read_standard_error, [this]() {
510+
saved_error_msg = vm_process->read_all_standard_error().data();
511+
mpl::log(mpl::Level::warning, vm_name, saved_error_msg);
512+
});
513+
514+
QObject::connect(vm_process.get(), &Process::state_changed, [this](QProcess::ProcessState newState) {
515+
mpl::log(mpl::Level::info, vm_name,
516+
fmt::format("process state changed to {}", utils::qenum_to_string(newState)));
517+
});
518+
519+
QObject::connect(
520+
vm_process.get(), &Process::error_occurred, [this](QProcess::ProcessError error, QString error_string) {
521+
// We just kill the process when suspending, so we don't want to print
522+
// out any scary error messages for this state
523+
if (update_shutdown_status)
524+
{
525+
mpl::log(mpl::Level::error, vm_name,
526+
fmt::format("process error occurred {} {}", utils::qenum_to_string(error), error_string));
527+
on_error();
528+
}
529+
});
530+
531+
QObject::connect(vm_process.get(), &Process::finished, [this](ProcessState process_state) {
532+
if (process_state.exit_code)
533+
{
534+
mpl::log(mpl::Level::info, vm_name,
535+
fmt::format("process finished with exit code {}", process_state.exit_code.value()));
536+
}
537+
if (process_state.error)
538+
{
539+
if (process_state.error->state == QProcess::Crashed &&
540+
(state == State::suspending || state == State::suspended))
541+
{
542+
// when suspending, we ask Qemu to savevm. Once it confirms that's done, we kill it. Catch the "crash"
543+
mpl::log(mpl::Level::debug, vm_name, "Suspended VM successfully stopped");
544+
}
545+
else
546+
{
547+
mpl::log(mpl::Level::error, vm_name, fmt::format("error: {}", process_state.error->message));
548+
}
549+
}
550+
551+
if (update_shutdown_status || state == State::starting)
552+
{
553+
on_shutdown();
554+
}
555+
});
556+
}

src/platform/backends/qemu/qemu_virtual_machine.h

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
#include <multipass/optional.h>
2323
#include <multipass/process.h>
2424
#include <multipass/virtual_machine.h>
25+
#include <multipass/virtual_machine_description.h>
2526

2627
#include <QObject>
2728
#include <QStringList>
@@ -30,7 +31,6 @@ namespace multipass
3031
{
3132
class DNSMasqServer;
3233
class VMStatusMonitor;
33-
class VirtualMachineDescription;
3434

3535
class QemuVirtualMachine final : public QObject, public VirtualMachine
3636
{
@@ -63,8 +63,11 @@ class QemuVirtualMachine final : public QObject, public VirtualMachine
6363
void on_shutdown();
6464
void on_suspend();
6565
void on_restart();
66+
void initialize_vm_process();
67+
6668
const std::string tap_device_name;
67-
std::unique_ptr<Process> vm_process;
69+
const VirtualMachineDescription desc;
70+
std::unique_ptr<Process> vm_process{nullptr};
6871
multipass::optional<IPAddress> ip;
6972
const std::string mac_addr;
7073
const std::string username;

tests/qemu/test_qemu_backend.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,6 +162,8 @@ TEST_F(QemuBackend, verify_dnsmasq_qemuimg_and_qemu_processes_created)
162162
mp::QemuVirtualMachineFactory backend{data_dir.path()};
163163

164164
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
165+
machine->start();
166+
machine->state = mp::VirtualMachine::State::running;
165167

166168
auto processes = factory->process_list();
167169
EXPECT_TRUE(std::find_if(processes.cbegin(), processes.cend(),
@@ -172,6 +174,7 @@ TEST_F(QemuBackend, verify_dnsmasq_qemuimg_and_qemu_processes_created)
172174
[](const mpt::StubProcessFactory::ProcessInfo& process_info) {
173175
return process_info.command == "qemu-img";
174176
}) != processes.cend());
177+
175178
EXPECT_TRUE(std::find_if(processes.cbegin(), processes.cend(),
176179
[](const mpt::StubProcessFactory::ProcessInfo& process_info) {
177180
return process_info.command.startsWith("qemu-system-");
@@ -186,6 +189,8 @@ TEST_F(QemuBackend, verify_some_common_qemu_arguments)
186189
mp::QemuVirtualMachineFactory backend{data_dir.path()};
187190

188191
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
192+
machine->start();
193+
machine->state = mp::VirtualMachine::State::running;
189194

190195
auto processes = factory->process_list();
191196
auto qemu = std::find_if(processes.cbegin(), processes.cend(),
@@ -217,6 +222,8 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image)
217222
mp::QemuVirtualMachineFactory backend{data_dir.path()};
218223

219224
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
225+
machine->start();
226+
machine->state = mp::VirtualMachine::State::running;
220227

221228
auto processes = factory->process_list();
222229
auto qemu = std::find_if(processes.cbegin(), processes.cend(),
@@ -244,6 +251,8 @@ TEST_F(QemuBackend, verify_qemu_arguments_when_resuming_suspend_image_uses_metad
244251
mp::QemuVirtualMachineFactory backend{data_dir.path()};
245252

246253
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
254+
machine->start();
255+
machine->state = mp::VirtualMachine::State::running;
247256

248257
auto processes = factory->process_list();
249258
auto qemu = std::find_if(processes.cbegin(), processes.cend(),
@@ -268,6 +277,8 @@ TEST_F(QemuBackend, verify_qemu_command_version_when_resuming_suspend_image_usin
268277
mp::QemuVirtualMachineFactory backend{data_dir.path()};
269278

270279
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
280+
machine->start();
281+
machine->state = mp::VirtualMachine::State::running;
271282

272283
auto processes = factory->process_list();
273284
auto qemu = std::find_if(processes.cbegin(), processes.cend(),
@@ -304,6 +315,8 @@ TEST_F(QemuBackend, verify_qemu_arguments_from_metadata_are_used)
304315
mp::QemuVirtualMachineFactory backend{data_dir.path()};
305316

306317
auto machine = backend.create_virtual_machine(default_description, mock_monitor);
318+
machine->start();
319+
machine->state = mp::VirtualMachine::State::running;
307320

308321
auto processes = factory->process_list();
309322
auto qemu = std::find_if(processes.cbegin(), processes.cend(),

0 commit comments

Comments
 (0)