diff --git a/include/multipass/exceptions/start_exception.h b/include/multipass/exceptions/start_exception.h index fb9d7dde29..3e4dc2ea30 100644 --- a/include/multipass/exceptions/start_exception.h +++ b/include/multipass/exceptions/start_exception.h @@ -25,8 +25,8 @@ namespace multipass class StartException : public std::runtime_error { public: - StartException(const std::string& instance_name, const std::string& what) - : runtime_error(what), instance_name(instance_name) + StartException(const std::string& instance_name, const std::string& what, bool expected = false) + : runtime_error(what), instance_name(instance_name), expected{expected} { } @@ -34,8 +34,13 @@ class StartException : public std::runtime_error { return instance_name; } + bool was_intentional() const + { + return expected; + } private: const std::string instance_name; + bool expected; }; } // namespace multipass diff --git a/include/multipass/utils.h b/include/multipass/utils.h index 044c8f0ed9..ec3bd80fc0 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -108,6 +108,8 @@ bool process_log_on_error(const QString& program, multipass::logging::Level level = multipass::logging::Level::debug, const int timeout = 30000); +bool expects_shutdown_from_cloud_init(const YAML::Node& user_data_config); + // networking helpers void validate_server_address(const std::string& value); bool valid_hostname(const std::string& name_string); diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 28d53c05fc..9ca5f65114 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -3592,6 +3592,13 @@ error_string mp::Daemon::async_wait_for_ssh_and_start_mounts_for( persist_instances(); } } + catch (const StartException& e) + { + if (!std::is_same_v || !e.was_intentional()) + { + fmt::format_to(std::back_inserter(errors), "{}", e.what()); + } + } catch (const std::exception& e) { fmt::format_to(std::back_inserter(errors), "{}", e.what()); diff --git a/src/platform/backends/hyperv/hyperv_virtual_machine.cpp b/src/platform/backends/hyperv/hyperv_virtual_machine.cpp index 183a4a6cdc..1dd4e9c1b5 100644 --- a/src/platform/backends/hyperv/hyperv_virtual_machine.cpp +++ b/src/platform/backends/hyperv/hyperv_virtual_machine.cpp @@ -154,8 +154,8 @@ mp::HyperVVirtualMachine::HyperVVirtualMachine(const VirtualMachineDescription& { if (!power_shell->run({"Get-VM", "-Name", name})) { - auto mem_size = - QString::number(desc.mem_size.in_bytes()); /* format documented in `Help(New-VM)`, under + auto mem_size = QString::number( + desc.mem_size.in_bytes()); /* format documented in `Help(New-VM)`, under `-MemoryStartupBytes` option; */ power_shell->easy_run({QString("$switch = Get-VMSwitch -Id %1").arg(default_switch_guid)}, @@ -219,8 +219,8 @@ mp::HyperVVirtualMachine::HyperVVirtualMachine(const std::string& source_vm_name // 'C:\ProgramData\Multipass\data\vault\instances\vm1-clone1\vm1\Virtual // Machines\7735327A-A22F-4926-95A1-51757D650BB7.vmcx' -Copy -GenerateNewId -VhdDestinationPath // "C:\ProgramData\Multipass\data\vault\instances\vm1-clone1\" - const fs::path exported_vm_path = - fs::path{dest_instance_dir.toStdString()} / fs::path{source_vm_name}; + const fs::path exported_vm_path = fs::path{dest_instance_dir.toStdString()} / + fs::path{source_vm_name}; const fs::path vmcx_file_path = locate_vmcx_file(exported_vm_path); // The next step needs to rename the instance, so we need to store the instance variable // $imported_vm from the Import-VM step. Because we can not use vm name to uniquely identify the @@ -248,8 +248,8 @@ mp::HyperVVirtualMachine::HyperVVirtualMachine(const std::string& source_vm_name "Could not remove the cloud-init-config.iso file from the virtual machine"); // 5. Add-VMDvdDrive -VMName vm1-clone1 -Path // 'C:\ProgramData\Multipass\data\vault\instances\vm1-clone1\cloud-init-config.iso' - const fs::path dest_cloud_init_path = - fs::path{dest_instance_dir.toStdString()} / cloud_init_file_name; + const fs::path dest_cloud_init_path = fs::path{dest_instance_dir.toStdString()} / + cloud_init_file_name; power_shell->easy_run({"Add-VMDvdDrive", "-VMName", name, @@ -350,7 +350,7 @@ void mp::HyperVVirtualMachine::start() { state = instance_state_for(power_shell.get(), name); handle_state_update(); - throw StartException{vm_name, output_err.toStdString()}; + throw StartException{vm_name, output_err.toStdString(), false}; } } diff --git a/src/platform/backends/shared/base_virtual_machine.cpp b/src/platform/backends/shared/base_virtual_machine.cpp index 28a1facb23..2e747b9022 100644 --- a/src/platform/backends/shared/base_virtual_machine.cpp +++ b/src/platform/backends/shared/base_virtual_machine.cpp @@ -326,10 +326,14 @@ void mp::BaseVirtualMachine::detect_aborted_start() std::string msg{"Instance shutdown during start"}; if (!saved_error_msg.empty()) + { msg += ": " + saved_error_msg; + saved_error_msg.clear(); + } - saved_error_msg.clear(); - throw StartException(vm_name, msg); + throw StartException(vm_name, + msg, + mpu::expects_shutdown_from_cloud_init(desc.user_data_config)); } } diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index adefd0e1af..646b73fef8 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -582,11 +582,11 @@ std::pair mp::utils::get_path_split(mp::SSHSession& se { std::string absolute{get_resolved_target(session, target)}; - std::string existing = - MP_UTILS.run_in_ssh_session(session, - fmt::format("sudo /bin/bash -c 'P={:?}; while [ ! -d \"$P/\" " - "]; do P=\"${{P%/*}}\"; done; echo $P/'", - absolute)); + std::string existing = MP_UTILS.run_in_ssh_session( + session, + fmt::format("sudo /bin/bash -c 'P={:?}; while [ ! -d \"$P/\" " + "]; do P=\"${{P%/*}}\"; done; echo $P/'", + absolute)); return {existing, QDir(QString::fromStdString(existing)) @@ -690,11 +690,25 @@ auto mp::utils::find_bridge_with(const std::vector& ne const std::string& bridge_type) -> std::optional { - const auto it = - std::find_if(std::cbegin(networks), - std::cend(networks), - [&target_network, &bridge_type](const NetworkInterfaceInfo& info) { - return info.type == bridge_type && info.has_link(target_network); - }); + const auto it = std::find_if(std::cbegin(networks), + std::cend(networks), + [&target_network, &bridge_type](const NetworkInterfaceInfo& info) { + return info.type == bridge_type && + info.has_link(target_network); + }); return it == std::cend(networks) ? std::nullopt : std::make_optional(*it); } + +bool mp::utils::expects_shutdown_from_cloud_init(const YAML::Node& user_data_config) +{ + if (user_data_config["power_state"]) + { + auto ps = user_data_config["power_state"]; + if (ps["mode"]) + { + std::string mode = ps["mode"].as(); + return (mode == "poweroff" || mode == "halt"); + } + } + return false; +}