diff --git a/include/multipass/utils.h b/include/multipass/utils.h index f732556b94..2225743acd 100644 --- a/include/multipass/utils.h +++ b/include/multipass/utils.h @@ -229,6 +229,7 @@ class Utils : public Singleton virtual std::vector random_bytes(size_t len); virtual QString make_uuid(const std::optional& seed = std::nullopt) const; virtual void sleep_for(const std::chrono::milliseconds& ms) const; + virtual bool is_ipv4_valid(const std::string& ipv4) const; }; } // namespace multipass diff --git a/src/daemon/CMakeLists.txt b/src/daemon/CMakeLists.txt index cd55592620..826c91895e 100644 --- a/src/daemon/CMakeLists.txt +++ b/src/daemon/CMakeLists.txt @@ -24,6 +24,7 @@ add_library(daemon STATIC daemon_rpc.cpp default_vm_image_vault.cpp instance_settings_handler.cpp + runtime_instance_info_helper.cpp snapshot_settings_handler.cpp ubuntu_image_host.cpp) diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp index 24c25e1041..fd85b33684 100644 --- a/src/daemon/daemon.cpp +++ b/src/daemon/daemon.cpp @@ -18,6 +18,7 @@ #include "daemon.h" #include "base_cloud_init_config.h" #include "instance_settings_handler.h" +#include "runtime_instance_info_helper.h" #include "snapshot_settings_handler.h" #include @@ -1030,20 +1031,6 @@ std::string generate_unused_mac_address(std::unordered_set& s) max_tries, s.size())}; } -bool is_ipv4_valid(const std::string& ipv4) -{ - try - { - (mp::IPAddress(ipv4)); - } - catch (std::invalid_argument&) - { - return false; - } - - return true; -} - struct SnapshotPick { std::unordered_set pick; @@ -1802,7 +1789,7 @@ try // clang-format on std::string management_ip = vm.management_ipv4(); auto all_ipv4 = vm.get_all_ipv4(); - if (is_ipv4_valid(management_ip)) + if (MP_UTILS.is_ipv4_valid(management_ip)) entry->add_ipv4(management_ip); else if (all_ipv4.empty()) entry->add_ipv4("N/A"); @@ -3522,32 +3509,11 @@ void mp::Daemon::populate_instance_info(VirtualMachine& vm, timestamp->set_nanos(created_time.time().msec() * 1'000'000); if (!no_runtime_info && MP_UTILS.is_running(present_state)) - { - instance_info->set_load(vm.ssh_exec("cat /proc/loadavg | cut -d ' ' -f1-3")); - instance_info->set_memory_usage(vm.ssh_exec("free -b | grep 'Mem:' | awk '{printf $3}'")); - info->set_memory_total(vm.ssh_exec("free -b | grep 'Mem:' | awk '{printf $2}'")); - instance_info->set_disk_usage(vm.ssh_exec("df -t ext4 -t vfat --total -B1 --output=used | tail -n 1")); - info->set_disk_total(vm.ssh_exec("df -t ext4 -t vfat --total -B1 --output=size | tail -n 1")); - info->set_cpu_count(vm.ssh_exec("nproc")); - - std::string management_ip = vm.management_ipv4(); - auto all_ipv4 = vm.get_all_ipv4(); - - if (is_ipv4_valid(management_ip)) - instance_info->add_ipv4(management_ip); - else if (all_ipv4.empty()) - instance_info->add_ipv4("N/A"); - - for (const auto& extra_ipv4 : all_ipv4) - if (extra_ipv4 != management_ip) - instance_info->add_ipv4(extra_ipv4); - - auto current_release = vm.ssh_exec("cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2"); - instance_info->set_current_release(!current_release.empty() ? current_release : original_release); - - instance_info->set_cpu_times(vm.ssh_exec("head -n1 /proc/stat")); - instance_info->set_uptime(vm.ssh_exec("uptime -p | tail -c+4")); - } + RuntimeInstanceInfoHelper::populate_runtime_info(vm, + info, + instance_info, + original_release, + vm_specs.num_cores == 1); } bool mp::Daemon::is_bridged(const std::string& instance_name) diff --git a/src/daemon/runtime_instance_info_helper.cpp b/src/daemon/runtime_instance_info_helper.cpp new file mode 100644 index 0000000000..4d1bb9b6d9 --- /dev/null +++ b/src/daemon/runtime_instance_info_helper.cpp @@ -0,0 +1,107 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#include "runtime_instance_info_helper.h" + +#include +#include +#include +#include + +#include + +#include + +namespace mp = multipass; + +namespace +{ + +struct Keys +{ +public: + static constexpr auto loadavg_key = "loadavg"; + static constexpr auto mem_usage_key = "mem_usage"; + static constexpr auto mem_total_key = "mem_total"; + static constexpr auto disk_usage_key = "disk_usage"; + static constexpr auto disk_total_key = "disk_total"; + static constexpr auto cpus_key = "cpus"; + static constexpr auto current_release_key = "current_release"; +}; + +struct Cmds +{ +private: + static constexpr auto key_val_cmd = R"(echo {}: \"$(eval "{}")\")"; + static constexpr std::array key_cmds_pairs{ + std::pair{Keys::loadavg_key, "cat /proc/loadavg | cut -d ' ' -f1-3"}, + std::pair{Keys::mem_usage_key, R"(free -b | grep 'Mem:' | awk '{printf \$3}')"}, + std::pair{Keys::mem_total_key, R"(free -b | grep 'Mem:' | awk '{printf \$2}')"}, + std::pair{Keys::disk_usage_key, "df -t ext4 -t vfat --total -B1 --output=used | tail -n 1"}, + std::pair{Keys::disk_total_key, "df -t ext4 -t vfat --total -B1 --output=size | tail -n 1"}, + std::pair{Keys::cpus_key, "nproc"}, + std::pair{Keys::current_release_key, R"(cat /etc/os-release | grep 'PRETTY_NAME' | cut -d \\\" -f2)"}}; + + inline static const std::array cmds = [] { + constexpr auto n = key_cmds_pairs.size(); + std::array ret; + for (std::size_t i = 0; i < n; ++i) + { + const auto [key, cmd] = key_cmds_pairs[i]; + ret[i] = fmt::format(key_val_cmd, key, cmd); + } + + return ret; + }(); + +public: + inline static const std::string sequential_composite_cmd = fmt::to_string(fmt::join(cmds, "; ")); + inline static const std::string parallel_composite_cmd = fmt::format("{} & wait", fmt::join(cmds, "& ")); +}; +} // namespace + +void mp::RuntimeInstanceInfoHelper::populate_runtime_info(mp::VirtualMachine& vm, + mp::DetailedInfoItem* info, + mp::InstanceDetails* instance_info, + const std::string& original_release, + bool parallelize) +{ + const auto& cmd = parallelize ? Cmds::parallel_composite_cmd : Cmds::sequential_composite_cmd; + auto results = YAML::Load(vm.ssh_exec(cmd)); + + instance_info->set_load(results[Keys::loadavg_key].as()); + instance_info->set_memory_usage(results[Keys::mem_usage_key].as()); + info->set_memory_total(results[Keys::mem_total_key].as()); + instance_info->set_disk_usage(results[Keys::disk_usage_key].as()); + info->set_disk_total(results[Keys::disk_total_key].as()); + info->set_cpu_count(results[Keys::cpus_key].as()); + + auto current_release = results[Keys::current_release_key].as(); + instance_info->set_current_release(!current_release.empty() ? current_release : original_release); + + std::string management_ip = vm.management_ipv4(); + auto all_ipv4 = vm.get_all_ipv4(); + + if (MP_UTILS.is_ipv4_valid(management_ip)) + instance_info->add_ipv4(management_ip); + else if (all_ipv4.empty()) + instance_info->add_ipv4("N/A"); + + for (const auto& extra_ipv4 : all_ipv4) + if (extra_ipv4 != management_ip) + instance_info->add_ipv4(extra_ipv4); +} diff --git a/src/daemon/runtime_instance_info_helper.h b/src/daemon/runtime_instance_info_helper.h new file mode 100644 index 0000000000..004d0c6b36 --- /dev/null +++ b/src/daemon/runtime_instance_info_helper.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) Canonical, Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +#ifndef MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H +#define MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H + +#include + +namespace multipass +{ +class VirtualMachine; +class DetailedInfoItem; +class InstanceDetails; + +// Note: we could extract other code to info/list populating code here, but that is left as a future improvement +struct RuntimeInstanceInfoHelper +{ + static void populate_runtime_info(VirtualMachine& vm, + DetailedInfoItem* info, + InstanceDetails* instance_info, + const std::string& original_release, + bool parallelize); +}; + +} // namespace multipass + +#endif // MULTIPASS_RUNTIME_INSTANCE_INFO_HELPER_H diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index cbaa044c95..1193f86f05 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -555,7 +555,21 @@ mp::Path mp::Utils::derive_instances_dir(const mp::Path& data_dir, return QDir(QDir(data_dir).filePath(backend_directory_name)).filePath(instances_subdir); } -void multipass::Utils::sleep_for(const std::chrono::milliseconds& ms) const +void mp::Utils::sleep_for(const std::chrono::milliseconds& ms) const { std::this_thread::sleep_for(ms); } + +bool mp::Utils::is_ipv4_valid(const std::string& ipv4) const +{ + try + { + (mp::IPAddress(ipv4)); + } + catch (std::invalid_argument&) + { + return false; + } + + return true; +} diff --git a/tests/mock_utils.h b/tests/mock_utils.h index 300575b526..14656e188e 100644 --- a/tests/mock_utils.h +++ b/tests/mock_utils.h @@ -48,6 +48,7 @@ class MockUtils : public Utils MOCK_METHOD(std::string, run_in_ssh_session, (SSHSession & session, const std::string& cmd), (const, override)); MOCK_METHOD(QString, make_uuid, (const std::optional&), (const, override)); MOCK_METHOD(void, sleep_for, (const std::chrono::milliseconds&), (const, override)); + MOCK_METHOD(bool, is_ipv4_valid, (const std::string& ipv4), (const, override)); MP_MOCK_SINGLETON_BOILERPLATE(MockUtils, Utils); };