diff --git a/src/platform/backends/qemu/qemu_virtual_machine.cpp b/src/platform/backends/qemu/qemu_virtual_machine.cpp index 5fd6524fe0..d834630162 100644 --- a/src/platform/backends/qemu/qemu_virtual_machine.cpp +++ b/src/platform/backends/qemu/qemu_virtual_machine.cpp @@ -380,7 +380,13 @@ void mp::QemuVirtualMachine::shutdown(bool force) mpl::log(mpl::Level::debug, vm_name, "No process to kill"); } - if (state == State::suspended || mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag)) + const auto has_suspend_snapshot = mp::backend::instance_image_has_snapshot(desc.image.image_path, suspend_tag); + if (has_suspend_snapshot != (state == State::suspended)) // clang-format off + mpl::log(mpl::Level::warning, vm_name, fmt::format("Image has {} suspension snapshot, but the state is {}", + has_suspend_snapshot ? "a" : "no", + static_cast(state))); // clang-format on + + if (has_suspend_snapshot) { mpl::log(mpl::Level::info, vm_name, "Deleting suspend image"); mp::backend::delete_instance_suspend_image(desc.image.image_path, suspend_tag); diff --git a/tests/qemu/test_qemu_backend.cpp b/tests/qemu/test_qemu_backend.cpp index 5d3b54a2ca..63f7b2cae2 100644 --- a/tests/qemu/test_qemu_backend.cpp +++ b/tests/qemu/test_qemu_backend.cpp @@ -507,7 +507,14 @@ TEST_F(QemuBackend, forceShutdownSuspendDeletesSuspendImageAndOffState) return std::move(mock_qemu_platform); }); - auto factory = mpt::StubProcessFactory::Inject(); + mpt::MockProcessFactory::Callback snapshot_list_suspend_tag_callback = [](mpt::MockProcess* process) { + if (process->program().contains("qemu-img") && process->arguments().contains("snapshot") && + process->arguments().contains("-l")) + { + EXPECT_CALL(*process, read_all_standard_output()).WillOnce(Return(fake_snapshot_list_with_suspend_tag)); + } + }; + process_factory->register_callback(snapshot_list_suspend_tag_callback); logger_scope.mock_logger->screen_logs(mpl::Level::debug); logger_scope.mock_logger->expect_log(mpl::Level::info, "Forcing shutdown"); @@ -517,21 +524,73 @@ TEST_F(QemuBackend, forceShutdownSuspendDeletesSuspendImageAndOffState) mpt::StubVMStatusMonitor stub_monitor; mp::QemuVirtualMachineFactory backend{data_dir.path()}; - auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + machine->state = mp::VirtualMachine::State::suspended; + machine->shutdown(true); + + EXPECT_EQ(machine->current_state(), mp::VirtualMachine::State::off); + const std::vector processes = process_factory->process_list(); + EXPECT_FALSE(processes.empty()); + EXPECT_TRUE(processes.back().command == "qemu-img" && processes.back().arguments.contains("-d") && + processes.back().arguments.contains(suspend_tag)); +} + +TEST_F(QemuBackend, forceShutdownSuspendedStateButNoSuspensionSnapshotInImage) +{ + EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { + return std::move(mock_qemu_platform); + }); + + logger_scope.mock_logger->screen_logs(mpl::Level::debug); + logger_scope.mock_logger->expect_log(mpl::Level::info, "Forcing shutdown"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "No process to kill"); + logger_scope.mock_logger->expect_log(mpl::Level::warning, "Image has no suspension snapshot, but the state is 7"); + + mpt::StubVMStatusMonitor stub_monitor; + mp::QemuVirtualMachineFactory backend{data_dir.path()}; + + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); machine->state = mp::VirtualMachine::State::suspended; + machine->shutdown(true); + EXPECT_EQ(machine->current_state(), mp::VirtualMachine::State::off); +} + +TEST_F(QemuBackend, forceShutdownRunningStateButWithSuspensionSnapshotInImage) +{ + EXPECT_CALL(*mock_qemu_platform_factory, make_qemu_platform(_)).WillOnce([this](auto...) { + return std::move(mock_qemu_platform); + }); + + mpt::MockProcessFactory::Callback snapshot_list_suspend_tag_callback = [](mpt::MockProcess* process) { + if (process->program().contains("qemu-img") && process->arguments().contains("snapshot") && + process->arguments().contains("-l")) + { + EXPECT_CALL(*process, read_all_standard_output()).WillOnce(Return(fake_snapshot_list_with_suspend_tag)); + } + }; + process_factory->register_callback(snapshot_list_suspend_tag_callback); + + logger_scope.mock_logger->screen_logs(mpl::Level::debug); + logger_scope.mock_logger->expect_log(mpl::Level::info, "Forcing shutdown"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "No process to kill"); + logger_scope.mock_logger->expect_log(mpl::Level::info, "Deleting suspend image"); + logger_scope.mock_logger->expect_log(mpl::Level::warning, "Image has a suspension snapshot, but the state is 4"); + + mpt::StubVMStatusMonitor stub_monitor; + mp::QemuVirtualMachineFactory backend{data_dir.path()}; + + const auto machine = backend.create_virtual_machine(default_description, key_provider, stub_monitor); + machine->state = mp::VirtualMachine::State::running; machine->shutdown(true); EXPECT_EQ(machine->current_state(), mp::VirtualMachine::State::off); - auto processes = factory->process_list(); - EXPECT_TRUE(std::find_if(processes.cbegin(), - processes.cend(), - [](const mpt::StubProcessFactory::ProcessInfo& process_info) { - return process_info.command == "qemu-img" && process_info.arguments.contains("-d") && - process_info.arguments.contains(suspend_tag); - }) != processes.cend()); + const std::vector processes = process_factory->process_list(); + EXPECT_FALSE(processes.empty()); + EXPECT_TRUE(processes.back().command == "qemu-img" && processes.back().arguments.contains("-d") && + processes.back().arguments.contains(suspend_tag)); } TEST_F(QemuBackend, verify_dnsmasq_qemuimg_and_qemu_processes_created)