From 0fabed9cf256c71803e1a7eb34dc20d761c1f1fc Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Thu, 30 Jan 2025 22:49:16 +0300 Subject: [PATCH 01/26] HCS/HCN API wrapper implementation [wip] --- CMakeLists.txt | 2 +- src/platform/CMakeLists.txt | 1 + .../backends/hyperv_api/CMakeLists.txt | 31 ++ .../hyperv_api/hcn/hyperv_hcn_api_table.h | 84 +++ .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 252 +++++++++ .../hyperv_api/hcn/hyperv_hcn_api_wrapper.h | 92 ++++ .../hcn/hyperv_hcn_create_endpoint_params.h | 84 +++ .../hcn/hyperv_hcn_create_network_params.h | 79 +++ .../hcn/hyperv_hcn_wrapper_interface.h | 42 ++ .../hcs/hyperv_hcs_add_endpoint_params.h | 75 +++ .../hyperv_api/hcs/hyperv_hcs_api_table.h | 114 +++++ .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 479 ++++++++++++++++++ .../hyperv_api/hcs/hyperv_hcs_api_wrapper.h | 219 ++++++++ .../hcs/hyperv_hcs_compute_system_state.h | 68 +++ .../hyperv_hcs_create_compute_system_params.h | 85 ++++ .../hcs/hyperv_hcs_wrapper_interface.h | 57 +++ .../backends/hyperv_api/hyperv_api_common.cpp | 148 ++++++ .../backends/hyperv_api/hyperv_api_common.h | 61 +++ .../hyperv_api/hyperv_api_operation_result.h | 85 ++++ tests/hyperv_api/CMakeLists.txt | 18 + tests/hyperv_api/test_it_hyperv_hcn_api.cpp | 116 +++++ tests/hyperv_api/test_it_hyperv_hcs_api.cpp | 103 ++++ tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 128 +++++ vcpkg.json | 2 +- 24 files changed, 2423 insertions(+), 2 deletions(-) create mode 100644 src/platform/backends/hyperv_api/CMakeLists.txt create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h create mode 100644 src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h create mode 100644 src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h create mode 100644 src/platform/backends/hyperv_api/hyperv_api_common.cpp create mode 100644 src/platform/backends/hyperv_api/hyperv_api_common.h create mode 100644 src/platform/backends/hyperv_api/hyperv_api_operation_result.h create mode 100644 tests/hyperv_api/CMakeLists.txt create mode 100644 tests/hyperv_api/test_it_hyperv_hcn_api.cpp create mode 100644 tests/hyperv_api/test_it_hyperv_hcs_api.cpp create mode 100644 tests/hyperv_api/test_ut_hyperv_hcn_api.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 77ac411879..fd86c2282f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -275,7 +275,7 @@ if(MSVC) add_definitions(-DLIBSSH_STATIC) # otherwise adds declspec specifiers to libssh apis add_definitions(-D_SILENCE_ALL_CXX17_DEPRECATION_WARNINGS) add_definitions(-DWIN32_LEAN_AND_MEAN) - set(MULTIPASS_BACKENDS hyperv virtualbox) + set(MULTIPASS_BACKENDS hyperv hyperv_api virtualbox) set(MULTIPASS_PLATFORM windows) else() add_compile_options(-Werror -Wall -pedantic -fPIC -Wno-error=deprecated-declarations) diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index 445f933fa7..c3f50c168f 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -12,6 +12,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . + function(add_target TARGET_NAME) if(LINUX) add_library(${TARGET_NAME} STATIC diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt new file mode 100644 index 0000000000..a58f1b8770 --- /dev/null +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -0,0 +1,31 @@ +# 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 version 3 as +# published by the Free Software Foundation. +# +# 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 . +# + +if(WIN32) + # WIL is only required for Hyper-V API implementation. + find_package(WIL REQUIRED) + + add_library(hyperv_api_backend STATIC) + + target_sources(hyperv_api_backend PRIVATE + hyperv_api_common.cpp + hcn/hyperv_hcn_api_wrapper.cpp + hcs/hyperv_hcs_api_wrapper.cpp) + + target_link_libraries(hyperv_api_backend PRIVATE + fmt + utils + WIL::WIL) +endif() diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h new file mode 100644 index 0000000000..2b80a5c0fd --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -0,0 +1,84 @@ +/* + * 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_HYPERV_API_HCN_API_TABLE +#define MULTIPASS_HYPERV_API_HCN_API_TABLE + +// clang-format off +// (xmkg): clang-format is messing with the include order. +#include +#include +// clang-format on + +#include +#include + +namespace multipass::hyperv::hcn +{ + +/** + * API function table for host compute network + */ +struct HCNAPITable +{ + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcncreatenetwork + std::function CreateNetwork = &HcnCreateNetwork; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopennetwork + std::function OpenNetwork = &HcnOpenNetwork; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcndeletenetwork + std::function DeleteNetwork = &HcnDeleteNetwork; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnclosenetwork + std::function CloseNetwork = &HcnCloseNetwork; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcncreateendpoint + std::function CreateEndpoint = &HcnCreateEndpoint; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcnopenendpoint + std::function OpenEndpoint = &HcnOpenEndpoint; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcndeleteendpoint + std::function DeleteEndpoint = &HcnDeleteEndpoint; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcndeleteendpoint + std::function CloseEndpoint = &HcnCloseEndpoint; +}; + +} // namespace multipass::hyperv::hcn + +/** + * Formatter type specialization for HCNAPITable + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcn::HCNAPITable& api, FormatContext& ctx) const + { + return format_to(ctx.out(), + "CreateNetwork: ({}) | OpenNetwork: ({}) | DeleteNetwork: ({}) | CreateEndpoint: ({}) | " + "OpenEndpoint: ({}) | DeleteEndpoint: ({})", + fmt::ptr(api.CreateNetwork.target()), + fmt::ptr(api.OpenNetwork.target()), + fmt::ptr(api.DeleteNetwork.target()), + fmt::ptr(api.CreateEndpoint.target()), + fmt::ptr(api.OpenEndpoint.target()), + fmt::ptr(api.DeleteEndpoint.target())); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp new file mode 100644 index 0000000000..de59a30d9b --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -0,0 +1,252 @@ +/* + * 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 "hyperv_hcn_api_wrapper.h" +#include "../hyperv_api_common.h" +#include "hyperv_hcn_api_table.h" +#include "hyperv_hcn_create_endpoint_params.h" +#include "hyperv_hcn_create_network_params.h" +#include "hyperv_hcn_wrapper_interface.h" + +#include +#include + +// clang-format off +#include +#include +#include +#include +#include +#include +#include // HCN API uses CoTaskMem* functions to allocate memory. + +#pragma comment(lib, "computecore.lib") +#pragma comment(lib, "computenetwork.lib") +// clang-format on + +#include +#include + +#include +#include + +namespace multipass::hyperv::hcn +{ + +/** + * Category for the log messages. + */ +static constexpr const char* kLogCategory = "HyperV-HCN-Wrapper"; + +using UniqueHcnNetwork = std::unique_ptr, decltype(HCNAPITable::CloseNetwork)>; +using UniqueHcnEndpoint = std::unique_ptr, decltype(HCNAPITable::CloseEndpoint)>; +using UniqueCotaskmemString = wil::unique_cotaskmem_string; + +// --------------------------------------------------------- + +/** + * Perform a Host Compute Network API operation + * + * @param fn The API function pointer + * @param args The arguments to the function + * + * @return HCNOperationResult Result of the performed operation + */ +template +static OperationResult perform_hcn_operation(const FnType& fn, Args&&... args) +{ + + // Ensure that function to call is set. + if (nullptr == fn) + { + assert(0); + // E_POINTER means "invalid pointer", seems to be appropriate. + return {E_POINTER, L"Operation function is unbound!"}; + } + + // HCN functions will use CoTaskMemAlloc to allocate the error message buffer + // so use UniqueCotaskmemString to auto-release it with appropriate free + // function. + UniqueCotaskmemString result_msgbuf{}; + + // Perform the operation. The last argument of the all HCN operations (except + // HcnClose*) is ErrorRecord, which is a JSON-formatted document emitted by + // the API describing the error happened. Therefore, we can streamline all API + // calls through perform_operation to perform co + const ResultCode result = fn(std::forward(args)..., &result_msgbuf); + + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("perform_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result))); + + // Error message is only valid when the operation resulted in an error. + // Passing a nullptr is well-defined in "< C++23", but it's going to be + // forbidden afterwards. Going an extra mile just to be future-proof. + return {result, {result ? L"" : result_msgbuf.get()}}; +} + +// --------------------------------------------------------- + +/** + * Open an existing Host Compute Network and return a handle to it. + * + * This function is used for altering network resources, e.g. adding a new + * endpoint. + * + * @param api The HCN API table + * @param network_guid GUID of the network to open + * + * @return UniqueHcnNetwork Unique handle to the network. Non-nullptr when successful. + */ +static UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("open_network(...) > network_guid: {} ", network_guid)); + HCN_NETWORK network{nullptr}; + + const auto result = perform_hcn_operation(api.OpenNetwork, guid_from_string(network_guid), &network); + (void)result; + return UniqueHcnNetwork{network, api.CloseNetwork}; +} + +// --------------------------------------------------------- + +HCNWrapper::HCNWrapper(const HCNAPITable& api_table) : api{api_table} +{ + logging::log(logging::Level::trace, kLogCategory, fmt::format("HCNWrapper::HCNWrapper(...): api_table: {}", api)); +} + +// --------------------------------------------------------- + +auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> OperationResult +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); + constexpr auto network_settings_template = LR"""( + {{ + "Name": "{0}", + "Type": "ICS", + "Subnets" : [ + {{ + "GatewayAddress": "{2}", + "AddressPrefix" : "{1}", + "IpSubnets" : [ + {{ + "IpAddressPrefix": "{1}" + }} + ] + }} + ] , + "IsolateSwitch": true, + "Flags" : 265 + }} + )"""; + + // Render the template + const auto network_settings = fmt::format(network_settings_template, + string_to_wstring(params.name), + string_to_wstring(params.subnet), + string_to_wstring(params.gateway)); + + HCN_NETWORK network{nullptr}; + auto result = + perform_hcn_operation(api.CreateNetwork, guid_from_string(params.guid), network_settings.c_str(), &network); + // We don't need the handle. + api.CloseNetwork(network); + return result; +} + +// --------------------------------------------------------- + +auto HCNWrapper::delete_network(const std::string& network_guid) -> OperationResult +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("HCNWrapper::delete_network(...) > network_guid: {} ", network_guid)); + return perform_hcn_operation(api.DeleteNetwork, guid_from_string(network_guid)); +} + +// --------------------------------------------------------- + +auto HCNWrapper::create_endpoint(const CreateEndpointParameters& params) -> OperationResult +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("HCNWrapper::create_endpoint(...) > params: {} ", params)); + constexpr auto endpoint_settings_template = LR"( + {{ + "SchemaVersion": {{ + "Major": 2, + "Minor": 16 + }}, + "HostComputeNetwork": "{0}", + "Policies": [ + {{ + "Type": "PortName" + }}, + {{ + "Type": "Firewall", + "Settings": {{ + "VmCreatorId": "{1}", + "PolicyFlags": 0 + }} + }} + ], + "IpConfigurations": [ + {{ + "IpAddress": "{2}" + }} + ] + }})"; + + auto network = open_network(api, params.network_guid); + + if (nullptr == network) + { + return {E_POINTER, L"Could not open the network!"}; + } + + // Render the template + const auto endpoint_settings = fmt::format(endpoint_settings_template, + string_to_wstring(params.network_guid), + string_to_wstring(params.vm_creator_id), + string_to_wstring(params.endpoint_ipvx_addr)); + HCN_ENDPOINT endpoint{nullptr}; + auto result = perform_hcn_operation(api.CreateEndpoint, + network.get(), + guid_from_string(params.endpoint_guid), + endpoint_settings.c_str(), + &endpoint); + api.CloseEndpoint(endpoint); + return result; +} + +// --------------------------------------------------------- + +auto HCNWrapper::delete_endpoint(const std::string& endpoint_guid) -> OperationResult +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid)); + return perform_hcn_operation(api.DeleteEndpoint, guid_from_string(endpoint_guid)); +} + +} // namespace multipass::hyperv::hcn diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h new file mode 100644 index 0000000000..d04301ce64 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h @@ -0,0 +1,92 @@ +/* + * 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_HYPERV_API_HCN_WRAPPER +#define MULTIPASS_HYPERV_API_HCN_WRAPPER + +#include "hyperv_hcn_api_table.h" +#include "hyperv_hcn_wrapper_interface.h" + +namespace multipass::hyperv::hcn +{ + +/** + * A high-level wrapper class that defines + * the common operations that Host Compute Network + * API provide. + */ +struct HCNWrapper : public HCNWrapperInterface +{ + + /** + * Construct a new HCNWrapper + * + * @param api_table The HCN API table object (optional) + * + * The wrapper will use the real HCN API by default. + */ + HCNWrapper(const HCNAPITable& api_table = {}); + HCNWrapper(const HCNWrapper&) = default; + HCNWrapper(HCNWrapper&&) = default; + + /** + * Create a new Host Compute Network + * + * @param [in] params Parameters for the new network + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult create_network(const CreateNetworkParameters& params) override; + + /** + * Delete an existing Host Compute Network + * + * @param [in] network_guid Target network's GUID + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult delete_network(const std::string& network_guid) override; + + /** + * Create a new Host Compute Network Endpoint + * + * @param [in] params Parameters for the new endpoint + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult create_endpoint(const CreateEndpointParameters& params) override; + + /** + * Delete an existing Host Compute Network Endpoint + * + * @param [in] params Target endpoint's GUID + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult delete_endpoint(const std::string& endpoint_guid) override; + +private: + const HCNAPITable api; +}; + +} // namespace multipass::hyperv::hcn + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h new file mode 100644 index 0000000000..658c147e0b --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h @@ -0,0 +1,84 @@ +/* + * 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_HYPERV_API_HCN_CREATE_ENDPOINT_PARAMETERS_H +#define MULTIPASS_HYPERV_API_HCN_CREATE_ENDPOINT_PARAMETERS_H + +#include +#include + +namespace multipass::hyperv::hcn +{ + +/** + * Parameters for creating a network endpoint. + */ +struct CreateEndpointParameters +{ + /** + * The GUID of the network that will own the endpoint. + * + * The network must already exist. + */ + std::string network_guid; + + /** + * GUID for the new endpoint. + * + * Must be unique. + */ + std::string endpoint_guid; + + /** + * An unique identifier that distinguishes the creator of + * this endpoint. This allows tracing the created endpoints + * back to the its' creator for diagnostics. + */ + std::string vm_creator_id; + + /** + * The IPv[4-6] address to assign to the endpoint. + */ + std::string endpoint_ipvx_addr; +}; + +} // namespace multipass::hyperv::hcn + +/** + * Formatter type specialization for CreateEndpointParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcn::CreateEndpointParameters& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Endpoint GUID: ({}) | Network GUID: ({}) | Endpoint IPvX Addr.: ({}) | VM Creator ID: ({})", + params.endpoint_guid, + params.network_guid, + params.endpoint_ipvx_addr, + params.vm_creator_id); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h new file mode 100644 index 0000000000..d863813667 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h @@ -0,0 +1,79 @@ +/* + * 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_HYPERV_API_HCN_CREATE_NETWORK_PARAMETERS_H +#define MULTIPASS_HYPERV_API_HCN_CREATE_NETWORK_PARAMETERS_H + +#include +#include + +namespace multipass::hyperv::hcn +{ + +/** + * Parameters for creating a new Host Compute Network + */ +struct CreateNetworkParameters +{ + /** + * Name for the network + */ + std::string name; + + /** + * RFC4122 unique identifier for the network. + */ + std::string guid; + + /** + * Subnet CIDR that defines the address space of + * the network. + */ + std::string subnet; + + /** + * The default gateway address for the network. + */ + std::string gateway; +}; + +} // namespace multipass::hyperv::hcn + +/** + * Formatter type specialization for CreateNetworkParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcn::CreateNetworkParameters& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Network Name: ({}) | Network GUID: ({}) | Subnet CIDR: ({}) | Gateway Addr.: ({}) ", + params.name, + params.guid, + params.subnet, + params.gateway); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h new file mode 100644 index 0000000000..e90e3b272b --- /dev/null +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h @@ -0,0 +1,42 @@ +/* + * 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_HYPERV_API_HCN_WRAPPER_INTERFACE_H +#define MULTIPASS_HYPERV_API_HCN_WRAPPER_INTERFACE_H + +#include "../hyperv_api_operation_result.h" + +#include +#include + +namespace multipass::hyperv::hcn +{ + +/** + * Abstract interface for Hyper-V Host Compute Networking wrapper. + */ +struct HCNWrapperInterface +{ + virtual OperationResult create_network(const struct CreateNetworkParameters& params) = 0; + virtual OperationResult delete_network(const std::string& network_guid) = 0; + virtual OperationResult create_endpoint(const struct CreateEndpointParameters& params) = 0; + virtual OperationResult delete_endpoint(const std::string& endpoint_guid) = 0; + virtual ~HCNWrapperInterface() = default; +}; +} // namespace multipass::hyperv::hcn + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h new file mode 100644 index 0000000000..9f316c689f --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h @@ -0,0 +1,75 @@ +/* + * 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_HYPERV_API_HCS_ADD_ENDPOINT_PARAMETERS_H +#define MULTIPASS_HYPERV_API_HCS_ADD_ENDPOINT_PARAMETERS_H + +#include +#include + +namespace multipass::hyperv::hcs +{ + +/** + * Parameters for adding a network endpoint to + * a Host Compute System.. + */ +struct AddEndpointParameters +{ + /** + * Name of the target host compute system + */ + std::string target_compute_system_name; + + /** + * GUID of the endpoint to add. + * + * Must be already existing. + */ + std::string endpoint_guid; + + /** + * MAC address to assign to the NIC + */ + std::string nic_mac_address; +}; + +} // namespace multipass::hyperv::hcs + +/** + * Formatter type specialization for CreateComputeSystemParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcs::AddEndpointParameters& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Host Compute System Name: ({}) | Endpoint GUID: ({}) | NIC MAC Address: ({}) ", + params.target_compute_system_name, + params.endpoint_guid, + params.nic_mac_address); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h new file mode 100644 index 0000000000..8d266a6a84 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -0,0 +1,114 @@ +/* + * 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_HYPERV_API_HCS_API_TABLE +#define MULTIPASS_HYPERV_API_HCS_API_TABLE + +// clang-format off + // (xmkg): clang-format is messing with the include order. + #include + #include +// clang-format on + +#include + +#include + +namespace multipass::hyperv::hcs +{ + +/** + * API function table for host compute system + * @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/apioverview + */ +struct HCSAPITable +{ + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcscreateoperation + std::function CreateOperation = &HcsCreateOperation; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcswaitforoperationresult + std::function WaitForOperationResult = &HcsWaitForOperationResult; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcscloseoperation + std::function CloseOperation = &HcsCloseOperation; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcscreatecomputesystem + std::function CreateComputeSystem = &HcsCreateComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsopencomputesystem + std::function OpenComputeSystem = &HcsOpenComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsstartcomputesystem + std::function StartComputeSystem = &HcsStartComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsshutdowncomputesystem + std::function ShutDownComputeSystem = &HcsShutDownComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsterminatecomputesystem + std::function TerminateComputeSystem = &HcsTerminateComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsclosecomputesystem + std::function CloseComputeSystem = &HcsCloseComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcspausecomputesystem + std::function PauseComputeSystem = &HcsPauseComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsresumecomputesystem + std::function ResumeComputeSystem = &HcsResumeComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsmodifycomputesystem + std::function ModifyComputeSystem = &HcsModifyComputeSystem; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsgetcomputesystemproperties + std::function GetComputeSystemProperties = &HcsGetComputeSystemProperties; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsgrantvmaccess + std::function GrantVmAccess = &HcsGrantVmAccess; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsrevokevmaccess + std::function RevokeVmAccess = &HcsRevokeVmAccess; + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems + std::function EnumerateComputeSystems = &HcsEnumerateComputeSystems; +}; + +} // namespace multipass::hyperv::hcs + +/** + * Formatter type specialization for HCNAPITable + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcs::HCSAPITable& api, FormatContext& ctx) const + { + return format_to(ctx.out(), + "CreateOperation: ({}) | WaitForOperationResult: ({}) | CreateComputeSystem: ({}) | " + "OpenComputeSystem: ({}) | " + "StartComputeSystem: ({}) | ShutDownComputeSystem: ({}) | PauseComputeSystem: ({}) | " + "ResumeComputeSystem: ({}) | ModifyComputeSystem: ({}) | GetComputeSystemProperties: ({}) | " + "GrantVmAccess: ({}) | RevokeVmAccess: ({}) | EnumerateComputeSystems: ({})", + fmt::ptr(api.CreateOperation.target()), + fmt::ptr(api.WaitForOperationResult.target()), + fmt::ptr(api.CreateComputeSystem.target()), + fmt::ptr(api.OpenComputeSystem.target()), + fmt::ptr(api.StartComputeSystem.target()), + fmt::ptr(api.ShutDownComputeSystem.target()), + fmt::ptr(api.PauseComputeSystem.target()), + fmt::ptr(api.ResumeComputeSystem.target()), + fmt::ptr(api.ModifyComputeSystem.target()), + fmt::ptr(api.GetComputeSystemProperties.target()), + fmt::ptr(api.GrantVmAccess.target()), + fmt::ptr(api.RevokeVmAccess.target()), + fmt::ptr(api.EnumerateComputeSystems.target()) + + ); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp new file mode 100644 index 0000000000..ba9d9b32c1 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -0,0 +1,479 @@ +/* + * 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 "hyperv_hcs_api_wrapper.h" +#include "../hyperv_api_common.h" +#include "hyperv_api/hyperv_api_operation_result.h" +#include "hyperv_hcs_add_endpoint_params.h" +#include "hyperv_hcs_api_table.h" +#include "hyperv_hcs_create_compute_system_params.h" +#include "multipass/format.h" + +#include + +#include +#include +#include + +#include +#include + +#include +#include + +namespace multipass::hyperv::hcs +{ + +/** + * Category for the log messages. + */ +static constexpr const char* kLogCategory = "HyperV-HCS-Wrapper"; + +using UniqueHcsSystem = std::unique_ptr, decltype(HCSAPITable::CloseComputeSystem)>; +using UniqueHcsOperation = std::unique_ptr, decltype(HCSAPITable::CloseOperation)>; +using UniqueHlocalString = wil::unique_hlocal_string; + +// HCS_OPERATION operation{nullptr}; + +// --------------------------------------------------------- + +/** + * Create a new HCS operation. + * + * @param api The HCS API table + * + * @return UniqueHcsOperation The new operation + */ +static auto create_operation(const HCSAPITable& api) -> UniqueHcsOperation +{ + logging::log(logging::Level::trace, kLogCategory, fmt::format("create_operation(...)")); + return UniqueHcsOperation{api.CreateOperation(nullptr, nullptr), api.CloseOperation}; +} + +// --------------------------------------------------------- + +/** + * Wait until given operation completes, or the timeout has reached. + * + * @param api The HCS API table + * @param op + * @param timeout + * @return auto + */ +static auto wait_for_operation_result(const HCSAPITable& api, + UniqueHcsOperation op, + std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) +{ + logging::log( + logging::Level::trace, + kLogCategory, + fmt::format("wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), timeout.count())); + wil::unique_hlocal_string result_msg{}; + const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg); + + if (result_msg.is_valid()) + { + // TODO: Convert from wstring to ascii and log this + // logging::log(logging::Level::trace, + // kLogCategory, + // fmt::format("wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), + // result, result_msg)); + return OperationResult{result, result_msg.get()}; + } + return OperationResult{result, L""}; +} + +// --------------------------------------------------------- + +/** + * Open an existing Host Compute System + * + * @param api The HCS API table + * @param name Target Host Compute System's name + * + * @return auto UniqueHcsSystem non-nullptr on success. + */ +static auto open_host_compute_system(const HCSAPITable& api, const std::string& name) +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("open_host_compute_system(...) > name: ({}), ", name)); + HCS_SYSTEM system{nullptr}; + const auto name_w = string_to_wstring(name); + constexpr auto kRequestedAccessLevel = GENERIC_ALL; + const auto result = api.OpenComputeSystem(name_w.c_str(), kRequestedAccessLevel, &system); + + if (FAILED(result)) + { + logging::log( + logging::Level::error, + kLogCategory, + fmt::format("open_host_compute_system(...) > failed to open ({}), result code: ({})", name, result)); + } + return UniqueHcsSystem{system, api.CloseComputeSystem}; +} + +// --------------------------------------------------------- + +/** + * Perform a Host Compute System API operation. + * + * Host Compute System operation functions have a common + * signature, where `system` and `operation` are always + * the first two arguments. This functions is a common + * shorthand for invoking any of those. + * + * @param [in] api The API function table + * @param [in] fn The API function pointer + * @param [in] target_hcs_system_name HCS system to operate on + * @param [in] args The arguments to the function + * + * @return HCNOperationResult Result of the performed operation + */ +template +static OperationResult perform_hcs_operation(const HCSAPITable& api, + const FnType& fn, + const std::string& target_hcs_system_name, + Args&&... args) +{ + // Ensure that function to call is set. + if (nullptr == fn) + { + assert(0); + // E_POINTER means "invalid pointer", seems to be appropriate. + return {E_POINTER, L"Operation function is unbound!"}; + } + + auto system = open_host_compute_system(api, target_hcs_system_name); + + if (nullptr == system) + { + return OperationResult{E_POINTER, L"Failure opening the target host compute system!"}; + } + + auto operation = create_operation(api); + const auto result = fn(system.get(), operation.get(), std::forward(args)...); + + if (FAILED(result)) + { + return OperationResult{result, L"HCN operation failed."}; + } + + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("perform_hcs_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result))); + + return wait_for_operation_result(api, std::move(operation), std::chrono::seconds{240}); +} + +// --------------------------------------------------------- + +HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} +{ + logging::log(logging::Level::trace, kLogCategory, fmt::format("HCSWrapper::HCSWrapper(...) > api_table: {}", api)); +} + +struct vm_settings +{ +}; + +// --------------------------------------------------------- + +OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) +{ + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("HCSWrapper::create_compute_system(...) > params: {} ", params)); + + constexpr auto scsi_device_template = LR"( + "{0}": {{ + "Attachments": {{ + "0": {{ + "Type": "{1}", + "Path": "{2}", + "ReadOnly": {3} + }} + }} + }}, + )"; + + std::wstring scsi_devices = {}; + + if (!params.cloudinit_iso_path.empty()) + { + auto scsi_device = fmt::format(scsi_device_template, + L"cloud-init iso file", + L"Iso", + string_to_wstring(params.cloudinit_iso_path), + true); + scsi_devices += scsi_device; + } + + if (!params.vhdx_path.empty()) + { + auto scsi_device = fmt::format(scsi_device_template, + L"Primary disk", + L"VirtualDisk", + string_to_wstring(params.vhdx_path), + false); + scsi_devices += scsi_device; + } + + // Ideally, we should codegen from the schema + // and use that. + // https://raw.githubusercontent.com/MicrosoftDocs/Virtualization-Documentation/refs/heads/main/hyperv-samples/hcs-samples/JSON_files/HCS_Schema%5BWindows_10_SDK_version_1809%5D.json + constexpr auto vm_settings_template = LR"( + {{ + "SchemaVersion": {{ + "Major": 2, + "Minor": 1 + }}, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": {{ + "Chipset": {{ + "Uefi": {{ + "BootThis": {{ + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }}, + "Console": "ComPort1" + }} + }}, + "ComputeTopology": {{ + "Memory": {{ + "Backing": "Virtual", + "SizeInMB": {1} + }}, + "Processor": {{ + "Count": {0} + }} + }}, + "Devices": {{ + "ComPorts": {{ + "0": {{ + "NamedPipe": "\\\\.\\pipe\\{2}" + }} + }}, + "Scsi": {{ + {3} + }} + }} + }} + }})"; + + // Render the template + const auto vm_settings = fmt::format(vm_settings_template, + params.processor_count, + params.memory_size_mb, + string_to_wstring(params.name), + scsi_devices); + HCS_SYSTEM system{nullptr}; + + auto operation = create_operation(api); + + const auto name_w = string_to_wstring(params.name); + const auto result = api.CreateComputeSystem(name_w.c_str(), vm_settings.c_str(), operation.get(), nullptr, &system); + + // Auto-release the system handle + UniqueHcsSystem system_u{system, api.CloseComputeSystem}; + (void)system_u; + + if (FAILED(result)) + { + return OperationResult{result, L"HcsCreateComputeSystem failed."}; + } + + return wait_for_operation_result(api, std::move(operation), std::chrono::seconds{240}); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) +{ + return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) +{ + return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) +{ + return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) +{ + static constexpr wchar_t c_pauseOption[] = LR"( + { + "SuspensionLevel": "Suspend", + "HostedNotification": { + "Reason": "Save" + } + })"; + return perform_hcs_operation(api, api.PauseComputeSystem, compute_system_name, c_pauseOption); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) +{ + return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) +{ + constexpr auto add_endpoint_settings_template = LR"( + {{ + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", + "RequestType": "Add", + "Settings": {{ + "EndpointId": "{0}", + "MacAddress": "{1}", + "InstanceId": "{0}" + }} + }})"; + + const auto add_endpoint_settings = fmt::format(add_endpoint_settings_template, + string_to_wstring(params.endpoint_guid), + string_to_wstring(params.nic_mac_address)); + + return perform_hcs_operation(api, + api.ModifyComputeSystem, + params.target_compute_system_name, + add_endpoint_settings.c_str(), + nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) +{ + constexpr auto remove_endpoint_settings_template = LR"( + {{ + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", + "RequestType": "Remove" + }})"; + + const auto remove_endpoint_settings = + fmt::format(remove_endpoint_settings_template, string_to_wstring(endpoint_guid)); + + return perform_hcs_operation(api, + api.ModifyComputeSystem, + compute_system_name, + remove_endpoint_settings.c_str(), + nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) +{ + constexpr auto resize_memory_settings_template = LR"( + {{ + "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", + "RequestType": "Update", + "Settings": {0} + }})"; + + const auto resize_memory_settings = fmt::format(resize_memory_settings_template, new_size_mib); + + return perform_hcs_operation(api, + api.ModifyComputeSystem, + compute_system_name, + resize_memory_settings.c_str(), + nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) +{ + + // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType + static constexpr wchar_t c_VmQuery[] = LR"( + { + "PropertyTypes":[] + })"; + + return perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, c_VmQuery); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) +{ + const auto path_as_wstring = file_path.wstring(); + const auto csname_as_wstring = string_to_wstring(compute_system_name); + + const auto result = api.GrantVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); + + return {result, FAILED(result) ? L"GrantVmAccess failed!" : L""}; +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) +{ + const auto path_as_wstring = file_path.wstring(); + const auto csname_as_wstring = string_to_wstring(compute_system_name); + + const auto result = api.RevokeVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); + + return {result, FAILED(result) ? L"RevokeVmAccess failed!" : L""}; +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) +{ + + auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); + if (!result) + { + return {result.code, L"Unknown"}; + } + + QString qstr{QString::fromStdWString(result.status_msg)}; + auto doc = QJsonDocument::fromJson(qstr.toUtf8()); + auto obj = doc.object(); + if (obj.contains("State")) + { + auto state = obj["State"]; + auto state_str = state.toString(); + return {result.code, state_str.toStdWString()}; + } + + return {result.code, L"Unknown"}; +} + +} // namespace multipass::hyperv::hcs \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h new file mode 100644 index 0000000000..cc2671c2cb --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h @@ -0,0 +1,219 @@ +/* + * 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_HYPERV_API_HCS_WRAPPER +#define MULTIPASS_HYPERV_API_HCS_WRAPPER + +#include "hyperv_hcs_api_table.h" +#include "hyperv_hcs_create_compute_system_params.h" +#include "hyperv_hcs_wrapper_interface.h" + +namespace multipass::hyperv::hcs +{ + +/** + * A high-level wrapper class that defines + * the common operations that Host Compute System + * API provide. + */ +struct HCSWrapper : public HCSWrapperInterface +{ + + /** + * Construct a new HCNWrapper + * + * @param api_table The HCN API table object (optional) + * + * The wrapper will use the real HCN API by default. + */ + HCSWrapper(const HCSAPITable& api_table = {}); + HCSWrapper(const HCSWrapper&) = default; + HCSWrapper(HCSWrapper&&) = default; + + // --------------------------------------------------------- + + /** + * Create a new Host Compute System + * + * @param [in] params Parameters for the new compute system + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult create_compute_system(const CreateComputeSystemParameters& params) override; + + // --------------------------------------------------------- + + /** + * Start a compute system. + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult start_compute_system(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * Gracefully shutdown the compute system + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult shutdown_compute_system(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * Forcefully shutdown the compute system + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult terminate_compute_system(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * Pause the execution of a running compute system + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult pause_compute_system(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * Resume the execution of a previously paused compute system + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult resume_compute_system(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult get_compute_system_properties(const std::string& compute_system_name) override; + + // --------------------------------------------------------- + + /** + * Grant a compute system access to a file path. + * + * @param [in] compute_system_name Target compute system's name + * @param [in] file_path File path to grant access to + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult grant_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) override; + + // --------------------------------------------------------- + + /** + * Revoke a compute system's access to a file path. + * + * @param [in] compute_system_name Target compute system's name + * @param [in] file_path File path to revoke access to + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult revoke_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) override; + + // --------------------------------------------------------- + + /** + * Add a network endpoint to the host compute system. + * + * A new network interface card will be automatically created for + * the endpoint. The network interface card's name will be the + * endpoint's GUID for convenience. + * + * @param [in] params Endpoint parameters + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult add_endpoint(const AddEndpointParameters& params) override; + + // --------------------------------------------------------- + + /** + * Remove a network endpoint from the host compute system. + * + * @param [in] name Target compute system's name + * @param [in] endpoint_guid GUID of the endpoint to remove + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) override; + + // --------------------------------------------------------- + + /** + * Resize the amount of memory the compute system has. + * + * @param compute_system_name Target compute system name + * @param new_size_mib New memory size, in megabytes + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) override; + + // --------------------------------------------------------- + + /** + * Retrieve the current state of the compute system. + * + * @param [in] compute_system_name Target compute system's name + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + OperationResult get_compute_system_state(const std::string& compute_system_name) override; + +private: + const HCSAPITable api; +}; + +} // namespace multipass::hyperv::hcs + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h new file mode 100644 index 0000000000..350f82d943 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h @@ -0,0 +1,68 @@ +/* + * 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_HYPERV_API_HCS_CREATE_COMPUTE_SYSTEM_STATE_H +#define MULTIPASS_HYPERV_API_HCS_CREATE_COMPUTE_SYSTEM_STATE_H + +#include +#include +#include +#include +#include + +namespace multipass::hyperv::hcs +{ + +/** + * Enum values representing a compute system's possible state + * + * @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#State + */ +enum class ComputeSystemState : std::uint8_t +{ + created, + running, + paused, + stopped, + saved_as_template, + unknown +}; + +inline ComputeSystemState compute_system_state_from_string(std::string str) +{ + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); + // std::unordered_map + static const std::unordered_map translation_map{ + {"created", ComputeSystemState::created}, + {"running", ComputeSystemState::running}, + {"paused", ComputeSystemState::paused}, + {"stopped", ComputeSystemState::stopped}, + {"savedastemplate", ComputeSystemState::saved_as_template}, + {"unknown", ComputeSystemState::unknown}}; + + const auto itr = translation_map.find(str); + if (translation_map.end() == itr) + { + throw std::domain_error{""}; + } + + return itr->second; +} + +} // namespace multipass::hyperv::hcs + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h new file mode 100644 index 0000000000..ea025cd91c --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h @@ -0,0 +1,85 @@ +/* + * 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_HYPERV_API_HCS_CREATE_COMPUTE_SYSTEM_PARAMETERS_H +#define MULTIPASS_HYPERV_API_HCS_CREATE_COMPUTE_SYSTEM_PARAMETERS_H + +#include +#include + +namespace multipass::hyperv::hcs +{ + +/** + * Parameters for creating a network endpoint. + */ +struct CreateComputeSystemParameters +{ + /** + * Unique name for the compute system + */ + std::string name; + + /** + * Memory size, in megabytes + */ + std::uint32_t memory_size_mb; + + /** + * vCPU count + */ + std::uint32_t processor_count; + + /** + * Path to the cloud-init ISO file + */ + std::string cloudinit_iso_path; + + /** + * Path to the Primary (boot) VHDX file + */ + std::string vhdx_path; +}; + +} // namespace multipass::hyperv::hcs + +/** + * Formatter type specialization for CreateComputeSystemParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::hcs::CreateComputeSystemParameters& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Compute System name: ({}) | vCPU count: ({}) | Memory size: ({} MiB) | cloud-init ISO path: " + "({}) | VHDX path: ({})", + params.name, + params.processor_count, + params.memory_size_mb, + params.cloudinit_iso_path, + params.vhdx_path); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h new file mode 100644 index 0000000000..11bf857a47 --- /dev/null +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h @@ -0,0 +1,57 @@ +/* + * 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_HYPERV_API_HCS_WRAPPER_INTERFACE_H +#define MULTIPASS_HYPERV_API_HCS_WRAPPER_INTERFACE_H + +#include "../hyperv_api_operation_result.h" + +#include "hyperv_hcs_add_endpoint_params.h" +#include "hyperv_hcs_create_compute_system_params.h" + +#include +#include + +namespace multipass::hyperv::hcs +{ + +/** + * Abstract interface for Hyper-V Host Compute System wrapper. + */ +struct HCSWrapperInterface +{ + virtual OperationResult create_compute_system(const CreateComputeSystemParameters& params) = 0; + virtual OperationResult start_compute_system(const std::string& compute_system_name) = 0; + virtual OperationResult shutdown_compute_system(const std::string& compute_system_name) = 0; + virtual OperationResult pause_compute_system(const std::string& compute_system_name) = 0; + virtual OperationResult resume_compute_system(const std::string& compute_system_name) = 0; + virtual OperationResult terminate_compute_system(const std::string& compute_system_name) = 0; + virtual OperationResult get_compute_system_properties(const std::string& compute_system_name) = 0; + virtual OperationResult grant_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) = 0; + virtual OperationResult revoke_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) = 0; + virtual OperationResult add_endpoint(const AddEndpointParameters& params) = 0; + virtual OperationResult remove_endpoint(const std::string& compute_system_name, + const std::string& endpoint_guid) = 0; + virtual OperationResult resize_memory(const std::string& compute_system_name, const std::uint32_t new_size_mib) = 0; + virtual OperationResult get_compute_system_state(const std::string& compute_system_name) = 0; + virtual ~HCSWrapperInterface() = default; +}; +} // namespace multipass::hyperv::hcs + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp new file mode 100644 index 0000000000..19e792ba26 --- /dev/null +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -0,0 +1,148 @@ +/* + * 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 "hyperv_api_common.h" + +#include +#include + +#include +#include +#include + +namespace multipass::hyperv +{ + +auto guid_from_string(const std::wstring& guid_wstr) -> ::GUID +{ + + // Since we're using the windows.h in LEAN_AND_MEAN mode, COM-provided + // GUID parsing functions such as CLSIDFromString are not available. + + auto iterator = guid_wstr.begin(); + ::GUID result = {}; + + constexpr auto kGUIDLength = 36; + constexpr auto kGUIDLengthWithBraces = kGUIDLength + 2; + constexpr auto kGUIDFormatString = L"%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx"; + constexpr auto kGUIDFieldCount = 11; + + if (guid_wstr.length() == kGUIDLengthWithBraces) + { + + if (guid_wstr[0] != L'{' || guid_wstr[guid_wstr.length() - 1] != L'}') + { + throw std::invalid_argument{"GUID string with brances either does not start or end with a brace."}; + } + + // Skip the initial brace character + std::advance(iterator, 1); + } + else if (guid_wstr.length() != kGUIDLength) + { + throw std::domain_error(fmt::format("Invalid GUID string {}.", guid_wstr.length())); + } + + const auto match_count = swscanf_s(&(*iterator), + kGUIDFormatString, + &result.Data1, + &result.Data2, + &result.Data3, + &result.Data4[0], + &result.Data4[1], + &result.Data4[2], + &result.Data4[3], + &result.Data4[4], + &result.Data4[5], + &result.Data4[6], + &result.Data4[7]); + + // Ensure that the swscanf parsed the exact amount of fields + if (!(match_count == kGUIDFieldCount)) + { + throw std::runtime_error("Failed to parse GUID string"); + } + + return result; +} + +// --------------------------------------------------------- + +auto string_to_wstring(const std::string& str) -> std::wstring +{ + // We could've gotten away with `return std::wstring{str.begin(), str.end()}` + // and it'd work for 99 pct of the scenarios we'd see, but... let's do the correct thing. + + using unique_locale = wil::unique_any<_locale_t, decltype(&_free_locale), _free_locale>; + // Avoid recreating the locale. + static unique_locale locale{_create_locale(LC_ALL, "C")}; + + // Call the function with nullptr to learn how much space we need to + // store the result. + std::size_t required_size{0}; + if (!(0 == _mbstowcs_s_l(&required_size, nullptr, 0, str.c_str(), str.length(), locale.get()))) + { + throw std::invalid_argument{"Failed to convert multi-byte string to wide-character string."}; + } + + // String to store the converted output. + std::wstring result{}; + + // The required_size will account for the NUL terminator. + // Hence, the actual amount of storage needed for resize is 1 + // characters less. + result.resize(required_size - 1, L'\0'); + + // Perform the conversion. + std::size_t converted_char_cnt{0}; + if (!(0 == _mbstowcs_s_l(&converted_char_cnt, + // data() returns a non-const pointer since C++17, and it is guaranteed + // to be contiguous memory. + result.data(), + // We're doing a size + 1 here because _mbstowcs_s_l will put a NUL terminator + // at the end. The string's internal buffer already account for it, and overwriting + // it with another NUL terminator is well-defined: + + // Quoting cppreference (https://en.cppreference.com/w/cpp/string/basic_string/data) + // ... + // 2) Modifying the past-the-end null terminator stored at data() + size() to any value + // other than CharT() has undefined behavior. + // + // CharT() == '\0'. + result.size() + 1, + // The multi-byte string. + str.c_str(), + // Convert at most the count of multi-byte string characters. + str.length(), + locale.get()))) + { + throw std::invalid_argument{"Failed to convert multi-byte string to wide-character string."}; + } + assert(converted_char_cnt == str.length() + 1); + assert(converted_char_cnt == required_size); + return result; +} + +// --------------------------------------------------------- + +auto guid_from_string(const std::string& guid_str) -> GUID +{ + // Just use the wide string overload. + return guid_from_string(string_to_wstring(guid_str)); +} + +} // namespace multipass::hyperv \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.h b/src/platform/backends/hyperv_api/hyperv_api_common.h new file mode 100644 index 0000000000..f665a1cb2c --- /dev/null +++ b/src/platform/backends/hyperv_api/hyperv_api_common.h @@ -0,0 +1,61 @@ +/* + * 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_HYPERV_API_COMMON_H +#define MULTIPASS_HYPERV_API_COMMON_H + +#include +#include + +namespace multipass::hyperv +{ + +/** + * Parse given GUID string into a GUID struct. + * + * @param guid_wstr GUID in string form, either 36 characters + * (without braces) or 38 characters (with braces.) + * + * @return GUID The parsed GUID + */ +auto guid_from_string(const std::wstring& guid_wstr) -> GUID; + +// --------------------------------------------------------- + +/** + * Convert a multi-byte string to a wide-character string. + * + * @param str Multi-byte string + * @return Wide-character equivalent of the given multi-byte string. + */ +auto string_to_wstring(const std::string& str) -> std::wstring; + +// --------------------------------------------------------- + +/** + * Parse given GUID string into a GUID struct. + * + * @param guid_str GUID in string form, either 36 characters + * (without braces) or 38 characters (with braces.) + * + * @return GUID The parsed GUID + */ +auto guid_from_string(const std::string& guid_str) -> GUID; + +} // namespace multipass::hyperv + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h new file mode 100644 index 0000000000..4941173aff --- /dev/null +++ b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h @@ -0,0 +1,85 @@ +/* + * 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_HYPERV_API_OPERATION_RESULT_H +#define MULTIPASS_HYPERV_API_OPERATION_RESULT_H + +#include +#include + +namespace multipass::hyperv +{ + +/** + * A simple HRESULT wrapper which is boolable for + * convenience. + */ +struct ResultCode +{ + + ResultCode(HRESULT r) noexcept : result(r) + { + } + ResultCode& operator=(HRESULT r) noexcept + { + result = r; + return *this; + } + + [[nodiscard]] operator bool() const noexcept + { + return !FAILED(result); + } + + [[nodiscard]] explicit operator HRESULT() const noexcept + { + return result; + } + +private: + HRESULT result{}; +}; + +/** + * An object that describes the result of an HCN operation + * performed through HCNWrapper. + */ +struct OperationResult +{ + + /** + * Status code of the operation. Evaluates to + * true and greater or equal to 0 on success. + */ + const ResultCode code; + + /** + * A message that describes the result of the operation. + * It might contain an error message describing the error + * when the operation fails, or details regarding the status + * of a successful operation. + */ + const std::wstring status_msg; + + operator bool() const noexcept + { + return static_cast(code); + } +}; +} // namespace multipass::hyperv + +#endif diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt new file mode 100644 index 0000000000..d98415fa0e --- /dev/null +++ b/tests/hyperv_api/CMakeLists.txt @@ -0,0 +1,18 @@ + +if(WIN32) + target_sources(multipass_tests + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcn_api.cpp + ) + + target_sources(multipass_tests + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcn_api.cpp + ) + + target_sources(multipass_tests + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcs_api.cpp + ) +endif() + diff --git a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp new file mode 100644 index 0000000000..e7f6465870 --- /dev/null +++ b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp @@ -0,0 +1,116 @@ +/* + * 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 "tests/common.h" +#include "tests/mock_logger.h" + +#include +#include +#include + + +namespace mpt = multipass::test; + +namespace multipass::test +{ + +using uut_t = hyperv::hcn::HCNWrapper; + +struct HyperVHCNAPI : public ::testing::Test +{ + + // mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + + void SetUp() override + { + // logger_scope.mock_logger. + } + + void TearDown() override + { + } +}; + +TEST_F(HyperVHCNAPI, create_delete_network) +{ + uut_t uut; + hyperv::hcn::CreateNetworkParameters params{}; + params.name = "multipass-hyperv-api-hcn-create-delete-test"; + params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; + params.subnet = "172.50.224.0/20"; + params.gateway = "172.50.224.1"; + + uut.delete_network(params.guid); + + { + const auto& [success, error_msg] = uut.create_network(params); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } + + { + const auto& [success, error_msg] = uut.delete_network(params.guid); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } +} + +TEST_F(HyperVHCNAPI, create_delete_endpoint) +{ + uut_t uut; + hyperv::hcn::CreateNetworkParameters network_params{}; + network_params.name = "multipass-hyperv-api-hcn-create-delete-test"; + network_params.guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; + network_params.subnet = "172.50.224.0/20"; + network_params.gateway = "172.50.224.1"; + + hyperv::hcn::CreateEndpointParameters endpoint_params{}; + + endpoint_params.network_guid = network_params.guid; + endpoint_params.endpoint_guid = "b70c479d-f808-4053-aafa-705bc15b6d70"; + endpoint_params.vm_creator_id = R"(456fe1ac-1e46-49ec-bb9f-5e44cc2de23b)"; + endpoint_params.endpoint_ipvx_addr = "172.50.224.2"; + + uut.delete_network(network_params.guid); + + { + const auto& [success, error_msg] = uut.create_network(network_params); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } + + { + const auto& [success, error_msg] = uut.create_endpoint(endpoint_params); + std::wprintf(L"%s\n", error_msg.c_str()); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } + + { + const auto& [success, error_msg] = uut.delete_endpoint(endpoint_params.endpoint_guid); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } + + { + const auto& [success, error_msg] = uut.delete_network(network_params.guid); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } +} + +} // namespace multipass::test \ No newline at end of file diff --git a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp new file mode 100644 index 0000000000..613e666244 --- /dev/null +++ b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp @@ -0,0 +1,103 @@ +/* + * 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 "hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h" +#include "tests/common.h" +#include "tests/mock_logger.h" + +#include + +namespace mpt = multipass::test; + +namespace multipass::test +{ + +using uut_t = hyperv::hcs::HCSWrapper; + +struct HyperVHCSAPI : public ::testing::Test +{ + + // mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + + void SetUp() override + { + // logger_scope.mock_logger. + } + + void TearDown() override + { + } +}; + +TEST_F(HyperVHCSAPI, create_delete_compute_system) +{ + + uut_t uut{}; + + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test"; + params.memory_size_mb = 1024; + params.processor_count = 1; + params.cloudinit_iso_path = ""; + params.vhdx_path = ""; + + const auto c_result = uut.create_compute_system(params); + + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + const auto d_result = uut.terminate_compute_system(params.name); + ASSERT_TRUE(d_result); + std::wprintf(L"%s\n", d_result.status_msg.c_str()); + ASSERT_FALSE(d_result.status_msg.empty()); +} + +TEST_F(HyperVHCSAPI, enumerate_properties) +{ + + uut_t uut{}; + + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test"; + params.memory_size_mb = 1024; + params.processor_count = 1; + params.cloudinit_iso_path = ""; + params.vhdx_path = ""; + + const auto c_result = uut.create_compute_system(params); + uut.start_compute_system(params.name); + + + + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + const auto p_result = uut.get_compute_system_properties(params.name); + EXPECT_TRUE(p_result); + std::wprintf(L"%s\n", p_result.status_msg.c_str()); + + // const auto e_result = uut.enumerate_all_compute_systems(); + // EXPECT_TRUE(e_result); + // std::wprintf(L"%s\n", e_result.status_msg.c_str()); + + const auto d_result = uut.terminate_compute_system(params.name); + ASSERT_TRUE(d_result); + std::wprintf(L"%s\n", d_result.status_msg.c_str()); + ASSERT_FALSE(d_result.status_msg.empty()); +} + +} // namespace multipass::test \ No newline at end of file diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp new file mode 100644 index 0000000000..aa2f4a1c40 --- /dev/null +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -0,0 +1,128 @@ +/* + * 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 "hyperv_api/hcn/hyperv_hcn_api_table.h" +#include "tests/common.h" +#include "tests/mock_logger.h" + +#include "gmock/gmock.h" +#include +#include +#include +#include + +namespace mpt = multipass::test; + +using testing::_; +using testing::DoAll; +using testing::Invoke; +using testing::Return; + +#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) + +namespace multipass::test +{ + +using uut_t = hyperv::hcn::HCNWrapper; + +struct HyperVHCNAPI_UnitTests : public ::testing::Test +{ + + mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + + void SetUp() override + { + // Each of the unit tests are expected to have their own mock functions + // and override the mock_api_table with them. Hence, the stub mocks should + // not be called at all. + // If any of them do get called, then: + // + // a-) You have forgotten to mock something + // b-) The implementation is using a function that you didn't expect + // + // Either way, you should have a look. + + EXPECT_NO_CALL(stub_mock_create_network); + EXPECT_NO_CALL(stub_mock_open_network); + EXPECT_NO_CALL(stub_mock_delete_network); + EXPECT_NO_CALL(stub_mock_close_network); + EXPECT_NO_CALL(stub_mock_create_endpoint); + EXPECT_NO_CALL(stub_mock_open_endpoint); + EXPECT_NO_CALL(stub_mock_delete_endpoint); + EXPECT_NO_CALL(stub_mock_close_endpoint); + } + + void TearDown() override + { + } + + // Set of placeholder mocks in order to catch *unexpected* calls. + ::testing::MockFunction stub_mock_create_network; + ::testing::MockFunction stub_mock_open_network; + ::testing::MockFunction stub_mock_delete_network; + ::testing::MockFunction stub_mock_close_network; + ::testing::MockFunction stub_mock_create_endpoint; + ::testing::MockFunction stub_mock_open_endpoint; + ::testing::MockFunction stub_mock_delete_endpoint; + ::testing::MockFunction stub_mock_close_endpoint; + + hyperv::hcn::HCNAPITable mock_api_table{stub_mock_create_network.AsStdFunction(), + stub_mock_open_network.AsStdFunction(), + stub_mock_delete_network.AsStdFunction(), + stub_mock_close_network.AsStdFunction(), + stub_mock_create_endpoint.AsStdFunction(), + stub_mock_open_endpoint.AsStdFunction(), + stub_mock_delete_endpoint.AsStdFunction(), + stub_mock_close_endpoint.AsStdFunction()}; +}; + +TEST_F(HyperVHCNAPI_UnitTests, create_network) +{ + ::testing::MockFunction mock_create_network; + + mock_api_table.CreateNetwork = mock_create_network.AsStdFunction(); + + ON_CALL(mock_create_network, Call(_, _, _, _)) + .WillByDefault(DoAll(Invoke([&]() { + // do some stuff + }), + Return(42))); + + + uut_t uut{mock_api_table}; + hyperv::hcn::CreateNetworkParameters params{}; + params.name = "multipass-hyperv-api-hcn-create-delete-test"; + params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; + params.subnet = "172.50.224.0/20"; + params.gateway = "172.50.224.1"; + + uut.delete_network(params.guid); + + { + const auto& [success, error_msg] = uut.create_network(params); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } + + { + const auto& [success, error_msg] = uut.delete_network(params.guid); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } +} + +} // namespace multipass::test \ No newline at end of file diff --git a/vcpkg.json b/vcpkg.json index b1a6b72d8b..b3e25517cf 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -11,4 +11,4 @@ "version": "11.0.2" } ] -} +} \ No newline at end of file From 7232028161e2bd6488afa4f6d19cb1b8b863f037 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 3 Feb 2025 14:06:31 +0300 Subject: [PATCH 02/26] [hyperv-hcs-hcn] Remove the WIL dependency It's no longer relevant since the wil::unique* wrappers don't accept deleter callable at construction time. Replaced all wil::* usage with std::unique_ptr RAII wrappers. [hcs]: Added LocalFree to the API table [hcn]: Added CoTaskMemFree to the API table [hyperv-common]: Added GUID -> std::[w]string conversion [hcn-unit-tests]: Added create network unit tests --- .gitignore | 3 + .../backends/hyperv_api/CMakeLists.txt | 6 +- .../hyperv_api/hcn/hyperv_hcn_api_table.h | 3 + .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 152 ++-- .../hyperv_api/hcs/hyperv_hcs_api_table.h | 9 + .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 13 +- .../backends/hyperv_api/hyperv_api_common.cpp | 65 +- .../backends/hyperv_api/hyperv_api_common.h | 22 +- .../hyperv_api/hyperv_api_operation_result.h | 47 +- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 682 +++++++++++++++++- 10 files changed, 901 insertions(+), 101 deletions(-) diff --git a/.gitignore b/.gitignore index 5547864f2b..37a03b5484 100644 --- a/.gitignore +++ b/.gitignore @@ -33,3 +33,6 @@ src/client/gui/flutter*.log packaging/windows/wix/obj/* packaging/windows/custom-actions/packages/* packaging/windows/custom-actions/x64/* + +# clangd cache path +.cache/ \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt index a58f1b8770..b46c5eb3cd 100644 --- a/src/platform/backends/hyperv_api/CMakeLists.txt +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -14,9 +14,6 @@ # if(WIN32) - # WIL is only required for Hyper-V API implementation. - find_package(WIL REQUIRED) - add_library(hyperv_api_backend STATIC) target_sources(hyperv_api_backend PRIVATE @@ -26,6 +23,5 @@ if(WIN32) target_link_libraries(hyperv_api_backend PRIVATE fmt - utils - WIL::WIL) + utils) endif() diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index 2b80a5c0fd..34a309c687 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -22,6 +22,7 @@ // (xmkg): clang-format is messing with the include order. #include #include +#include // for CoTaskMemFree // clang-format on #include @@ -51,6 +52,8 @@ struct HCNAPITable std::function DeleteEndpoint = &HcnDeleteEndpoint; // @ref https://learn.microsoft.com/en-us/virtualization/api/hcn/reference/hcndeleteendpoint std::function CloseEndpoint = &HcnCloseEndpoint; + // @ref https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-cotaskmemfree + std::function CoTaskMemFree = &::CoTaskMemFree; }; } // namespace multipass::hyperv::hcn diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index de59a30d9b..612242e371 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -39,7 +39,6 @@ // clang-format on #include -#include #include #include @@ -47,14 +46,72 @@ namespace multipass::hyperv::hcn { +using UniqueHcnNetwork = std::unique_ptr, decltype(HCNAPITable::CloseNetwork)>; +using UniqueHcnEndpoint = std::unique_ptr, decltype(HCNAPITable::CloseEndpoint)>; +using UniqueCotaskmemString = std::unique_ptr; + +// --------------------------------------------------------- + /** * Category for the log messages. */ static constexpr const char* kLogCategory = "HyperV-HCN-Wrapper"; -using UniqueHcnNetwork = std::unique_ptr, decltype(HCNAPITable::CloseNetwork)>; -using UniqueHcnEndpoint = std::unique_ptr, decltype(HCNAPITable::CloseEndpoint)>; -using UniqueCotaskmemString = wil::unique_cotaskmem_string; +// --------------------------------------------------------- + +/** + * HcnCreateNetwork settings JSON template + */ +constexpr auto network_settings_template = LR"""( +{{ + "Name": "{0}", + "Type": "ICS", + "Subnets" : [ + {{ + "GatewayAddress": "{2}", + "AddressPrefix" : "{1}", + "IpSubnets" : [ + {{ + "IpAddressPrefix": "{1}" + }} + ] + }} + ], + "IsolateSwitch": true, + "Flags" : 265 +}} +)"""; + +// --------------------------------------------------------- + +/** + * HcnCreateEndpoint settings JSON template + */ +constexpr auto endpoint_settings_template = LR"( +{{ + "SchemaVersion": {{ + "Major": 2, + "Minor": 16 + }}, + "HostComputeNetwork": "{0}", + "Policies": [ + {{ + "Type": "PortName" + }}, + {{ + "Type": "Firewall", + "Settings": {{ + "VmCreatorId": "{1}", + "PolicyFlags": 0 + }} + }} + ], + "IpConfigurations": [ + {{ + "IpAddress": "{2}" + }} + ] +}})"; // --------------------------------------------------------- @@ -67,7 +124,7 @@ using UniqueCotaskmemString = wil::unique_cotaskmem_string; * @return HCNOperationResult Result of the performed operation */ template -static OperationResult perform_hcn_operation(const FnType& fn, Args&&... args) +static OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, Args&&... args) { // Ensure that function to call is set. @@ -81,13 +138,16 @@ static OperationResult perform_hcn_operation(const FnType& fn, Args&&... args) // HCN functions will use CoTaskMemAlloc to allocate the error message buffer // so use UniqueCotaskmemString to auto-release it with appropriate free // function. - UniqueCotaskmemString result_msgbuf{}; + + wchar_t* result_msg_out{nullptr}; // Perform the operation. The last argument of the all HCN operations (except // HcnClose*) is ErrorRecord, which is a JSON-formatted document emitted by // the API describing the error happened. Therefore, we can streamline all API // calls through perform_operation to perform co - const ResultCode result = fn(std::forward(args)..., &result_msgbuf); + const ResultCode result = fn(std::forward(args)..., &result_msg_out); + + UniqueCotaskmemString result_msgbuf{result_msg_out, api.CoTaskMemFree}; logging::log(logging::Level::trace, kLogCategory, @@ -98,7 +158,7 @@ static OperationResult perform_hcn_operation(const FnType& fn, Args&&... args) // Error message is only valid when the operation resulted in an error. // Passing a nullptr is well-defined in "< C++23", but it's going to be // forbidden afterwards. Going an extra mile just to be future-proof. - return {result, {result ? L"" : result_msgbuf.get()}}; + return {result, {result_msgbuf ? result_msgbuf.get() : L""}}; } // --------------------------------------------------------- @@ -121,7 +181,7 @@ static UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& fmt::format("open_network(...) > network_guid: {} ", network_guid)); HCN_NETWORK network{nullptr}; - const auto result = perform_hcn_operation(api.OpenNetwork, guid_from_string(network_guid), &network); + const auto result = perform_hcn_operation(api, api.OpenNetwork, guid_from_string(network_guid), &network); (void)result; return UniqueHcnNetwork{network, api.CloseNetwork}; } @@ -140,25 +200,6 @@ auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> Operat logging::log(logging::Level::trace, kLogCategory, fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); - constexpr auto network_settings_template = LR"""( - {{ - "Name": "{0}", - "Type": "ICS", - "Subnets" : [ - {{ - "GatewayAddress": "{2}", - "AddressPrefix" : "{1}", - "IpSubnets" : [ - {{ - "IpAddressPrefix": "{1}" - }} - ] - }} - ] , - "IsolateSwitch": true, - "Flags" : 265 - }} - )"""; // Render the template const auto network_settings = fmt::format(network_settings_template, @@ -167,10 +208,21 @@ auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> Operat string_to_wstring(params.gateway)); HCN_NETWORK network{nullptr}; - auto result = - perform_hcn_operation(api.CreateNetwork, guid_from_string(params.guid), network_settings.c_str(), &network); - // We don't need the handle. - api.CloseNetwork(network); + const auto result = perform_hcn_operation(api, + api.CreateNetwork, + guid_from_string(params.guid), + network_settings.c_str(), + &network); + + if (!result) + { + // FIXME: Also include the result error message, if any. + logging::log(logging::Level::warning, + kLogCategory, + fmt::format("HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}.", result.code)); + } + + [[maybe_unused]] UniqueHcnNetwork _{network, api.CloseNetwork}; return result; } @@ -180,8 +232,8 @@ auto HCNWrapper::delete_network(const std::string& network_guid) -> OperationRes { logging::log(logging::Level::trace, kLogCategory, - fmt::format("HCNWrapper::delete_network(...) > network_guid: {} ", network_guid)); - return perform_hcn_operation(api.DeleteNetwork, guid_from_string(network_guid)); + fmt::format("HCNWrapper::delete_network(...) > network_guid: {}", network_guid)); + return perform_hcn_operation(api, api.DeleteNetwork, guid_from_string(network_guid)); } // --------------------------------------------------------- @@ -191,31 +243,6 @@ auto HCNWrapper::create_endpoint(const CreateEndpointParameters& params) -> Oper logging::log(logging::Level::trace, kLogCategory, fmt::format("HCNWrapper::create_endpoint(...) > params: {} ", params)); - constexpr auto endpoint_settings_template = LR"( - {{ - "SchemaVersion": {{ - "Major": 2, - "Minor": 16 - }}, - "HostComputeNetwork": "{0}", - "Policies": [ - {{ - "Type": "PortName" - }}, - {{ - "Type": "Firewall", - "Settings": {{ - "VmCreatorId": "{1}", - "PolicyFlags": 0 - }} - }} - ], - "IpConfigurations": [ - {{ - "IpAddress": "{2}" - }} - ] - }})"; auto network = open_network(api, params.network_guid); @@ -230,12 +257,13 @@ auto HCNWrapper::create_endpoint(const CreateEndpointParameters& params) -> Oper string_to_wstring(params.vm_creator_id), string_to_wstring(params.endpoint_ipvx_addr)); HCN_ENDPOINT endpoint{nullptr}; - auto result = perform_hcn_operation(api.CreateEndpoint, + auto result = perform_hcn_operation(api, + api.CreateEndpoint, network.get(), guid_from_string(params.endpoint_guid), endpoint_settings.c_str(), &endpoint); - api.CloseEndpoint(endpoint); + [[maybe_unused]] UniqueHcnEndpoint _{endpoint, api.CloseEndpoint}; return result; } @@ -246,7 +274,7 @@ auto HCNWrapper::delete_endpoint(const std::string& endpoint_guid) -> OperationR logging::log(logging::Level::trace, kLogCategory, fmt::format("HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid)); - return perform_hcn_operation(api.DeleteEndpoint, guid_from_string(endpoint_guid)); + return perform_hcn_operation(api, api.DeleteEndpoint, guid_from_string(endpoint_guid)); } } // namespace multipass::hyperv::hcn diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index 8d266a6a84..c7990b0f3c 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -69,6 +69,15 @@ struct HCSAPITable std::function RevokeVmAccess = &HcsRevokeVmAccess; // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsenumeratecomputesystems std::function EnumerateComputeSystems = &HcsEnumerateComputeSystems; + + /** + * @brief LocalAlloc/LocalFree is used by the HCS API to manage memory for the status/error + * messages. It's caller's responsibility to free the messages allocated by the API, that's + * why the LocalFree is part of the API table. + * + * @ref https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree + */ + std::function LocalFree = &::LocalFree; }; } // namespace multipass::hyperv::hcs diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index ba9d9b32c1..187680eaf3 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -30,7 +30,6 @@ #include #include -#include #include #include @@ -45,9 +44,7 @@ static constexpr const char* kLogCategory = "HyperV-HCS-Wrapper"; using UniqueHcsSystem = std::unique_ptr, decltype(HCSAPITable::CloseComputeSystem)>; using UniqueHcsOperation = std::unique_ptr, decltype(HCSAPITable::CloseOperation)>; -using UniqueHlocalString = wil::unique_hlocal_string; - -// HCS_OPERATION operation{nullptr}; +using UniqueHlocalString = std::unique_ptr; // --------------------------------------------------------- @@ -82,10 +79,12 @@ static auto wait_for_operation_result(const HCSAPITable& api, logging::Level::trace, kLogCategory, fmt::format("wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), timeout.count())); - wil::unique_hlocal_string result_msg{}; - const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg); - if (result_msg.is_valid()) + wchar_t* result_msg_out{nullptr}; + const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg_out); + UniqueHlocalString result_msg{result_msg_out, api.LocalFree}; + + if (result_msg) { // TODO: Convert from wstring to ascii and log this // logging::log(logging::Level::trace, diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp index 19e792ba26..30b8947c6f 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.cpp +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -17,17 +17,54 @@ #include "hyperv_api_common.h" -#include -#include +#include #include #include #include +/** + * Formatter for GUID type + */ +template +struct fmt::formatter<::GUID, Char> +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const ::GUID& guid, FormatContext& ctx) const + { + // The format string is laid out char by char to allow it + // to be used for initializing variables with different character + // sizes. + static constexpr Char guid_f[] = {'{', ':', '0', '8', 'x', '}', '-', '{', ':', '0', '4', 'x', '}', '-', '{', + ':', '0', '4', 'x', '}', '-', '{', ':', '0', '2', 'x', '}', '{', ':', '0', + '2', 'x', '}', '-', '{', ':', '0', '2', 'x', '}', '{', ':', '0', '2', 'x', + '}', '{', ':', '0', '2', 'x', '}', '{', ':', '0', '2', 'x', '}', '{', ':', + '0', '2', 'x', '}', '{', ':', '0', '2', 'x', '}', 0}; + return format_to(ctx.out(), + guid_f, + guid.Data1, + guid.Data2, + guid.Data3, + guid.Data4[0], + guid.Data4[1], + guid.Data4[2], + guid.Data4[3], + guid.Data4[4], + guid.Data4[5], + guid.Data4[6], + guid.Data4[7]); + } +}; + namespace multipass::hyperv { -auto guid_from_string(const std::wstring& guid_wstr) -> ::GUID +auto guid_from_wstring(const std::wstring& guid_wstr) -> ::GUID { // Since we're using the windows.h in LEAN_AND_MEAN mode, COM-provided @@ -87,9 +124,10 @@ auto string_to_wstring(const std::string& str) -> std::wstring // We could've gotten away with `return std::wstring{str.begin(), str.end()}` // and it'd work for 99 pct of the scenarios we'd see, but... let's do the correct thing. - using unique_locale = wil::unique_any<_locale_t, decltype(&_free_locale), _free_locale>; // Avoid recreating the locale. - static unique_locale locale{_create_locale(LC_ALL, "C")}; + static std::unique_ptr, decltype(&_free_locale)> locale{ + _create_locale(LC_ALL, "C"), + _free_locale}; // Call the function with nullptr to learn how much space we need to // store the result. @@ -142,7 +180,22 @@ auto string_to_wstring(const std::string& str) -> std::wstring auto guid_from_string(const std::string& guid_str) -> GUID { // Just use the wide string overload. - return guid_from_string(string_to_wstring(guid_str)); + return guid_from_wstring(string_to_wstring(guid_str)); +} + +// --------------------------------------------------------- + +auto guid_to_string(const ::GUID& guid) -> std::string +{ + + return fmt::format("{}", guid); +} + +// --------------------------------------------------------- + +auto guid_to_wstring(const ::GUID& guid) -> std::wstring +{ + return fmt::format(L"{}", guid); } } // namespace multipass::hyperv \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.h b/src/platform/backends/hyperv_api/hyperv_api_common.h index f665a1cb2c..4e10ea6eff 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.h +++ b/src/platform/backends/hyperv_api/hyperv_api_common.h @@ -32,7 +32,27 @@ namespace multipass::hyperv * * @return GUID The parsed GUID */ -auto guid_from_string(const std::wstring& guid_wstr) -> GUID; +auto guid_from_wstring(const std::wstring& guid_wstr) -> GUID; + +// --------------------------------------------------------- + +/** + * @brief + * + * @param guid + * @return std::string + */ +auto guid_to_string(const ::GUID& guid) -> std::string; + +// --------------------------------------------------------- + +/** + * @brief + * + * @param guid + * @return std::wstring + */ +auto guid_to_wstring(const ::GUID& guid) -> std::wstring; // --------------------------------------------------------- diff --git a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h index 4941173aff..2d29a6313a 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h +++ b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h @@ -21,6 +21,8 @@ #include #include +#include + namespace multipass::hyperv { @@ -40,7 +42,7 @@ struct ResultCode return *this; } - [[nodiscard]] operator bool() const noexcept + [[nodiscard]] explicit operator bool() const noexcept { return !FAILED(result); } @@ -50,6 +52,11 @@ struct ResultCode return result; } + [[nodiscard]] explicit operator std::make_unsigned_t() const noexcept + { + return static_cast(result); + } + private: HRESULT result{}; }; @@ -75,11 +82,47 @@ struct OperationResult */ const std::wstring status_msg; - operator bool() const noexcept + [[nodiscard]] explicit operator bool() const noexcept { return static_cast(code); } }; } // namespace multipass::hyperv +/** + * Formatter type specialization for ResultCode + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::ResultCode& rc, FormatContext& ctx) const + { + return format_to(ctx.out(), "{0:#x}", static_cast>(rc)); + } +}; + +/** + * Formatter type specialization for ResultCode + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::OperationResult& opr, FormatContext& ctx) const + { + return format_to(ctx.out(), "{0:#x}", opr.code); + } +}; + #endif diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index aa2f4a1c40..a2458c42cf 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -16,20 +16,22 @@ */ #include "hyperv_api/hcn/hyperv_hcn_api_table.h" -#include "tests/common.h" #include "tests/mock_logger.h" #include "gmock/gmock.h" +#include #include +#include #include #include #include +#include +#include namespace mpt = multipass::test; +namespace mpl = multipass::logging; -using testing::_; using testing::DoAll; -using testing::Invoke; using testing::Return; #define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) @@ -46,6 +48,7 @@ struct HyperVHCNAPI_UnitTests : public ::testing::Test void SetUp() override { + // Each of the unit tests are expected to have their own mock functions // and override the mock_api_table with them. Hence, the stub mocks should // not be called at all. @@ -64,6 +67,7 @@ struct HyperVHCNAPI_UnitTests : public ::testing::Test EXPECT_NO_CALL(stub_mock_open_endpoint); EXPECT_NO_CALL(stub_mock_delete_endpoint); EXPECT_NO_CALL(stub_mock_close_endpoint); + EXPECT_NO_CALL(stub_mock_cotaskmemfree); } void TearDown() override @@ -79,7 +83,10 @@ struct HyperVHCNAPI_UnitTests : public ::testing::Test ::testing::MockFunction stub_mock_open_endpoint; ::testing::MockFunction stub_mock_delete_endpoint; ::testing::MockFunction stub_mock_close_endpoint; + ::testing::MockFunction stub_mock_cotaskmemfree; + // Initialize the API table with stub functions, so if any of these fire without + // our will, we'll know. hyperv::hcn::HCNAPITable mock_api_table{stub_mock_create_network.AsStdFunction(), stub_mock_open_network.AsStdFunction(), stub_mock_delete_network.AsStdFunction(), @@ -87,42 +94,681 @@ struct HyperVHCNAPI_UnitTests : public ::testing::Test stub_mock_create_endpoint.AsStdFunction(), stub_mock_open_endpoint.AsStdFunction(), stub_mock_delete_endpoint.AsStdFunction(), - stub_mock_close_endpoint.AsStdFunction()}; + stub_mock_close_endpoint.AsStdFunction(), + stub_mock_cotaskmemfree.AsStdFunction()}; + + // Sentinel values as mock API parameters. These handles are opaque handles and + // they're not being dereferenced in any way -- only address values are compared. + inline static auto mock_network_object = reinterpret_cast(0xbadf00d); + inline static auto mock_endpoint_object = reinterpret_cast(0xbadcafe); + + // Generic error message for all tests, intended to be used for API calls returning + // an "error_record". + inline static wchar_t mock_error_msg[16] = L"It's a failure."; }; -TEST_F(HyperVHCNAPI_UnitTests, create_network) +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCNAPI_UnitTests, create_network_success) { + /****************************************************** + * Override the default mock functions. + ******************************************************/ ::testing::MockFunction mock_create_network; + ::testing::MockFunction mock_close_network; mock_api_table.CreateNetwork = mock_create_network.AsStdFunction(); + mock_api_table.CloseNetwork = mock_close_network.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_network, Call) + .WillOnce(DoAll( + [&](REFGUID id, PCWSTR settings, PHCN_NETWORK network, PWSTR* error_record) { + constexpr auto expected_network_settings = LR"""( +{ + "Name": "multipass-hyperv-api-hcn-create-test", + "Type": "ICS", + "Subnets" : [ + { + "GatewayAddress": "172.50.224.1", + "AddressPrefix" : "172.50.224.0/20", + "IpSubnets" : [ + { + "IpAddressPrefix": "172.50.224.0/20" + } + ] + } + ], + "IsolateSwitch": true, + "Flags" : 265 +} +)"""; + ASSERT_NE(nullptr, network); + ASSERT_EQ(nullptr, *network); + ASSERT_NE(nullptr, error_record); + ASSERT_EQ(nullptr, *error_record); + ASSERT_STREQ(settings, expected_network_settings); + const auto guid_str = hyperv::guid_to_string(id); + ASSERT_EQ("b70c479d-f808-4053-aafa-705bc15b6d68", guid_str); + *network = mock_network_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_network, Call) + .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(NOERROR))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcn::CreateNetworkParameters params{}; + params.name = "multipass-hyperv-api-hcn-create-test"; + params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; + params.subnet = "172.50.224.0/20"; + params.gateway = "172.50.224.1"; + + const auto& [success, error_msg] = uut.create_network(params); + ASSERT_TRUE(success); + ASSERT_TRUE(error_msg.empty()); + } +} - ON_CALL(mock_create_network, Call(_, _, _, _)) - .WillByDefault(DoAll(Invoke([&]() { - // do some stuff - }), - Return(42))); +// --------------------------------------------------------- +/** + * Success scenario 2: HcnCloseNetwork returns an error. + */ +TEST_F(HyperVHCNAPI_UnitTests, create_network_close_network_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_network; + ::testing::MockFunction mock_close_network; + + mock_api_table.CreateNetwork = mock_create_network.AsStdFunction(); + mock_api_table.CloseNetwork = mock_close_network.AsStdFunction(); - uut_t uut{mock_api_table}; - hyperv::hcn::CreateNetworkParameters params{}; - params.name = "multipass-hyperv-api-hcn-create-delete-test"; - params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; - params.subnet = "172.50.224.0/20"; - params.gateway = "172.50.224.1"; + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_network, Call) + .WillOnce(DoAll( + [&](REFGUID id, PCWSTR settings, PHCN_NETWORK network, PWSTR* error_record) { + *network = mock_network_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_network, Call) + .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(E_POINTER))); - uut.delete_network(params.guid); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::create_network(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...)"); + } + /****************************************************** + * Verify the expected outcome. + ******************************************************/ { + hyperv::hcn::CreateNetworkParameters params{}; + params.name = "multipass-hyperv-api-hcn-create-test"; + params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; + params.subnet = "172.50.224.0/20"; + params.gateway = "172.50.224.1"; + + uut_t uut{mock_api_table}; const auto& [success, error_msg] = uut.create_network(params); ASSERT_TRUE(success); ASSERT_TRUE(error_msg.empty()); } +} + +// --------------------------------------------------------- + +/** + * Failure scenario 1: HcnCreateNetwork returns an error. + */ +TEST_F(HyperVHCNAPI_UnitTests, create_network_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_network; + ::testing::MockFunction mock_close_network; + ::testing::MockFunction mock_cotaskmemfree; + + mock_api_table.CreateNetwork = mock_create_network.AsStdFunction(); + mock_api_table.CloseNetwork = mock_close_network.AsStdFunction(); + mock_api_table.CoTaskMemFree = mock_cotaskmemfree.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_network, Call) + .WillOnce(DoAll( + [&](REFGUID id, PCWSTR settings, PHCN_NETWORK network, PWSTR* error_record) { + *network = mock_network_object; + *error_record = mock_error_msg; + }, + Return(E_POINTER))); + + EXPECT_CALL(mock_close_network, Call) + .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(NOERROR))); + + EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([&](void* ptr) { EXPECT_EQ(ptr, mock_error_msg); }); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::create_network(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::warning, + "HCNWrapper::create_network(...) > HcnCreateNetwork failed with 0x80004003."); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + hyperv::hcn::CreateNetworkParameters params{}; + params.name = "multipass-hyperv-api-hcn-create-test"; + params.guid = "{b70c479d-f808-4053-aafa-705bc15b6d68}"; + params.subnet = "172.50.224.0/20"; + params.gateway = "172.50.224.1"; + + uut_t uut{mock_api_table}; + const auto& [success, error_msg] = uut.create_network(params); + ASSERT_FALSE(success); + ASSERT_EQ(static_cast(success), E_POINTER); + ASSERT_FALSE(error_msg.empty()); + ASSERT_STREQ(error_msg.c_str(), mock_error_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCNAPI_UnitTests, delete_network_success) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_delete_network; + + mock_api_table.DeleteNetwork = mock_delete_network.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_delete_network, Call) + .WillOnce(DoAll( + [&](REFGUID guid, PWSTR* error_record) { + const auto guid_str = hyperv::guid_to_string(guid); + ASSERT_EQ("af3fb745-2f23-463c-8ded-443f876d9e81", guid_str); + ASSERT_EQ(nullptr, *error_record); + ASSERT_NE(nullptr, error_record); + }, + Return(NOERROR))); + + // Expected logs + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::delete_network(...) > network_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + const auto& [status, error_msg] = uut.delete_network("af3fb745-2f23-463c-8ded-443f876d9e81"); + ASSERT_TRUE(status); + ASSERT_TRUE(error_msg.empty()); + } +} + +// --------------------------------------------------------- + +/** + * Failure scenario: API call returns non-success + */ +TEST_F(HyperVHCNAPI_UnitTests, delete_network_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_delete_network; + ::testing::MockFunction mock_cotaskmemfree; + + mock_api_table.DeleteNetwork = mock_delete_network.AsStdFunction(); + mock_api_table.CoTaskMemFree = mock_cotaskmemfree.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_delete_network, Call) + .WillOnce(DoAll( + [&](REFGUID, PWSTR* error_record) { + ASSERT_EQ(nullptr, *error_record); + ASSERT_NE(nullptr, error_record); + *error_record = mock_error_msg; + }, + Return(E_POINTER))); + + EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([&](void* ptr) { EXPECT_EQ(ptr, mock_error_msg); }); + // Expected logs + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::delete_network(...) > network_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + const auto& [status, error_msg] = uut.delete_network("af3fb745-2f23-463c-8ded-443f876d9e81"); + ASSERT_FALSE(status); + ASSERT_FALSE(error_msg.empty()); + ASSERT_STREQ(error_msg.c_str(), mock_error_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_endpoint; + ::testing::MockFunction mock_close_endpoint; + ::testing::MockFunction mock_open_network; + ::testing::MockFunction mock_close_network; + + mock_api_table.CreateEndpoint = mock_create_endpoint.AsStdFunction(); + mock_api_table.CloseEndpoint = mock_close_endpoint.AsStdFunction(); + mock_api_table.OpenNetwork = mock_open_network.AsStdFunction(); + mock_api_table.CloseNetwork = mock_close_network.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_endpoint, Call) + .WillOnce(DoAll( + [&](HCN_NETWORK network, REFGUID id, PCWSTR settings, PHCN_ENDPOINT endpoint, PWSTR* error_record) { + constexpr auto expected_endpoint_settings = LR"""( +{ + "SchemaVersion": { + "Major": 2, + "Minor": 16 + }, + "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", + "Policies": [ + { + "Type": "PortName" + }, + { + "Type": "Firewall", + "Settings": { + "VmCreatorId": "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e", + "PolicyFlags": 0 + } + } + ], + "IpConfigurations": [ + { + "IpAddress": "172.50.224.27" + } + ] +})"""; + + ASSERT_NE(nullptr, network); + ASSERT_EQ(mock_network_object, network); + ASSERT_NE(nullptr, error_record); + ASSERT_EQ(nullptr, *error_record); + ASSERT_NE(nullptr, endpoint); + ASSERT_EQ(nullptr, *endpoint); + ASSERT_STREQ(settings, expected_endpoint_settings); + const auto endpoint_guid_str = hyperv::guid_to_string(id); + ASSERT_EQ("77c27c1e-8204-437d-a7cc-fb4ce1614819", endpoint_guid_str); + *endpoint = mock_endpoint_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_endpoint, Call) + .WillOnce(DoAll([&](HCN_ENDPOINT n) { ASSERT_EQ(n, mock_endpoint_object); }, Return(NOERROR))); + + EXPECT_CALL(mock_open_network, Call) + .WillOnce(DoAll( + [&](REFGUID id, PHCN_NETWORK network, PWSTR* error_record) { + const auto expected_network_guid_str = hyperv::guid_to_string(id); + ASSERT_EQ("b70c479d-f808-4053-aafa-705bc15b6d68", expected_network_guid_str); + ASSERT_NE(nullptr, network); + ASSERT_EQ(nullptr, *network); + ASSERT_NE(nullptr, error_record); + ASSERT_EQ(nullptr, *error_record); + *network = mock_network_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_network, Call) + .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(NOERROR))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " + "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "perform_operation(...) > fn: 0x0, result: true", + testing::Exactly(2)); + } + /****************************************************** + * Verify the expected outcome. + ******************************************************/ { - const auto& [success, error_msg] = uut.delete_network(params.guid); + uut_t uut{mock_api_table}; + hyperv::hcn::CreateEndpointParameters params{}; + params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; + params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; + params.endpoint_ipvx_addr = "172.50.224.27"; + params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; + + const auto& [success, error_msg] = uut.create_endpoint(params); ASSERT_TRUE(success); ASSERT_TRUE(error_msg.empty()); } } +// --------------------------------------------------------- + +/** + * Failure scenario: internal open_network call fails. + */ +TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_open_network_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_network; + + mock_api_table.OpenNetwork = mock_open_network.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_network, Call).WillOnce(Return(E_POINTER)); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " + "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcn::CreateEndpointParameters params{}; + params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; + params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; + params.endpoint_ipvx_addr = "172.50.224.27"; + params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; + + const auto& [status, error_msg] = uut.create_endpoint(params); + ASSERT_FALSE(status); + ASSERT_EQ(E_POINTER, static_cast(status)); + ASSERT_FALSE(error_msg.empty()); + ASSERT_STREQ(error_msg.c_str(), L"Could not open the network!"); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + + ::testing::MockFunction mock_create_endpoint; + ::testing::MockFunction mock_close_endpoint; + ::testing::MockFunction mock_open_network; + ::testing::MockFunction mock_close_network; + ::testing::MockFunction mock_cotaskmemfree; + + mock_api_table.CreateEndpoint = mock_create_endpoint.AsStdFunction(); + mock_api_table.CloseEndpoint = mock_close_endpoint.AsStdFunction(); + mock_api_table.OpenNetwork = mock_open_network.AsStdFunction(); + mock_api_table.CloseNetwork = mock_close_network.AsStdFunction(); + mock_api_table.CoTaskMemFree = mock_cotaskmemfree.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_endpoint, Call) + .WillOnce(DoAll( + [&](HCN_NETWORK network, REFGUID id, PCWSTR settings, PHCN_ENDPOINT endpoint, PWSTR* error_record) { + constexpr auto expected_endpoint_settings = LR"""( +{ + "SchemaVersion": { + "Major": 2, + "Minor": 16 + }, + "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", + "Policies": [ + { + "Type": "PortName" + }, + { + "Type": "Firewall", + "Settings": { + "VmCreatorId": "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e", + "PolicyFlags": 0 + } + } + ], + "IpConfigurations": [ + { + "IpAddress": "172.50.224.27" + } + ] +})"""; + + ASSERT_EQ(mock_network_object, network); + ASSERT_NE(nullptr, error_record); + ASSERT_STREQ(settings, expected_endpoint_settings); + const auto expected_endpoint_guid_str = hyperv::guid_to_string(id); + ASSERT_EQ("77c27c1e-8204-437d-a7cc-fb4ce1614819", expected_endpoint_guid_str); + *endpoint = mock_endpoint_object; + *error_record = mock_error_msg; + }, + Return(E_POINTER))); + + EXPECT_CALL(mock_close_endpoint, Call) + .WillOnce(DoAll([&](HCN_ENDPOINT n) { ASSERT_EQ(n, mock_endpoint_object); }, Return(NOERROR))); + + EXPECT_CALL(mock_open_network, Call) + .WillOnce(DoAll( + [&](REFGUID id, PHCN_NETWORK network, PWSTR* error_record) { + const auto expected_network_guid_str = hyperv::guid_to_string(id); + ASSERT_EQ("b70c479d-f808-4053-aafa-705bc15b6d68", expected_network_guid_str); + ASSERT_NE(nullptr, error_record); + ASSERT_EQ(nullptr, *error_record); + *network = mock_network_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_network, Call) + .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(NOERROR))); + + EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([](const void* ptr) { ASSERT_EQ(ptr, mock_error_msg); }); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " + "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcn::CreateEndpointParameters params{}; + params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; + params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; + params.endpoint_ipvx_addr = "172.50.224.27"; + params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; + + const auto& [success, error_msg] = uut.create_endpoint(params); + ASSERT_FALSE(success); + ASSERT_FALSE(error_msg.empty()); + ASSERT_STREQ(error_msg.c_str(), mock_error_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCNAPI_UnitTests, delete_endpoint_success) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_delete_endpoint; + + mock_api_table.DeleteEndpoint = mock_delete_endpoint.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_delete_endpoint, Call) + .WillOnce(DoAll( + [&](REFGUID guid, PWSTR* error_record) { + const auto guid_str = hyperv::guid_to_string(guid); + ASSERT_EQ("af3fb745-2f23-463c-8ded-443f876d9e81", guid_str); + ASSERT_EQ(nullptr, *error_record); + ASSERT_NE(nullptr, error_record); + }, + Return(NOERROR))); + + // Expected logs + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::delete_endpoint(...) > endpoint_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + const auto& [status, error_msg] = uut.delete_endpoint("af3fb745-2f23-463c-8ded-443f876d9e81"); + ASSERT_TRUE(status); + ASSERT_TRUE(error_msg.empty()); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCNAPI_UnitTests, delete_endpoint_failure) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_delete_endpoint; + ::testing::MockFunction mock_cotaskmemfree; + + mock_api_table.DeleteEndpoint = mock_delete_endpoint.AsStdFunction(); + mock_api_table.CoTaskMemFree = mock_cotaskmemfree.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_delete_endpoint, Call) + .WillOnce(DoAll([&](REFGUID, PWSTR* error_record) { *error_record = mock_error_msg; }, Return(E_POINTER))); + + EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([](const void* ptr) { ASSERT_EQ(ptr, mock_error_msg); }); + + // Expected logs + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCNWrapper::delete_endpoint(...) > endpoint_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + const auto& [status, error_msg] = uut.delete_endpoint("af3fb745-2f23-463c-8ded-443f876d9e81"); + ASSERT_FALSE(status); + ASSERT_FALSE(error_msg.empty()); + ASSERT_STREQ(error_msg.c_str(), mock_error_msg); + } +} + } // namespace multipass::test \ No newline at end of file From fd2800d140b3dc837af403e3b76b8ca5fabb3bfc Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 11 Feb 2025 21:37:38 +0300 Subject: [PATCH 03/26] [hyperv-hcs]: Add unit tests for the HCSWrapper --- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 99 +- tests/hyperv_api/CMakeLists.txt | 5 + tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 6 +- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 2484 +++++++++++++++++ 4 files changed, 2543 insertions(+), 51 deletions(-) create mode 100644 tests/hyperv_api/test_ut_hyperv_hcs_api.cpp diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 187680eaf3..2ce156ffad 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -161,15 +161,21 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == system) { - return OperationResult{E_POINTER, L"Failure opening the target host compute system!"}; + return OperationResult{E_POINTER, L"HcsOpenComputeSystem failed!"}; } auto operation = create_operation(api); + + if (nullptr == operation) + { + return OperationResult{E_POINTER, L"HcsCreateOperation failed!"}; + } + const auto result = fn(system.get(), operation.get(), std::forward(args)...); if (FAILED(result)) { - return OperationResult{result, L"HCN operation failed."}; + return OperationResult{result, L"HCS operation failed!"}; } logging::log(logging::Level::trace, @@ -188,10 +194,6 @@ HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} logging::log(logging::Level::trace, kLogCategory, fmt::format("HCSWrapper::HCSWrapper(...) > api_table: {}", api)); } -struct vm_settings -{ -}; - // --------------------------------------------------------- OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) @@ -238,45 +240,45 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam // and use that. // https://raw.githubusercontent.com/MicrosoftDocs/Virtualization-Documentation/refs/heads/main/hyperv-samples/hcs-samples/JSON_files/HCS_Schema%5BWindows_10_SDK_version_1809%5D.json constexpr auto vm_settings_template = LR"( - {{ - "SchemaVersion": {{ - "Major": 2, - "Minor": 1 + {{ + "SchemaVersion": {{ + "Major": 2, + "Minor": 1 + }}, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": {{ + "Chipset": {{ + "Uefi": {{ + "BootThis": {{ + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" }}, - "Owner": "Multipass", - "ShouldTerminateOnLastHandleClosed": false, - "VirtualMachine": {{ - "Chipset": {{ - "Uefi": {{ - "BootThis": {{ - "DevicePath": "Primary disk", - "DiskNumber": 0, - "DeviceType": "ScsiDrive" - }}, - "Console": "ComPort1" - }} - }}, - "ComputeTopology": {{ - "Memory": {{ - "Backing": "Virtual", - "SizeInMB": {1} - }}, - "Processor": {{ - "Count": {0} - }} - }}, - "Devices": {{ - "ComPorts": {{ - "0": {{ - "NamedPipe": "\\\\.\\pipe\\{2}" - }} - }}, - "Scsi": {{ - {3} - }} - }} + "Console": "ComPort1" + }} + }}, + "ComputeTopology": {{ + "Memory": {{ + "Backing": "Virtual", + "SizeInMB": {1} + }}, + "Processor": {{ + "Count": {0} + }} + }}, + "Devices": {{ + "ComPorts": {{ + "0": {{ + "NamedPipe": "\\\\.\\pipe\\{2}" }} - }})"; + }}, + "Scsi": {{ + {3} + }} + }} + }} + }})"; // Render the template const auto vm_settings = fmt::format(vm_settings_template, @@ -288,6 +290,11 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam auto operation = create_operation(api); + if (nullptr == operation) + { + return OperationResult{E_POINTER, L"HcsCreateOperation failed."}; + } + const auto name_w = string_to_wstring(params.name); const auto result = api.CreateComputeSystem(name_w.c_str(), vm_settings.c_str(), operation.get(), nullptr, &system); @@ -432,9 +439,7 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na { const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); - const auto result = api.GrantVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); - return {result, FAILED(result) ? L"GrantVmAccess failed!" : L""}; } @@ -445,9 +450,7 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n { const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); - const auto result = api.RevokeVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); - return {result, FAILED(result) ? L"RevokeVmAccess failed!" : L""}; } @@ -456,7 +459,7 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) { - auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); + const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) { return {result.code, L"Unknown"}; diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt index d98415fa0e..f91a088707 100644 --- a/tests/hyperv_api/CMakeLists.txt +++ b/tests/hyperv_api/CMakeLists.txt @@ -14,5 +14,10 @@ if(WIN32) PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcs_api.cpp ) + + target_sources(multipass_tests + PRIVATE + ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcs_api.cpp + ) endif() diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index a2458c42cf..78737bfe34 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -176,9 +176,9 @@ TEST_F(HyperVHCNAPI_UnitTests, create_network_success) params.subnet = "172.50.224.0/20"; params.gateway = "172.50.224.1"; - const auto& [success, error_msg] = uut.create_network(params); - ASSERT_TRUE(success); - ASSERT_TRUE(error_msg.empty()); + const auto& [status, status_msg] = uut.create_network(params); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } } diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp new file mode 100644 index 0000000000..cce1d5ae95 --- /dev/null +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -0,0 +1,2484 @@ +/* + * 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 "hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h" +#include "hyperv_api/hcs/hyperv_hcs_api_wrapper.h" +#include "hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h" +#include "tests/mock_logger.h" +#include "gmock/gmock.h" + +#include +#include +#include +#include +#include + +namespace mpt = multipass::test; +namespace mpl = multipass::logging; + +using testing::DoAll; +using testing::Return; + +#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) + +static auto trim_whitespace(const wchar_t* input) +{ + std::wstring str{input}; + str.erase(std::remove_if(str.begin(), str.end(), ::iswspace), str.end()); + return str; +} + +namespace multipass::test +{ +using uut_t = hyperv::hcs::HCSWrapper; + +struct HyperVHCSAPI_UnitTests : public ::testing::Test +{ + mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + + void SetUp() override + { + + // Each of the unit tests are expected to have their own mock functions + // and override the mock_api_table with them. Hence, the stub mocks should + // not be called at all. + // If any of them do get called, then: + // + // a-) You have forgotten to mock something + // b-) The implementation is using a function that you didn't expect + // + // Either way, you should have a look. + EXPECT_NO_CALL(stub_mock_create_operation); + EXPECT_NO_CALL(stub_mock_wait_for_operation_result); + EXPECT_NO_CALL(stub_mock_close_operation); + EXPECT_NO_CALL(stub_mock_create_compute_system); + EXPECT_NO_CALL(stub_mock_open_compute_system); + EXPECT_NO_CALL(stub_mock_start_compute_system); + EXPECT_NO_CALL(stub_mock_shutdown_compute_system); + EXPECT_NO_CALL(stub_mock_terminate_compute_system); + EXPECT_NO_CALL(stub_mock_close_compute_system); + EXPECT_NO_CALL(stub_mock_pause_compute_system); + EXPECT_NO_CALL(stub_mock_resume_compute_system); + EXPECT_NO_CALL(stub_mock_modify_compute_system); + EXPECT_NO_CALL(stub_mock_get_compute_system_properties); + EXPECT_NO_CALL(stub_mock_grant_vm_access); + EXPECT_NO_CALL(stub_mock_revoke_vm_access); + EXPECT_NO_CALL(stub_mock_enumerate_compute_systems); + EXPECT_NO_CALL(stub_mock_local_free); + } + + void TearDown() override + { + } + + // Set of placeholder mocks in order to catch *unexpected* calls. + ::testing::MockFunction stub_mock_create_operation; + ::testing::MockFunction stub_mock_wait_for_operation_result; + ::testing::MockFunction stub_mock_close_operation; + ::testing::MockFunction stub_mock_create_compute_system; + ::testing::MockFunction stub_mock_open_compute_system; + ::testing::MockFunction stub_mock_start_compute_system; + ::testing::MockFunction stub_mock_shutdown_compute_system; + ::testing::MockFunction stub_mock_terminate_compute_system; + ::testing::MockFunction stub_mock_close_compute_system; + ::testing::MockFunction stub_mock_pause_compute_system; + ::testing::MockFunction stub_mock_resume_compute_system; + ::testing::MockFunction stub_mock_modify_compute_system; + ::testing::MockFunction stub_mock_get_compute_system_properties; + ::testing::MockFunction stub_mock_grant_vm_access; + ::testing::MockFunction stub_mock_revoke_vm_access; + ::testing::MockFunction stub_mock_enumerate_compute_systems; + ::testing::MockFunction stub_mock_local_free; + + // Initialize the API table with stub functions, so if any of these fire without + // our will, we'll know. + hyperv::hcs::HCSAPITable mock_api_table{stub_mock_create_operation.AsStdFunction(), + stub_mock_wait_for_operation_result.AsStdFunction(), + stub_mock_close_operation.AsStdFunction(), + stub_mock_create_compute_system.AsStdFunction(), + stub_mock_open_compute_system.AsStdFunction(), + stub_mock_start_compute_system.AsStdFunction(), + stub_mock_shutdown_compute_system.AsStdFunction(), + stub_mock_terminate_compute_system.AsStdFunction(), + stub_mock_close_compute_system.AsStdFunction(), + stub_mock_pause_compute_system.AsStdFunction(), + stub_mock_resume_compute_system.AsStdFunction(), + stub_mock_modify_compute_system.AsStdFunction(), + stub_mock_get_compute_system_properties.AsStdFunction(), + stub_mock_grant_vm_access.AsStdFunction(), + stub_mock_revoke_vm_access.AsStdFunction(), + stub_mock_enumerate_compute_systems.AsStdFunction(), + stub_mock_local_free.AsStdFunction()}; + + // Sentinel values as mock API parameters. These handles are opaque handles and + // they're not being dereferenced in any way -- only address values are compared. + inline static auto mock_operation_object = reinterpret_cast(0xbadf00d); + inline static auto mock_compute_system_object = reinterpret_cast(0xbadcafe); + + // Generic error message for all tests, intended to be used for API calls returning + // an "error_record". + inline static wchar_t mock_error_msg[16] = L"It's a failure."; + inline static wchar_t mock_success_msg[16] = L"Succeeded."; + inline static wchar_t operation_fail_msg[22] = L"HCS operation failed!"; + inline static wchar_t hcs_create_operation_fail_msg[27] = L"HcsCreateOperation failed!"; + inline static wchar_t hcs_open_compute_system_fail_msg[29] = L"HcsOpenComputeSystem failed!"; + + template + void generic_operation_happy_path(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR operation_result_document = nullptr, + PWSTR expected_status_msg = nullptr); + + template + void generic_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR expected_status_msg = operation_fail_msg); + + template + void generic_operation_wait_for_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR operation_result_document = mock_error_msg, + PWSTR expected_status_msg = mock_error_msg); + + template + void generic_operation_hcs_open_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + PWSTR expected_status_msg = hcs_open_compute_system_fail_msg); + + template + void generic_operation_create_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + PWSTR expected_status_msg = hcs_create_operation_fail_msg); +}; + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_happy_path) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_create_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + "cloud-init iso file": { + "Attachments": { + "0": { + "Type": "Iso", + "Path": "cloudinit iso path", + "ReadOnly": true + } + } + }, + "Primary disk": { + "Attachments": { + "0": { + "Type": "VirtualDisk", + "Path": "virtual disk path", + "ReadOnly": false + } + } + }, + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = mock_success_msg; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = "cloudinit iso path"; + params.vhdx_path = "virtual disk path"; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_TRUE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), mock_success_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_create_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + "Primary disk": { + "Attachments": { + "0": { + "Type": "VirtualDisk", + "Path": "virtual disk path", + "ReadOnly": false + } + } + }, + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = mock_success_msg; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = ""; + params.vhdx_path = "virtual disk path"; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_TRUE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), mock_success_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_vhdx) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_create_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + "cloud-init iso file": { + "Attachments": { + "0": { + "Type": "Iso", + "Path": "cloudinit iso path", + "ReadOnly": true + } + } + }, + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = mock_success_msg; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = "cloudinit iso path"; + params.vhdx_path = ""; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_TRUE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), mock_success_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit_and_vhdx) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_create_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = mock_success_msg; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = ""; + params.vhdx_path = ""; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_TRUE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), mock_success_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_create_operation_fail) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = "cloudinit iso path"; + params.vhdx_path = "virtual disk path"; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"HcsCreateOperation failed."); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_fail) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_create_compute_system; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + "cloud-init iso file": { + "Attachments": { + "0": { + "Type": "Iso", + "Path": "cloudinit iso path", + "ReadOnly": true + } + } + }, + "Primary disk": { + "Attachments": { + "0": { + "Type": "VirtualDisk", + "Path": "virtual disk path", + "ReadOnly": false + } + } + }, + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + }, + Return(E_POINTER))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = "cloudinit iso path"; + params.vhdx_path = "virtual disk path"; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"HcsCreateComputeSystem failed."); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wait_for_operation_fail) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_create_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + + constexpr auto expected_vm_settings_json = LR"( + { + "SchemaVersion": { + "Major": 2, + "Minor": 1 + }, + "Owner": "Multipass", + "ShouldTerminateOnLastHandleClosed": false, + "VirtualMachine": { + "Chipset": { + "Uefi": { + "BootThis": { + "DevicePath": "Primary disk", + "DiskNumber": 0, + "DeviceType": "ScsiDrive" + }, + "Console": "ComPort1" + } + }, + "ComputeTopology": { + "Memory": { + "Backing": "Virtual", + "SizeInMB": 16384 + }, + "Processor": { + "Count": 8 + } + }, + "Devices": { + "ComPorts": { + "0": { + "NamedPipe": "\\\\.\\pipe\\test_vm" + } + }, + "Scsi": { + "cloud-init iso file": { + "Attachments": { + "0": { + "Type": "Iso", + "Path": "cloudinit iso path", + "ReadOnly": true + } + } + }, + "Primary disk": { + "Attachments": { + "0": { + "Type": "VirtualDisk", + "Path": "virtual disk path", + "ReadOnly": false + } + } + }, + } + } + } + })"; + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = mock_error_msg; + }, + Return(E_POINTER))); + + EXPECT_CALL(mock_create_compute_system, Call) + .WillOnce(DoAll( + [](PCWSTR id, + PCWSTR configuration, + HCS_OPERATION operation, + const SECURITY_DESCRIPTOR* securityDescriptor, + HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(L"test_vm", id); + + const auto config_no_whitespace = trim_whitespace(configuration); + const auto expected_no_whitespace = trim_whitespace(expected_vm_settings_json); + + ASSERT_STREQ(expected_no_whitespace.c_str(), config_no_whitespace.c_str()); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, securityDescriptor); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_error_msg); }, Return(nullptr))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test_vm"; + params.cloudinit_iso_path = "cloudinit iso path"; + params.vhdx_path = "virtual disk path"; + params.memory_size_mb = 16384; + params.processor_count = 8; + + const auto& [status, status_msg] = uut.create_compute_system(params); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), mock_error_msg); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_success) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_grant_vm_access; + + mock_api_table.GrantVmAccess = mock_grant_vm_access.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_grant_vm_access, Call) + .WillOnce(DoAll( + [](PCWSTR vmId, PCWSTR filePath) { + ASSERT_NE(nullptr, vmId); + ASSERT_NE(nullptr, filePath); + ASSERT_STREQ(vmId, L"test_vm"); + ASSERT_STREQ(filePath, L"this is a path"); + }, + Return(NOERROR))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut.grant_vm_access("test_vm", "this is a path"); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_fail) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_grant_vm_access; + + mock_api_table.GrantVmAccess = mock_grant_vm_access.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_grant_vm_access, Call) + .WillOnce(DoAll( + [](PCWSTR vmId, PCWSTR filePath) { + ASSERT_NE(nullptr, vmId); + ASSERT_NE(nullptr, filePath); + ASSERT_STREQ(vmId, L"test_vm"); + ASSERT_STREQ(filePath, L"this is a path"); + }, + Return(E_POINTER))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut.grant_vm_access("test_vm", "this is a path"); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"GrantVmAccess failed!"); + } +} + +// --------------------------------------------------------- + +/** + * Success scenario: Everything goes as expected. + */ +TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_success) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_revoke_vm_access; + + mock_api_table.RevokeVmAccess = mock_revoke_vm_access.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_revoke_vm_access, Call) + .WillOnce(DoAll( + [](PCWSTR vmId, PCWSTR filePath) { + ASSERT_NE(nullptr, vmId); + ASSERT_NE(nullptr, filePath); + ASSERT_STREQ(vmId, L"test_vm"); + ASSERT_STREQ(filePath, L"this is a path"); + }, + Return(NOERROR))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut.revoke_vm_access("test_vm", "this is a path"); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_fail) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_revoke_vm_access; + + mock_api_table.RevokeVmAccess = mock_revoke_vm_access.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_revoke_vm_access, Call) + .WillOnce(DoAll( + [](PCWSTR vmId, PCWSTR filePath) { + ASSERT_NE(nullptr, vmId); + ASSERT_NE(nullptr, filePath); + ASSERT_STREQ(vmId, L"test_vm"); + ASSERT_STREQ(filePath, L"this is a path"); + }, + Return(E_POINTER))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut.revoke_vm_access("test_vm", "this is a path"); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"RevokeVmAccess failed!"); + } +} + +// --------------------------------------------------------- + +// +// Below are the skeleton test cases for the functions that are following +// the same pattern. +// + +template +void HyperVHCSAPI_UnitTests::generic_operation_happy_path(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR operation_result_document, + PWSTR expected_status_msg) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_open_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_target_function; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.OpenComputeSystem = mock_open_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + target_api_function = mock_target_function.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [operation_result_document](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = operation_result_document; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_open_compute_system, Call) + .WillOnce(DoAll( + [&](PCWSTR id, DWORD requestedAccess, HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(id, L"test_vm"); + ASSERT_EQ(requestedAccess, GENERIC_ALL); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_target_function, Call).WillOnce(DoAll(mock_callback, Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + if (operation_result_document) + { + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([operation_result_document](HLOCAL ptr) { ASSERT_EQ(operation_result_document, ptr); }, + Return(nullptr))); + } + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut_callback(uut); + ASSERT_TRUE(status); + + if (nullptr == expected_status_msg) + { + ASSERT_TRUE(status_msg.empty()); + } + else + { + ASSERT_STREQ(status_msg.c_str(), expected_status_msg); + } + } +} + +template +void HyperVHCSAPI_UnitTests::generic_operation_hcs_open_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + PWSTR expected_status_msg) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + + ::testing::MockFunction mock_open_compute_system; + + mock_api_table.OpenComputeSystem = mock_open_compute_system.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + + EXPECT_CALL(mock_open_compute_system, Call) + .WillOnce(DoAll( + [&](PCWSTR id, DWORD requestedAccess, HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(id, L"test_vm"); + ASSERT_EQ(requestedAccess, GENERIC_ALL); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + }, + Return(E_POINTER))); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut_callback(uut); + ASSERT_FALSE(status); + + if (nullptr == expected_status_msg) + { + ASSERT_TRUE(status_msg.empty()); + } + else + { + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), expected_status_msg); + } + } +} + +template +void HyperVHCSAPI_UnitTests::generic_operation_create_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + PWSTR expected_status_msg) +{ + + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_open_compute_system; + ::testing::MockFunction mock_close_compute_system; + + mock_api_table.OpenComputeSystem = mock_open_compute_system.AsStdFunction(); + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + + EXPECT_CALL(mock_open_compute_system, Call) + .WillOnce(DoAll( + [&](PCWSTR id, DWORD requestedAccess, HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(id, L"test_vm"); + ASSERT_EQ(requestedAccess, GENERIC_ALL); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(nullptr))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut_callback(uut); + ASSERT_FALSE(status); + + if (nullptr == expected_status_msg) + { + ASSERT_TRUE(status_msg.empty()); + } + else + { + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), expected_status_msg); + } + } +} + +template +void HyperVHCSAPI_UnitTests::generic_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR expected_status_msg) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_open_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_target_function; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.OpenComputeSystem = mock_open_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + target_api_function = mock_target_function.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_open_compute_system, Call) + .WillOnce(DoAll( + [&](PCWSTR id, DWORD requestedAccess, HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(id, L"test_vm"); + ASSERT_EQ(requestedAccess, GENERIC_ALL); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_target_function, Call).WillOnce(DoAll(mock_callback, Return(E_POINTER))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut_callback(uut); + ASSERT_FALSE(status); + if (nullptr == expected_status_msg) + { + ASSERT_TRUE(status_msg.empty()); + } + else + { + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), expected_status_msg); + } + } +} + +template +void HyperVHCSAPI_UnitTests::generic_operation_wait_for_operation_fail(ApiFnT& target_api_function, + UutCallableT uut_callback, + MockCallableT mock_callback, + PWSTR operation_result_document, + PWSTR expected_status_msg) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_operation; + ::testing::MockFunction mock_close_operation; + ::testing::MockFunction mock_wait_for_operation_result; + ::testing::MockFunction mock_open_compute_system; + ::testing::MockFunction mock_close_compute_system; + ::testing::MockFunction mock_target_function; + ::testing::MockFunction mock_local_free; + + mock_api_table.CreateOperation = mock_create_operation.AsStdFunction(); + mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); + mock_api_table.WaitForOperationResult = mock_wait_for_operation_result.AsStdFunction(); + mock_api_table.OpenComputeSystem = mock_open_compute_system.AsStdFunction(); + mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); + target_api_function = mock_target_function.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_operation, Call) + .WillOnce(DoAll( + [](const void* context, HCS_OPERATION_COMPLETION callback) { + ASSERT_EQ(nullptr, context); + ASSERT_EQ(nullptr, callback); + }, + Return(mock_operation_object))); + + EXPECT_CALL(mock_close_operation, Call).WillOnce([](HCS_OPERATION op) { + ASSERT_EQ(op, mock_operation_object); + }); + + EXPECT_CALL(mock_wait_for_operation_result, Call) + .WillOnce(DoAll( + [operation_result_document](HCS_OPERATION operation, DWORD timeoutMs, PWSTR* resultDocument) { + ASSERT_EQ(operation, mock_operation_object); + ASSERT_EQ(timeoutMs, 240000); + ASSERT_NE(nullptr, resultDocument); + ASSERT_EQ(nullptr, *resultDocument); + *resultDocument = operation_result_document; + }, + Return(E_POINTER))); + + EXPECT_CALL(mock_open_compute_system, Call) + .WillOnce(DoAll( + [&](PCWSTR id, DWORD requestedAccess, HCS_SYSTEM* computeSystem) { + ASSERT_STREQ(id, L"test_vm"); + ASSERT_EQ(requestedAccess, GENERIC_ALL); + ASSERT_NE(nullptr, computeSystem); + ASSERT_EQ(nullptr, *computeSystem); + *computeSystem = mock_compute_system_object; + }, + Return(NOERROR))); + + EXPECT_CALL(mock_target_function, Call).WillOnce(DoAll(mock_callback, Return(NOERROR))); + + EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + }); + + if (operation_result_document) + { + mock_api_table.LocalFree = mock_local_free.AsStdFunction(); + EXPECT_CALL(mock_local_free, Call) + .WillOnce(DoAll([operation_result_document](HLOCAL ptr) { ASSERT_EQ(operation_result_document, ptr); }, + Return(nullptr))); + } + } + + /****************************************************** + * Verify the expected outcome. + ******************************************************/ + { + uut_t uut{mock_api_table}; + + const auto& [status, status_msg] = uut_callback(uut); + ASSERT_FALSE(status); + + if (nullptr == expected_status_msg) + { + ASSERT_TRUE(status_msg.empty()); + } + else + { + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), expected_status_msg); + } + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_happy_path) +{ + generic_operation_happy_path( + mock_api_table.StartComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.StartComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_create_operation_fail) +{ + + generic_operation_create_operation_fail( + mock_api_table.StartComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_fail) +{ + generic_operation_fail( + mock_api_table.StartComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_wait_for_operation_result_fail) +{ + generic_operation_wait_for_operation_fail( + mock_api_table.StartComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_happy_path) +{ + generic_operation_happy_path( + mock_api_table.ShutDownComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.ShutDownComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_create_operation_fail) +{ + + generic_operation_create_operation_fail( + mock_api_table.ShutDownComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_fail) +{ + generic_operation_fail( + mock_api_table.ShutDownComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_wait_for_operation_result_fail) +{ + generic_operation_wait_for_operation_fail( + mock_api_table.ShutDownComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_happy_path) +{ + generic_operation_happy_path( + mock_api_table.TerminateComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.TerminateComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_create_operation_fail) +{ + + generic_operation_create_operation_fail( + mock_api_table.TerminateComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_fail) +{ + generic_operation_fail( + mock_api_table.TerminateComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_wait_for_operation_result_fail) +{ + generic_operation_wait_for_operation_fail( + mock_api_table.TerminateComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_happy_path) +{ + static constexpr wchar_t expected_pause_option[] = LR"( + { + "SuspensionLevel": "Suspend", + "HostedNotification": { + "Reason": "Save" + } + })"; + + generic_operation_happy_path( + mock_api_table.PauseComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(options); + const auto expected_options_no_whitespace = trim_whitespace(expected_pause_option); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.PauseComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_create_operation_fail) +{ + + generic_operation_create_operation_fail( + mock_api_table.PauseComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_fail) +{ + static constexpr wchar_t expected_pause_option[] = LR"( + { + "SuspensionLevel": "Suspend", + "HostedNotification": { + "Reason": "Save" + } + })"; + generic_operation_fail( + mock_api_table.PauseComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(options); + const auto expected_options_no_whitespace = trim_whitespace(expected_pause_option); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_wait_for_operation_result_fail) +{ + static constexpr wchar_t expected_pause_option[] = LR"( + { + "SuspensionLevel": "Suspend", + "HostedNotification": { + "Reason": "Save" + } + })"; + generic_operation_wait_for_operation_fail( + mock_api_table.PauseComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(options); + const auto expected_options_no_whitespace = trim_whitespace(expected_pause_option); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_happy_path) +{ + generic_operation_happy_path( + mock_api_table.ResumeComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.ResumeComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_create_operation_fail) +{ + + generic_operation_create_operation_fail( + mock_api_table.ResumeComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_fail) +{ + generic_operation_fail( + mock_api_table.ResumeComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_wait_for_operation_result_fail) +{ + generic_operation_wait_for_operation_fail( + mock_api_table.ResumeComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(options, nullptr); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_happy_path) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Add", + "Settings": { + "EndpointId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca", + "MacAddress": "00:00:00:00:00:00", + "InstanceId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca" + } + })"; + + generic_operation_happy_path( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + hyperv::hcs::AddEndpointParameters params{}; + params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; + params.nic_mac_address = "00:00:00:00:00:00"; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail(mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + hyperv::hcs::AddEndpointParameters params{}; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_create_operation_fail) +{ + generic_operation_create_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + hyperv::hcs::AddEndpointParameters params{}; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Add", + "Settings": { + "EndpointId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca", + "MacAddress": "00:00:00:00:00:00", + "InstanceId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca" + } + })"; + + generic_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + hyperv::hcs::AddEndpointParameters params{}; + params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; + params.nic_mac_address = "00:00:00:00:00:00"; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_wait_for_operation_result_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Add", + "Settings": { + "EndpointId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca", + "MacAddress": "00:00:00:00:00:00", + "InstanceId": "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca" + } + })"; + + generic_operation_wait_for_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + hyperv::hcs::AddEndpointParameters params{}; + params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; + params.nic_mac_address = "00:00:00:00:00:00"; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_happy_path) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Remove" + })"; + + generic_operation_happy_path( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_create_operation_fail) +{ + generic_operation_create_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Remove" + })"; + + generic_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_wait_for_operation_result_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", + "RequestType": "Remove" + })"; + + generic_operation_wait_for_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); + }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_happy_path) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", + "RequestType": "Update", + "Settings": 16384 + })"; + + generic_operation_happy_path( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_create_operation_fail) +{ + generic_operation_create_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", + "RequestType": "Update", + "Settings": 16384 + })"; + + generic_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_wait_for_operation_result_fail) +{ + constexpr auto expected_modify_compute_system_configuration = LR"( + { + "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", + "RequestType": "Update", + "Settings": 16384 + })"; + + generic_operation_wait_for_operation_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(configuration); + const auto expected_options_no_whitespace = trim_whitespace(expected_modify_compute_system_configuration); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_happy_path) +{ + constexpr auto expected_vm_query = LR"( + { + "PropertyTypes":[] + })"; + + generic_operation_happy_path( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(propertyQuery); + const auto expected_options_no_whitespace = trim_whitespace(expected_vm_query); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_hcs_open_fail) +{ + generic_operation_hcs_open_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_create_operation_fail) +{ + generic_operation_create_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_fail) +{ + constexpr auto expected_vm_query = LR"( + { + "PropertyTypes":[] + })"; + + generic_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(propertyQuery); + const auto expected_options_no_whitespace = trim_whitespace(expected_vm_query); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_wait_for_operation_result_fail) +{ + constexpr auto expected_vm_query = LR"( + { + "PropertyTypes":[] + })"; + + generic_operation_wait_for_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + const auto options_no_whitespace = trim_whitespace(propertyQuery); + const auto expected_options_no_whitespace = trim_whitespace(expected_vm_query); + ASSERT_STREQ(options_no_whitespace.c_str(), expected_options_no_whitespace.c_str()); + }); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_happy_path) +{ + static wchar_t result_doc[21] = L"{\"State\": \"Running\"}"; + static wchar_t expected_state[8] = L"Running"; + + generic_operation_happy_path( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(propertyQuery, nullptr); + }, + result_doc, + expected_state); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_no_state) +{ + static wchar_t result_doc[21] = L"{\"Frodo\": \"Baggins\"}"; + static wchar_t expected_state[8] = L"Unknown"; + + generic_operation_happy_path( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(propertyQuery, nullptr); + }, + result_doc, + expected_state); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_hcs_open_fail) +{ + static wchar_t expected_status_msg[] = L"Unknown"; + generic_operation_hcs_open_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + expected_status_msg); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_create_operation_fail) +{ + static wchar_t expected_status_msg[] = L"Unknown"; + generic_operation_create_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + expected_status_msg); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_fail) +{ + static wchar_t expected_status_msg[] = L"Unknown"; + + generic_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, propertyQuery); + }, + expected_status_msg); +} + +// --------------------------------------------------------- + +TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_wait_for_operation_result_fail) +{ + static wchar_t expected_status_msg[] = L"Unknown"; + + generic_operation_wait_for_operation_fail( + mock_api_table.GetComputeSystemProperties, + [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { + ASSERT_EQ(mock_compute_system_object, computeSystem); + ASSERT_EQ(mock_operation_object, operation); + ASSERT_EQ(nullptr, propertyQuery); + }, + nullptr, + expected_status_msg); +} + +} // namespace multipass::test \ No newline at end of file From 51eeeb8360f5e6ae5ea8fd7172889ef47d395b82 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Wed, 12 Feb 2025 18:41:25 +0300 Subject: [PATCH 04/26] [hyperv-hcs]: add logging assertions to the unit test cases --- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 62 +++- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 326 +++++++++++++++--- 2 files changed, 333 insertions(+), 55 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 2ce156ffad..e8d75d98fc 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -21,16 +21,15 @@ #include "hyperv_hcs_add_endpoint_params.h" #include "hyperv_hcs_api_table.h" #include "hyperv_hcs_create_compute_system_params.h" -#include "multipass/format.h" #include +#include + #include #include #include -#include - #include #include @@ -108,15 +107,13 @@ static auto wait_for_operation_result(const HCSAPITable& api, */ static auto open_host_compute_system(const HCSAPITable& api, const std::string& name) { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("open_host_compute_system(...) > name: ({}), ", name)); + logging::log(logging::Level::trace, kLogCategory, fmt::format("open_host_compute_system(...) > name: ({})", name)); HCS_SYSTEM system{nullptr}; const auto name_w = string_to_wstring(name); constexpr auto kRequestedAccessLevel = GENERIC_ALL; - const auto result = api.OpenComputeSystem(name_w.c_str(), kRequestedAccessLevel, &system); + const auto result = ResultCode{api.OpenComputeSystem(name_w.c_str(), kRequestedAccessLevel, &system)}; - if (FAILED(result)) + if (!result) { logging::log( logging::Level::error, @@ -161,6 +158,10 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == system) { + logging::log( + logging::Level::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", target_hcs_system_name)); return OperationResult{E_POINTER, L"HcsOpenComputeSystem failed!"}; } @@ -168,6 +169,9 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == operation) { + logging::log(logging::Level::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > HcsCreateOperation failed! {}", target_hcs_system_name)); return OperationResult{E_POINTER, L"HcsCreateOperation failed!"}; } @@ -175,6 +179,9 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, if (FAILED(result)) { + logging::log(logging::Level::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > Operation failed! {}", target_hcs_system_name)); return OperationResult{result, L"HCS operation failed!"}; } @@ -314,6 +321,9 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("start_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); } @@ -321,6 +331,9 @@ OperationResult HCSWrapper::start_compute_system(const std::string& compute_syst OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("shutdown_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); } @@ -328,6 +341,9 @@ OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_s OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("terminate_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); } @@ -335,6 +351,9 @@ OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("pause_compute_system(...) > name: ({})", compute_system_name)); static constexpr wchar_t c_pauseOption[] = LR"( { "SuspensionLevel": "Suspend", @@ -349,6 +368,9 @@ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_syst OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("resume_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); } @@ -356,6 +378,7 @@ OperationResult HCSWrapper::resume_compute_system(const std::string& compute_sys OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) { + logging::log(logging::Level::trace, kLogCategory, fmt::format("add_endpoint(...) > params: {}", params)); constexpr auto add_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -382,6 +405,11 @@ OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) { + logging::log( + logging::Level::trace, + kLogCategory, + fmt::format("remove_endpoint(...) > name: ({}), endpoint_guid: ({})", compute_system_name, endpoint_guid)); + constexpr auto remove_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -402,6 +430,9 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib)); constexpr auto resize_memory_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", @@ -423,6 +454,10 @@ OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("get_compute_system_properties(...) > name: ({})", compute_system_name)); + // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType static constexpr wchar_t c_VmQuery[] = LR"( { @@ -437,6 +472,10 @@ OperationResult HCSWrapper::get_compute_system_properties(const std::string& com OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) { + logging::log( + logging::Level::trace, + kLogCategory, + fmt::format("grant_vm_access(...) > name: ({}), file_path: ({})", compute_system_name, file_path.string())); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); const auto result = api.GrantVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); @@ -448,6 +487,10 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) { + logging::log( + logging::Level::trace, + kLogCategory, + fmt::format("revoke_vm_access(...) > name: ({}), file_path: ({}) ", compute_system_name, file_path.string())); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); const auto result = api.RevokeVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); @@ -458,6 +501,9 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) { + logging::log(logging::Level::trace, + kLogCategory, + fmt::format("get_compute_system_state(...) > name: ({})", compute_system_name)); const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp index cce1d5ae95..4dc971db5b 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -305,6 +305,14 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_happy_path) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " + "Memory size: (16384 MiB) | cloud-init ISO path: (cloudinit iso path) | VHDX path: (virtual disk path)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -454,6 +462,14 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " + "Memory size: (16384 MiB) | cloud-init ISO path: () | VHDX path: (virtual disk path)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -603,6 +619,14 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_vhdx) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " + "Memory size: (16384 MiB) | cloud-init ISO path: (cloudinit iso path) | VHDX path: ()"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -743,6 +767,14 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit_and_vhdx) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " + "Memory size: (16384 MiB) | cloud-init ISO path: () | VHDX path: ()"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -790,6 +822,10 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_create_operation_fail) ASSERT_EQ(nullptr, callback); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); } /****************************************************** @@ -923,6 +959,10 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_fail) ASSERT_EQ(nullptr, *computeSystem); }, Return(E_POINTER))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); } /****************************************************** @@ -1081,6 +1121,11 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wait_for_operation_fail) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_error_msg); }, Return(nullptr))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -1130,6 +1175,10 @@ TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_success) ASSERT_STREQ(filePath, L"this is a path"); }, Return(NOERROR))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "grant_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } /****************************************************** @@ -1169,6 +1218,10 @@ TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_fail) ASSERT_STREQ(filePath, L"this is a path"); }, Return(E_POINTER))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "grant_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } /****************************************************** @@ -1212,6 +1265,10 @@ TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_success) ASSERT_STREQ(filePath, L"this is a path"); }, Return(NOERROR))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "revoke_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } /****************************************************** @@ -1251,6 +1308,9 @@ TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_fail) ASSERT_STREQ(filePath, L"this is a path"); }, Return(E_POINTER))); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "revoke_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } /****************************************************** @@ -1350,6 +1410,12 @@ void HyperVHCSAPI_UnitTests::generic_operation_happy_path(ApiFnT& target_api_fun .WillOnce(DoAll([operation_result_document](HLOCAL ptr) { ASSERT_EQ(operation_result_document, ptr); }, Return(nullptr))); } + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_hcs_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -1400,6 +1466,14 @@ void HyperVHCSAPI_UnitTests::generic_operation_hcs_open_fail(ApiFnT& target_api_ ASSERT_EQ(nullptr, *computeSystem); }, Return(E_POINTER))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log( + mpl::Level::error, + "open_host_compute_system(...) > failed to open (test_vm), result code: (0x80004003)"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "perform_hcs_operation(...) > HcsOpenComputeSystem failed!"); } /****************************************************** @@ -1468,6 +1542,12 @@ void HyperVHCSAPI_UnitTests::generic_operation_create_operation_fail(ApiFnT& tar EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { ASSERT_EQ(mock_compute_system_object, computeSystem); }); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "perform_hcs_operation(...) > HcsCreateOperation failed!"); } /****************************************************** @@ -1545,6 +1625,11 @@ void HyperVHCSAPI_UnitTests::generic_operation_fail(ApiFnT& target_api_function, EXPECT_CALL(mock_close_compute_system, Call).WillOnce([](HCS_SYSTEM computeSystem) { ASSERT_EQ(mock_compute_system_object, computeSystem); }); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::error, "perform_hcs_operation(...) > Operation failed!"); } /****************************************************** @@ -1644,6 +1729,12 @@ void HyperVHCSAPI_UnitTests::generic_operation_wait_for_operation_fail(ApiFnT& t .WillOnce(DoAll([operation_result_document](HLOCAL ptr) { ASSERT_EQ(operation_result_document, ptr); }, Return(nullptr))); } + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_hcs_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); } /****************************************************** @@ -1673,7 +1764,10 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_happy_path) { generic_operation_happy_path( mock_api_table.StartComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...) > name: (test_vm)"); + return wrapper.start_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1687,7 +1781,10 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.StartComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + return wrapper.start_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1697,7 +1794,10 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.StartComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + return wrapper.start_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1706,7 +1806,10 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_fail) { generic_operation_fail( mock_api_table.StartComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + return wrapper.start_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1720,7 +1823,10 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_wait_for_operation_result_fa { generic_operation_wait_for_operation_fail( mock_api_table.StartComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.start_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + return wrapper.start_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1734,7 +1840,10 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_happy_path) { generic_operation_happy_path( mock_api_table.ShutDownComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...) > name: (test_vm)"); + return wrapper.shutdown_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1748,7 +1857,10 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.ShutDownComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + return wrapper.shutdown_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1758,7 +1870,10 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.ShutDownComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + return wrapper.shutdown_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1767,7 +1882,10 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_fail) { generic_operation_fail( mock_api_table.ShutDownComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + return wrapper.shutdown_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1781,7 +1899,10 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_wait_for_operation_result { generic_operation_wait_for_operation_fail( mock_api_table.ShutDownComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.shutdown_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + return wrapper.shutdown_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1795,7 +1916,10 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_happy_path) { generic_operation_happy_path( mock_api_table.TerminateComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...) > name: (test_vm)"); + return wrapper.terminate_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1809,7 +1933,10 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.TerminateComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + return wrapper.terminate_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1819,7 +1946,10 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.TerminateComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + return wrapper.terminate_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1828,7 +1958,10 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_fail) { generic_operation_fail( mock_api_table.TerminateComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + return wrapper.terminate_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1842,7 +1975,10 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_wait_for_operation_resul { generic_operation_wait_for_operation_fail( mock_api_table.TerminateComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.terminate_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + return wrapper.terminate_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1864,7 +2000,10 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_happy_path) generic_operation_happy_path( mock_api_table.PauseComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...) > name: (test_vm)"); + return wrapper.pause_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1880,7 +2019,10 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.PauseComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + return wrapper.pause_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1890,7 +2032,10 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.PauseComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + return wrapper.pause_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1906,7 +2051,10 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_fail) })"; generic_operation_fail( mock_api_table.PauseComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + return wrapper.pause_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1929,7 +2077,10 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_wait_for_operation_result_fa })"; generic_operation_wait_for_operation_fail( mock_api_table.PauseComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.pause_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + return wrapper.pause_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1945,7 +2096,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_happy_path) { generic_operation_happy_path( mock_api_table.ResumeComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...) > name: (test_vm)"); + return wrapper.resume_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1959,7 +2113,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.ResumeComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + return wrapper.resume_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1969,7 +2126,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.ResumeComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + return wrapper.resume_compute_system("test_vm"); + }); } // --------------------------------------------------------- @@ -1978,7 +2138,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_fail) { generic_operation_fail( mock_api_table.ResumeComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + return wrapper.resume_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -1992,7 +2155,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_wait_for_operation_result_f { generic_operation_wait_for_operation_fail( mock_api_table.ResumeComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resume_compute_system("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + return wrapper.resume_compute_system("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2018,6 +2184,10 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "add_endpoint(...) > params: Host Compute System Name: (test_vm) | Endpoint GUID: " + "(288cc1ac-8f31-4a09-9e90-30ad0bcfdbca) | NIC MAC Address: (00:00:00:00:00:00)"); hyperv::hcs::AddEndpointParameters params{}; params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; params.nic_mac_address = "00:00:00:00:00:00"; @@ -2037,12 +2207,14 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_happy_path) TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_hcs_open_fail) { - generic_operation_hcs_open_fail(mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { - hyperv::hcs::AddEndpointParameters params{}; - params.target_compute_system_name = "test_vm"; - return wrapper.add_endpoint(params); - }); + generic_operation_hcs_open_fail( + mock_api_table.ModifyComputeSystem, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); + hyperv::hcs::AddEndpointParameters params{}; + params.target_compute_system_name = "test_vm"; + return wrapper.add_endpoint(params); + }); } // --------------------------------------------------------- @@ -2052,6 +2224,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_create_operation_f generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.target_compute_system_name = "test_vm"; return wrapper.add_endpoint(params); @@ -2076,6 +2249,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; params.nic_mac_address = "00:00:00:00:00:00"; @@ -2109,6 +2283,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_wait_for_operation generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; params.nic_mac_address = "00:00:00:00:00:00"; @@ -2137,6 +2312,9 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "remove_endpoint(...) > name: (test_vm), endpoint_guid: (288cc1ac-8f31-4a09-9e90-30ad0bcfdbca)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2155,6 +2333,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_hcs_open_fail generic_operation_hcs_open_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }); } @@ -2166,6 +2345,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_create_operat generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }); } @@ -2183,6 +2363,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2207,6 +2388,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_wait_for_oper generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2231,7 +2413,11 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "resize_memory(...) > name: (test_vm), new_size_mb: (16384)"); + return wrapper.resize_memory("test_vm", 16384); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2247,7 +2433,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + return wrapper.resize_memory("test_vm", 16384); + }); } // --------------------------------------------------------- @@ -2256,7 +2445,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_create_operation_ { generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + return wrapper.resize_memory("test_vm", 16384); + }); } // --------------------------------------------------------- @@ -2272,7 +2464,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + return wrapper.resize_memory("test_vm", 16384); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2295,7 +2490,10 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_wait_for_operatio generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.resize_memory("test_vm", 16384); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + return wrapper.resize_memory("test_vm", 16384); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2316,7 +2514,11 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_happy_path) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, + "get_compute_system_properties(...) > name: (test_vm)"); + return wrapper.get_compute_system_properties("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2332,7 +2534,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_hcs_open_fail) { generic_operation_hcs_open_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + return wrapper.get_compute_system_properties("test_vm"); + }); } // --------------------------------------------------------- @@ -2341,7 +2546,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_create_operation_fa { generic_operation_create_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }); + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + return wrapper.get_compute_system_properties("test_vm"); + }); } // --------------------------------------------------------- @@ -2355,7 +2563,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_fail) generic_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + return wrapper.get_compute_system_properties("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2376,7 +2587,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_wait_for_operation_ generic_operation_wait_for_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_properties("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + return wrapper.get_compute_system_properties("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2395,7 +2609,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_happy_path) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...) > name: (test_vm)"); + return wrapper.get_compute_system_state("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2414,7 +2631,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_no_state) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + return wrapper.get_compute_system_state("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2431,7 +2651,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_hcs_open_fail) static wchar_t expected_status_msg[] = L"Unknown"; generic_operation_hcs_open_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + return wrapper.get_compute_system_state("test_vm"); + }, expected_status_msg); } @@ -2442,7 +2665,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_create_operation_fail) static wchar_t expected_status_msg[] = L"Unknown"; generic_operation_create_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + return wrapper.get_compute_system_state("test_vm"); + }, expected_status_msg); } @@ -2454,7 +2680,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_fail) generic_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + return wrapper.get_compute_system_state("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); @@ -2471,7 +2700,10 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_wait_for_operation_resul generic_operation_wait_for_operation_fail( mock_api_table.GetComputeSystemProperties, - [&](hyperv::hcs::HCSWrapper& wrapper) { return wrapper.get_compute_system_state("test_vm"); }, + [&](hyperv::hcs::HCSWrapper& wrapper) { + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + return wrapper.get_compute_system_state("test_vm"); + }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { ASSERT_EQ(mock_compute_system_object, computeSystem); ASSERT_EQ(mock_operation_object, operation); From c02a4025c3ad3245940f43c5d0d751c2359895d6 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Wed, 12 Feb 2025 23:09:16 +0300 Subject: [PATCH 05/26] [hyperv-hcn-hcs] overall grooming & polishing --- .../hyperv_api/hcn/hyperv_hcn_api_table.h | 2 +- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 83 +++--- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.h | 10 +- .../hcn/hyperv_hcn_wrapper_interface.h | 8 +- .../hcs/hyperv_hcs_add_endpoint_params.h | 4 +- .../hyperv_api/hcs/hyperv_hcs_api_table.h | 8 +- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 269 +++++++++--------- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.h | 38 +-- .../hcs/hyperv_hcs_compute_system_state.h | 6 + .../hcs/hyperv_hcs_wrapper_interface.h | 30 +- .../backends/hyperv_api/hyperv_api_common.cpp | 56 +--- .../backends/hyperv_api/hyperv_api_common.h | 48 ++-- tests/hyperv_api/CMakeLists.txt | 15 + tests/hyperv_api/test_it_hyperv_hcs_api.cpp | 12 +- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 6 +- 15 files changed, 290 insertions(+), 305 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index 34a309c687..ec0e23cdea 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -32,7 +32,7 @@ namespace multipass::hyperv::hcn { /** - * API function table for host compute network + * API function table for the Host Compute Network API */ struct HCNAPITable { diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 612242e371..444ae07f17 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -46,16 +46,22 @@ namespace multipass::hyperv::hcn { +namespace +{ + using UniqueHcnNetwork = std::unique_ptr, decltype(HCNAPITable::CloseNetwork)>; using UniqueHcnEndpoint = std::unique_ptr, decltype(HCNAPITable::CloseEndpoint)>; using UniqueCotaskmemString = std::unique_ptr; +namespace mpl = logging; +using lvl = mpl::Level; + // --------------------------------------------------------- /** * Category for the log messages. */ -static constexpr const char* kLogCategory = "HyperV-HCN-Wrapper"; +constexpr auto kLogCategory = "HyperV-HCN-Wrapper"; // --------------------------------------------------------- @@ -124,7 +130,7 @@ constexpr auto endpoint_settings_template = LR"( * @return HCNOperationResult Result of the performed operation */ template -static OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, Args&&... args) +OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, Args&&... args) { // Ensure that function to call is set. @@ -145,15 +151,15 @@ static OperationResult perform_hcn_operation(const HCNAPITable& api, const FnTyp // HcnClose*) is ErrorRecord, which is a JSON-formatted document emitted by // the API describing the error happened. Therefore, we can streamline all API // calls through perform_operation to perform co - const ResultCode result = fn(std::forward(args)..., &result_msg_out); + const auto result = ResultCode{fn(std::forward(args)..., &result_msg_out)}; UniqueCotaskmemString result_msgbuf{result_msg_out, api.CoTaskMemFree}; - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("perform_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result))); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("perform_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result))); // Error message is only valid when the operation resulted in an error. // Passing a nullptr is well-defined in "< C++23", but it's going to be @@ -174,32 +180,33 @@ static OperationResult perform_hcn_operation(const HCNAPITable& api, const FnTyp * * @return UniqueHcnNetwork Unique handle to the network. Non-nullptr when successful. */ -static UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) +UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("open_network(...) > network_guid: {} ", network_guid)); + mpl::log(lvl::trace, kLogCategory, fmt::format("open_network(...) > network_guid: {} ", network_guid)); HCN_NETWORK network{nullptr}; const auto result = perform_hcn_operation(api, api.OpenNetwork, guid_from_string(network_guid), &network); - (void)result; + if (!result) + { + mpl::log(lvl::error, kLogCategory, fmt::format("open_network() > HcnOpenNetwork failed with {}!", result.code)); + } return UniqueHcnNetwork{network, api.CloseNetwork}; } +} // namespace + // --------------------------------------------------------- HCNWrapper::HCNWrapper(const HCNAPITable& api_table) : api{api_table} { - logging::log(logging::Level::trace, kLogCategory, fmt::format("HCNWrapper::HCNWrapper(...): api_table: {}", api)); + mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::HCNWrapper(...): api_table: {}", api)); } // --------------------------------------------------------- -auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> OperationResult +OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); + mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); // Render the template const auto network_settings = fmt::format(network_settings_template, @@ -217,9 +224,9 @@ auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> Operat if (!result) { // FIXME: Also include the result error message, if any. - logging::log(logging::Level::warning, - kLogCategory, - fmt::format("HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}.", result.code)); + mpl::log(lvl::error, + kLogCategory, + fmt::format("HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}!", result.code)); } [[maybe_unused]] UniqueHcnNetwork _{network, api.CloseNetwork}; @@ -228,23 +235,19 @@ auto HCNWrapper::create_network(const CreateNetworkParameters& params) -> Operat // --------------------------------------------------------- -auto HCNWrapper::delete_network(const std::string& network_guid) -> OperationResult +OperationResult HCNWrapper::delete_network(const std::string& network_guid) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("HCNWrapper::delete_network(...) > network_guid: {}", network_guid)); + mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::delete_network(...) > network_guid: {}", network_guid)); return perform_hcn_operation(api, api.DeleteNetwork, guid_from_string(network_guid)); } // --------------------------------------------------------- -auto HCNWrapper::create_endpoint(const CreateEndpointParameters& params) -> OperationResult +OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& params) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("HCNWrapper::create_endpoint(...) > params: {} ", params)); + mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::create_endpoint(...) > params: {} ", params)); - auto network = open_network(api, params.network_guid); + const auto network = open_network(api, params.network_guid); if (nullptr == network) { @@ -257,23 +260,23 @@ auto HCNWrapper::create_endpoint(const CreateEndpointParameters& params) -> Oper string_to_wstring(params.vm_creator_id), string_to_wstring(params.endpoint_ipvx_addr)); HCN_ENDPOINT endpoint{nullptr}; - auto result = perform_hcn_operation(api, - api.CreateEndpoint, - network.get(), - guid_from_string(params.endpoint_guid), - endpoint_settings.c_str(), - &endpoint); + const auto result = perform_hcn_operation(api, + api.CreateEndpoint, + network.get(), + guid_from_string(params.endpoint_guid), + endpoint_settings.c_str(), + &endpoint); [[maybe_unused]] UniqueHcnEndpoint _{endpoint, api.CloseEndpoint}; return result; } // --------------------------------------------------------- -auto HCNWrapper::delete_endpoint(const std::string& endpoint_guid) -> OperationResult +OperationResult HCNWrapper::delete_endpoint(const std::string& endpoint_guid) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid)); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid)); return perform_hcn_operation(api, api.DeleteEndpoint, guid_from_string(endpoint_guid)); } diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h index d04301ce64..2272aeb9e9 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h @@ -42,6 +42,8 @@ struct HCNWrapper : public HCNWrapperInterface HCNWrapper(const HCNAPITable& api_table = {}); HCNWrapper(const HCNWrapper&) = default; HCNWrapper(HCNWrapper&&) = default; + HCNWrapper& operator=(const HCNWrapper&) = delete; + HCNWrapper& operator=(HCNWrapper&&) = delete; /** * Create a new Host Compute Network @@ -51,7 +53,7 @@ struct HCNWrapper : public HCNWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult create_network(const CreateNetworkParameters& params) override; + [[nodiscard]] OperationResult create_network(const CreateNetworkParameters& params) const override; /** * Delete an existing Host Compute Network @@ -61,7 +63,7 @@ struct HCNWrapper : public HCNWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult delete_network(const std::string& network_guid) override; + [[nodiscard]] OperationResult delete_network(const std::string& network_guid) const override; /** * Create a new Host Compute Network Endpoint @@ -71,7 +73,7 @@ struct HCNWrapper : public HCNWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult create_endpoint(const CreateEndpointParameters& params) override; + [[nodiscard]] OperationResult create_endpoint(const CreateEndpointParameters& params) const override; /** * Delete an existing Host Compute Network Endpoint @@ -81,7 +83,7 @@ struct HCNWrapper : public HCNWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult delete_endpoint(const std::string& endpoint_guid) override; + [[nodiscard]] OperationResult delete_endpoint(const std::string& endpoint_guid) const override; private: const HCNAPITable api; diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h index e90e3b272b..4ec4aef464 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h @@ -31,10 +31,10 @@ namespace multipass::hyperv::hcn */ struct HCNWrapperInterface { - virtual OperationResult create_network(const struct CreateNetworkParameters& params) = 0; - virtual OperationResult delete_network(const std::string& network_guid) = 0; - virtual OperationResult create_endpoint(const struct CreateEndpointParameters& params) = 0; - virtual OperationResult delete_endpoint(const std::string& endpoint_guid) = 0; + [[nodiscard]] virtual OperationResult create_network(const struct CreateNetworkParameters& params) const = 0; + [[nodiscard]] virtual OperationResult delete_network(const std::string& network_guid) const = 0; + [[nodiscard]] virtual OperationResult create_endpoint(const struct CreateEndpointParameters& params) const = 0; + [[nodiscard]] virtual OperationResult delete_endpoint(const std::string& endpoint_guid) const = 0; virtual ~HCNWrapperInterface() = default; }; } // namespace multipass::hyperv::hcn diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h index 9f316c689f..8e4c83d320 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h @@ -37,8 +37,6 @@ struct AddEndpointParameters /** * GUID of the endpoint to add. - * - * Must be already existing. */ std::string endpoint_guid; @@ -65,7 +63,7 @@ struct fmt::formatter auto format(const multipass::hyperv::hcs::AddEndpointParameters& params, FormatContext& ctx) const { return format_to(ctx.out(), - "Host Compute System Name: ({}) | Endpoint GUID: ({}) | NIC MAC Address: ({}) ", + "Host Compute System Name: ({}) | Endpoint GUID: ({}) | NIC MAC Address: ({})", params.target_compute_system_name, params.endpoint_guid, params.nic_mac_address); diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index c7990b0f3c..08e9c1c5b7 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -19,9 +19,9 @@ #define MULTIPASS_HYPERV_API_HCS_API_TABLE // clang-format off - // (xmkg): clang-format is messing with the include order. - #include - #include +// (xmkg): clang-format is messing with the include order. +#include +#include // clang-format on #include @@ -72,7 +72,7 @@ struct HCSAPITable /** * @brief LocalAlloc/LocalFree is used by the HCS API to manage memory for the status/error - * messages. It's caller's responsibility to free the messages allocated by the API, that's + * messages. It's caller's responsibility to free the messages allocated by the API, that's * why the LocalFree is part of the API table. * * @ref https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-localfree diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index e8d75d98fc..075af0e02c 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -36,15 +36,26 @@ namespace multipass::hyperv::hcs { -/** - * Category for the log messages. - */ -static constexpr const char* kLogCategory = "HyperV-HCS-Wrapper"; +namespace +{ using UniqueHcsSystem = std::unique_ptr, decltype(HCSAPITable::CloseComputeSystem)>; using UniqueHcsOperation = std::unique_ptr, decltype(HCSAPITable::CloseOperation)>; using UniqueHlocalString = std::unique_ptr; +namespace mpl = logging; +using lvl = mpl::Level; + +/** + * Category for the log messages. + */ +constexpr auto kLogCategory = "HyperV-HCS-Wrapper"; + +/** + * Default timeout value for HCS API operations + */ +constexpr auto kDefaultOperationTimeout = std::chrono::seconds{240}; + // --------------------------------------------------------- /** @@ -54,9 +65,9 @@ using UniqueHlocalString = std::unique_ptr UniqueHcsOperation +UniqueHcsOperation create_operation(const HCSAPITable& api) { - logging::log(logging::Level::trace, kLogCategory, fmt::format("create_operation(...)")); + mpl::log(lvl::trace, kLogCategory, fmt::format("create_operation(...)")); return UniqueHcsOperation{api.CreateOperation(nullptr, nullptr), api.CloseOperation}; } @@ -66,18 +77,17 @@ static auto create_operation(const HCSAPITable& api) -> UniqueHcsOperation * Wait until given operation completes, or the timeout has reached. * * @param api The HCS API table - * @param op - * @param timeout - * @return auto + * @param op Operation to wait for + * @param timeout Maximum amount of time to wait + * @return Operation result */ -static auto wait_for_operation_result(const HCSAPITable& api, - UniqueHcsOperation op, - std::chrono::milliseconds timeout = std::chrono::milliseconds::max()) +OperationResult wait_for_operation_result(const HCSAPITable& api, + UniqueHcsOperation op, + std::chrono::milliseconds timeout = kDefaultOperationTimeout) { - logging::log( - logging::Level::trace, - kLogCategory, - fmt::format("wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), timeout.count())); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), timeout.count())); wchar_t* result_msg_out{nullptr}; const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg_out); @@ -86,7 +96,7 @@ static auto wait_for_operation_result(const HCSAPITable& api, if (result_msg) { // TODO: Convert from wstring to ascii and log this - // logging::log(logging::Level::trace, + // mpl::log(lvl::trace, // kLogCategory, // fmt::format("wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), // result, result_msg)); @@ -105,20 +115,22 @@ static auto wait_for_operation_result(const HCSAPITable& api, * * @return auto UniqueHcsSystem non-nullptr on success. */ -static auto open_host_compute_system(const HCSAPITable& api, const std::string& name) +UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::string& name) { - logging::log(logging::Level::trace, kLogCategory, fmt::format("open_host_compute_system(...) > name: ({})", name)); - HCS_SYSTEM system{nullptr}; + mpl::log(lvl::trace, kLogCategory, fmt::format("open_host_compute_system(...) > name: ({})", name)); + + // Windows API uses wide strings. const auto name_w = string_to_wstring(name); constexpr auto kRequestedAccessLevel = GENERIC_ALL; + + HCS_SYSTEM system{nullptr}; const auto result = ResultCode{api.OpenComputeSystem(name_w.c_str(), kRequestedAccessLevel, &system)}; if (!result) { - logging::log( - logging::Level::error, - kLogCategory, - fmt::format("open_host_compute_system(...) > failed to open ({}), result code: ({})", name, result)); + mpl::log(lvl::error, + kLogCategory, + fmt::format("open_host_compute_system(...) > failed to open ({}), result code: ({})", name, result)); } return UniqueHcsSystem{system, api.CloseComputeSystem}; } @@ -141,10 +153,10 @@ static auto open_host_compute_system(const HCSAPITable& api, const std::string& * @return HCNOperationResult Result of the performed operation */ template -static OperationResult perform_hcs_operation(const HCSAPITable& api, - const FnType& fn, - const std::string& target_hcs_system_name, - Args&&... args) +OperationResult perform_hcs_operation(const HCSAPITable& api, + const FnType& fn, + const std::string& target_hcs_system_name, + Args&&... args) { // Ensure that function to call is set. if (nullptr == fn) @@ -154,14 +166,13 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, return {E_POINTER, L"Operation function is unbound!"}; } - auto system = open_host_compute_system(api, target_hcs_system_name); + const auto system = open_host_compute_system(api, target_hcs_system_name); if (nullptr == system) { - logging::log( - logging::Level::error, - kLogCategory, - fmt::format("perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", target_hcs_system_name)); + mpl::log(lvl::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", target_hcs_system_name)); return OperationResult{E_POINTER, L"HcsOpenComputeSystem failed!"}; } @@ -169,79 +180,82 @@ static OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == operation) { - logging::log(logging::Level::error, - kLogCategory, - fmt::format("perform_hcs_operation(...) > HcsCreateOperation failed! {}", target_hcs_system_name)); + mpl::log(lvl::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > HcsCreateOperation failed! {}", target_hcs_system_name)); return OperationResult{E_POINTER, L"HcsCreateOperation failed!"}; } - const auto result = fn(system.get(), operation.get(), std::forward(args)...); + const auto result = ResultCode{fn(system.get(), operation.get(), std::forward(args)...)}; - if (FAILED(result)) + if (!result) { - logging::log(logging::Level::error, - kLogCategory, - fmt::format("perform_hcs_operation(...) > Operation failed! {}", target_hcs_system_name)); + mpl::log(lvl::error, + kLogCategory, + fmt::format("perform_hcs_operation(...) > Operation failed! {} Result code {}", + target_hcs_system_name, + result)); return OperationResult{result, L"HCS operation failed!"}; } - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("perform_hcs_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result))); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("perform_hcs_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result))); - return wait_for_operation_result(api, std::move(operation), std::chrono::seconds{240}); + return wait_for_operation_result(api, std::move(operation)); } +} // namespace + // --------------------------------------------------------- HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} { - logging::log(logging::Level::trace, kLogCategory, fmt::format("HCSWrapper::HCSWrapper(...) > api_table: {}", api)); + mpl::log(lvl::trace, kLogCategory, fmt::format("HCSWrapper::HCSWrapper(...) > api_table: {}", api)); } // --------------------------------------------------------- -OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) +OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("HCSWrapper::create_compute_system(...) > params: {} ", params)); - - constexpr auto scsi_device_template = LR"( - "{0}": {{ - "Attachments": {{ - "0": {{ - "Type": "{1}", - "Path": "{2}", - "ReadOnly": {3} + mpl::log(lvl::trace, kLogCategory, fmt::format("HCSWrapper::create_compute_system(...) > params: {} ", params)); + + // Fill the SCSI devices template depending on + // available drives. + const auto scsi_devices = [&]() { + constexpr auto scsi_device_template = LR"( + "{0}": {{ + "Attachments": {{ + "0": {{ + "Type": "{1}", + "Path": "{2}", + "ReadOnly": {3} + }} }} - }} - }}, - )"; - - std::wstring scsi_devices = {}; - - if (!params.cloudinit_iso_path.empty()) - { - auto scsi_device = fmt::format(scsi_device_template, - L"cloud-init iso file", - L"Iso", - string_to_wstring(params.cloudinit_iso_path), - true); - scsi_devices += scsi_device; - } - - if (!params.vhdx_path.empty()) - { - auto scsi_device = fmt::format(scsi_device_template, - L"Primary disk", - L"VirtualDisk", - string_to_wstring(params.vhdx_path), - false); - scsi_devices += scsi_device; - } + }}, + )"; + std::wstring result = {}; + if (!params.cloudinit_iso_path.empty()) + { + result += fmt::format(scsi_device_template, + L"cloud-init iso file", + L"Iso", + string_to_wstring(params.cloudinit_iso_path), + true); + } + + if (!params.vhdx_path.empty()) + { + result += fmt::format(scsi_device_template, + L"Primary disk", + L"VirtualDisk", + string_to_wstring(params.vhdx_path), + false); + } + return result; + }(); // Ideally, we should codegen from the schema // and use that. @@ -303,13 +317,14 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam } const auto name_w = string_to_wstring(params.name); - const auto result = api.CreateComputeSystem(name_w.c_str(), vm_settings.c_str(), operation.get(), nullptr, &system); + const auto result = + ResultCode{api.CreateComputeSystem(name_w.c_str(), vm_settings.c_str(), operation.get(), nullptr, &system)}; // Auto-release the system handle UniqueHcsSystem system_u{system, api.CloseComputeSystem}; (void)system_u; - if (FAILED(result)) + if (!result) { return OperationResult{result, L"HcsCreateComputeSystem failed."}; } @@ -319,41 +334,33 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam // --------------------------------------------------------- -OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) +OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("start_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("start_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); } // --------------------------------------------------------- -OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) +OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("shutdown_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("shutdown_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); } // --------------------------------------------------------- -OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) +OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("terminate_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("terminate_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); } // --------------------------------------------------------- -OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) +OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("pause_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("pause_compute_system(...) > name: ({})", compute_system_name)); static constexpr wchar_t c_pauseOption[] = LR"( { "SuspensionLevel": "Suspend", @@ -366,19 +373,17 @@ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_syst // --------------------------------------------------------- -OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) +OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("resume_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("resume_compute_system(...) > name: ({})", compute_system_name)); return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); } // --------------------------------------------------------- -OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) +OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) const { - logging::log(logging::Level::trace, kLogCategory, fmt::format("add_endpoint(...) > params: {}", params)); + mpl::log(lvl::trace, kLogCategory, fmt::format("add_endpoint(...) > params: {}", params)); constexpr auto add_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -403,12 +408,12 @@ OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) // --------------------------------------------------------- -OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) +OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, + const std::string& endpoint_guid) const { - logging::log( - logging::Level::trace, - kLogCategory, - fmt::format("remove_endpoint(...) > name: ({}), endpoint_guid: ({})", compute_system_name, endpoint_guid)); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("remove_endpoint(...) > name: ({}), endpoint_guid: ({})", compute_system_name, endpoint_guid)); constexpr auto remove_endpoint_settings_template = LR"( {{ @@ -428,11 +433,11 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na // --------------------------------------------------------- -OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) +OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib)); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib)); constexpr auto resize_memory_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", @@ -451,12 +456,12 @@ OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name // --------------------------------------------------------- -OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) +OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("get_compute_system_properties(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, + kLogCategory, + fmt::format("get_compute_system_properties(...) > name: ({})", compute_system_name)); // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType static constexpr wchar_t c_VmQuery[] = LR"( @@ -470,12 +475,13 @@ OperationResult HCSWrapper::get_compute_system_properties(const std::string& com // --------------------------------------------------------- OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) + const std::filesystem::path& file_path) const { - logging::log( - logging::Level::trace, + mpl::log( + lvl::trace, kLogCategory, fmt::format("grant_vm_access(...) > name: ({}), file_path: ({})", compute_system_name, file_path.string())); + const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); const auto result = api.GrantVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); @@ -485,12 +491,13 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na // --------------------------------------------------------- OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) + const std::filesystem::path& file_path) const { - logging::log( - logging::Level::trace, + mpl::log( + lvl::trace, kLogCategory, fmt::format("revoke_vm_access(...) > name: ({}), file_path: ({}) ", compute_system_name, file_path.string())); + const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); const auto result = api.RevokeVmAccess(csname_as_wstring.c_str(), path_as_wstring.c_str()); @@ -499,11 +506,9 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n // --------------------------------------------------------- -OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) +OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) const { - logging::log(logging::Level::trace, - kLogCategory, - fmt::format("get_compute_system_state(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, fmt::format("get_compute_system_state(...) > name: ({})", compute_system_name)); const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h index cc2671c2cb..686860c89c 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h @@ -43,6 +43,8 @@ struct HCSWrapper : public HCSWrapperInterface HCSWrapper(const HCSAPITable& api_table = {}); HCSWrapper(const HCSWrapper&) = default; HCSWrapper(HCSWrapper&&) = default; + HCSWrapper& operator=(const HCSWrapper&) = delete; + HCSWrapper& operator=(HCSWrapper&&) = delete; // --------------------------------------------------------- @@ -54,7 +56,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult create_compute_system(const CreateComputeSystemParameters& params) override; + [[nodiscard]] OperationResult create_compute_system(const CreateComputeSystemParameters& params) const override; // --------------------------------------------------------- @@ -66,7 +68,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult start_compute_system(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult start_compute_system(const std::string& compute_system_name) const override; // --------------------------------------------------------- @@ -78,7 +80,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult shutdown_compute_system(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult shutdown_compute_system(const std::string& compute_system_name) const override; // --------------------------------------------------------- @@ -90,7 +92,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult terminate_compute_system(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult terminate_compute_system(const std::string& compute_system_name) const override; // --------------------------------------------------------- @@ -102,7 +104,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult pause_compute_system(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult pause_compute_system(const std::string& compute_system_name) const override; // --------------------------------------------------------- @@ -114,19 +116,19 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult resume_compute_system(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult resume_compute_system(const std::string& compute_system_name) const override; // --------------------------------------------------------- /** - * + * Retrieve a Host Compute System's properties * * @param [in] compute_system_name Target compute system's name * * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult get_compute_system_properties(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult get_compute_system_properties(const std::string& compute_system_name) const override; // --------------------------------------------------------- @@ -139,8 +141,8 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult grant_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) override; + [[nodiscard]] OperationResult grant_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) const override; // --------------------------------------------------------- @@ -153,8 +155,8 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult revoke_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) override; + [[nodiscard]] OperationResult revoke_vm_access(const std::string& compute_system_name, + const std::filesystem::path& file_path) const override; // --------------------------------------------------------- @@ -170,7 +172,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult add_endpoint(const AddEndpointParameters& params) override; + [[nodiscard]] OperationResult add_endpoint(const AddEndpointParameters& params) const override; // --------------------------------------------------------- @@ -183,7 +185,8 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) override; + [[nodiscard]] OperationResult remove_endpoint(const std::string& compute_system_name, + const std::string& endpoint_guid) const override; // --------------------------------------------------------- @@ -192,11 +195,12 @@ struct HCSWrapper : public HCSWrapperInterface * * @param compute_system_name Target compute system name * @param new_size_mib New memory size, in megabytes - * + * * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) override; + [[nodiscard]] OperationResult resize_memory(const std::string& compute_system_name, + std::uint32_t new_size_mib) const override; // --------------------------------------------------------- @@ -208,7 +212,7 @@ struct HCSWrapper : public HCSWrapperInterface * @return An object that evaluates to true on success, false otherwise. * message() may contain details of failure when result is false. */ - OperationResult get_compute_system_state(const std::string& compute_system_name) override; + [[nodiscard]] OperationResult get_compute_system_state(const std::string& compute_system_name) const override; private: const HCSAPITable api; diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h index 350f82d943..9ad1d78b74 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h @@ -42,6 +42,12 @@ enum class ComputeSystemState : std::uint8_t unknown }; +/** + * Translate host compute system state string to enum + * + * @param str + * @return ComputeSystemState + */ inline ComputeSystemState compute_system_state_from_string(std::string str) { std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h index 11bf857a47..d1631c7fc9 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h @@ -30,26 +30,28 @@ namespace multipass::hyperv::hcs { /** - * Abstract interface for Hyper-V Host Compute System wrapper. + * + * */ struct HCSWrapperInterface { - virtual OperationResult create_compute_system(const CreateComputeSystemParameters& params) = 0; - virtual OperationResult start_compute_system(const std::string& compute_system_name) = 0; - virtual OperationResult shutdown_compute_system(const std::string& compute_system_name) = 0; - virtual OperationResult pause_compute_system(const std::string& compute_system_name) = 0; - virtual OperationResult resume_compute_system(const std::string& compute_system_name) = 0; - virtual OperationResult terminate_compute_system(const std::string& compute_system_name) = 0; - virtual OperationResult get_compute_system_properties(const std::string& compute_system_name) = 0; + virtual OperationResult create_compute_system(const CreateComputeSystemParameters& params) const = 0; + virtual OperationResult start_compute_system(const std::string& compute_system_name) const = 0; + virtual OperationResult shutdown_compute_system(const std::string& compute_system_name) const = 0; + virtual OperationResult pause_compute_system(const std::string& compute_system_name) const = 0; + virtual OperationResult resume_compute_system(const std::string& compute_system_name) const = 0; + virtual OperationResult terminate_compute_system(const std::string& compute_system_name) const = 0; + virtual OperationResult get_compute_system_properties(const std::string& compute_system_name) const = 0; virtual OperationResult grant_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) = 0; + const std::filesystem::path& file_path) const = 0; virtual OperationResult revoke_vm_access(const std::string& compute_system_name, - const std::filesystem::path& file_path) = 0; - virtual OperationResult add_endpoint(const AddEndpointParameters& params) = 0; + const std::filesystem::path& file_path) const = 0; + virtual OperationResult add_endpoint(const AddEndpointParameters& params) const = 0; virtual OperationResult remove_endpoint(const std::string& compute_system_name, - const std::string& endpoint_guid) = 0; - virtual OperationResult resize_memory(const std::string& compute_system_name, const std::uint32_t new_size_mib) = 0; - virtual OperationResult get_compute_system_state(const std::string& compute_system_name) = 0; + const std::string& endpoint_guid) const = 0; + virtual OperationResult resize_memory(const std::string& compute_system_name, + const std::uint32_t new_size_mib) const = 0; + virtual OperationResult get_compute_system_state(const std::string& compute_system_name) const = 0; virtual ~HCSWrapperInterface() = default; }; } // namespace multipass::hyperv::hcs diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp index 30b8947c6f..2465223d38 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.cpp +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -20,7 +20,8 @@ #include #include -#include +#include +#include #include /** @@ -121,58 +122,7 @@ auto guid_from_wstring(const std::wstring& guid_wstr) -> ::GUID auto string_to_wstring(const std::string& str) -> std::wstring { - // We could've gotten away with `return std::wstring{str.begin(), str.end()}` - // and it'd work for 99 pct of the scenarios we'd see, but... let's do the correct thing. - - // Avoid recreating the locale. - static std::unique_ptr, decltype(&_free_locale)> locale{ - _create_locale(LC_ALL, "C"), - _free_locale}; - - // Call the function with nullptr to learn how much space we need to - // store the result. - std::size_t required_size{0}; - if (!(0 == _mbstowcs_s_l(&required_size, nullptr, 0, str.c_str(), str.length(), locale.get()))) - { - throw std::invalid_argument{"Failed to convert multi-byte string to wide-character string."}; - } - - // String to store the converted output. - std::wstring result{}; - - // The required_size will account for the NUL terminator. - // Hence, the actual amount of storage needed for resize is 1 - // characters less. - result.resize(required_size - 1, L'\0'); - - // Perform the conversion. - std::size_t converted_char_cnt{0}; - if (!(0 == _mbstowcs_s_l(&converted_char_cnt, - // data() returns a non-const pointer since C++17, and it is guaranteed - // to be contiguous memory. - result.data(), - // We're doing a size + 1 here because _mbstowcs_s_l will put a NUL terminator - // at the end. The string's internal buffer already account for it, and overwriting - // it with another NUL terminator is well-defined: - - // Quoting cppreference (https://en.cppreference.com/w/cpp/string/basic_string/data) - // ... - // 2) Modifying the past-the-end null terminator stored at data() + size() to any value - // other than CharT() has undefined behavior. - // - // CharT() == '\0'. - result.size() + 1, - // The multi-byte string. - str.c_str(), - // Convert at most the count of multi-byte string characters. - str.length(), - locale.get()))) - { - throw std::invalid_argument{"Failed to convert multi-byte string to wide-character string."}; - } - assert(converted_char_cnt == str.length() + 1); - assert(converted_char_cnt == required_size); - return result; + return std::wstring_convert>().from_bytes(str); } // --------------------------------------------------------- diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.h b/src/platform/backends/hyperv_api/hyperv_api_common.h index 4e10ea6eff..f0225df4aa 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.h +++ b/src/platform/backends/hyperv_api/hyperv_api_common.h @@ -24,57 +24,57 @@ namespace multipass::hyperv { +// --------------------------------------------------------- + /** * Parse given GUID string into a GUID struct. * - * @param guid_wstr GUID in string form, either 36 characters + * @param guid_str GUID in string form, either 36 characters * (without braces) or 38 characters (with braces.) * * @return GUID The parsed GUID */ -auto guid_from_wstring(const std::wstring& guid_wstr) -> GUID; - -// --------------------------------------------------------- +[[nodiscard]] auto guid_from_string(const std::string& guid_str) -> GUID; /** - * @brief - * - * @param guid - * @return std::string + * Parse given GUID string into a GUID struct. + * + * @param guid_wstr GUID in string form, either 36 characters + * (without braces) or 38 characters (with braces.) + * + * @return GUID The parsed GUID */ -auto guid_to_string(const ::GUID& guid) -> std::string; +[[nodiscard]] auto guid_from_wstring(const std::wstring& guid_wstr) -> GUID; // --------------------------------------------------------- /** - * @brief - * - * @param guid - * @return std::wstring + * @brief Convert a GUID to its string representation + * + * @param [in] guid GUID to convert + * @return std::string GUID in string form */ -auto guid_to_wstring(const ::GUID& guid) -> std::wstring; +[[nodiscard]] auto guid_to_string(const ::GUID& guid) -> std::string; // --------------------------------------------------------- /** - * Convert a multi-byte string to a wide-character string. + * @brief Convert a guid to its wide string representation * - * @param str Multi-byte string - * @return Wide-character equivalent of the given multi-byte string. + * @param [in] guid GUID to convert + * @return std::wstring GUID in wstring form */ -auto string_to_wstring(const std::string& str) -> std::wstring; +[[nodiscard]] auto guid_to_wstring(const ::GUID& guid) -> std::wstring; // --------------------------------------------------------- /** - * Parse given GUID string into a GUID struct. - * - * @param guid_str GUID in string form, either 36 characters - * (without braces) or 38 characters (with braces.) + * Convert a multi-byte string to a wide-character string. * - * @return GUID The parsed GUID + * @param str Multi-byte string + * @return Wide-character equivalent of the given multi-byte string. */ -auto guid_from_string(const std::string& guid_str) -> GUID; +[[nodiscard]] auto string_to_wstring(const std::string& str) -> std::wstring; } // namespace multipass::hyperv diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt index f91a088707..59756821fe 100644 --- a/tests/hyperv_api/CMakeLists.txt +++ b/tests/hyperv_api/CMakeLists.txt @@ -1,3 +1,18 @@ +# 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 version 3 as +# published by the Free Software Foundation. +# +# 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 . +# + if(WIN32) target_sources(multipass_tests diff --git a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp index 613e666244..e6e8219db3 100644 --- a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp @@ -15,14 +15,11 @@ * */ -#include "hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h" #include "tests/common.h" #include "tests/mock_logger.h" #include -namespace mpt = multipass::test; - namespace multipass::test { @@ -79,13 +76,14 @@ TEST_F(HyperVHCSAPI, enumerate_properties) params.vhdx_path = ""; const auto c_result = uut.create_compute_system(params); - uut.start_compute_system(params.name); - - - + ASSERT_TRUE(c_result); ASSERT_TRUE(c_result.status_msg.empty()); + const auto s_result = uut.start_compute_system(params.name); + ASSERT_TRUE(s_result); + ASSERT_TRUE(s_result.status_msg.empty()); + const auto p_result = uut.get_compute_system_properties(params.name); EXPECT_TRUE(p_result); std::wprintf(L"%s\n", p_result.status_msg.c_str()); diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index 78737bfe34..ad7b169178 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -275,8 +275,8 @@ TEST_F(HyperVHCNAPI_UnitTests, create_network_failed) logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::create_network(...)"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::warning, - "HCNWrapper::create_network(...) > HcnCreateNetwork failed with 0x80004003."); + mpl::Level::error, + "HCNWrapper::create_network(...) > HcnCreateNetwork failed with 0x80004003!"); } /****************************************************** @@ -542,6 +542,8 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_open_network_failed) "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "open_network() > HcnOpenNetwork failed with 0x80004003!"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); } From 4e319875e4fc39a5cfd312abb215630d745199de Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Fri, 14 Feb 2025 08:53:18 +0300 Subject: [PATCH 06/26] [hyperv-hcs] add update_cpu_count to API --- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 34 +++++++------- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.h | 14 ++++++ .../hcs/hyperv_hcs_wrapper_interface.h | 6 ++- tests/hyperv_api/test_it_hyperv_hcs_api.cpp | 45 ++++++++++++++++++- 4 files changed, 79 insertions(+), 20 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 075af0e02c..9c9f054710 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -395,14 +395,14 @@ OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) co }} }})"; - const auto add_endpoint_settings = fmt::format(add_endpoint_settings_template, - string_to_wstring(params.endpoint_guid), - string_to_wstring(params.nic_mac_address)); + const auto settings = fmt::format(add_endpoint_settings_template, + string_to_wstring(params.endpoint_guid), + string_to_wstring(params.nic_mac_address)); return perform_hcs_operation(api, api.ModifyComputeSystem, params.target_compute_system_name, - add_endpoint_settings.c_str(), + settings.c_str(), nullptr); } @@ -421,23 +421,20 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na "RequestType": "Remove" }})"; - const auto remove_endpoint_settings = - fmt::format(remove_endpoint_settings_template, string_to_wstring(endpoint_guid)); + const auto settings = fmt::format(remove_endpoint_settings_template, string_to_wstring(endpoint_guid)); - return perform_hcs_operation(api, - api.ModifyComputeSystem, - compute_system_name, - remove_endpoint_settings.c_str(), - nullptr); + return perform_hcs_operation(api, api.ModifyComputeSystem, compute_system_name, settings.c_str(), nullptr); } // --------------------------------------------------------- OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) const { + // Machine must be booted up. mpl::log(lvl::trace, kLogCategory, fmt::format("resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib)); + // https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsmodifycomputesystem#remarks constexpr auto resize_memory_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", @@ -445,13 +442,16 @@ OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name "Settings": {0} }})"; - const auto resize_memory_settings = fmt::format(resize_memory_settings_template, new_size_mib); + const auto settings = fmt::format(resize_memory_settings_template, new_size_mib); - return perform_hcs_operation(api, - api.ModifyComputeSystem, - compute_system_name, - resize_memory_settings.c_str(), - nullptr); + return perform_hcs_operation(api, api.ModifyComputeSystem, compute_system_name, settings.c_str(), nullptr); +} + +// --------------------------------------------------------- + +OperationResult HCSWrapper::update_cpu_count(const std::string& compute_system_name, std::uint32_t new_vcpu_count) const +{ + return OperationResult{E_NOTIMPL, L"Not implemented yet!"}; } // --------------------------------------------------------- diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h index 686860c89c..fbc8e18827 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h @@ -204,6 +204,20 @@ struct HCSWrapper : public HCSWrapperInterface // --------------------------------------------------------- + /** + * Change the amount of available vCPUs in the compute system + * + * @param compute_system_name Target compute system name + * @param new_size_mib New memory size, in megabytes + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + [[nodiscard]] OperationResult update_cpu_count(const std::string& compute_system_name, + std::uint32_t new_vcpu_count) const override; + + // --------------------------------------------------------- + /** * Retrieve the current state of the compute system. * diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h index d1631c7fc9..bf188557b4 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_wrapper_interface.h @@ -30,8 +30,8 @@ namespace multipass::hyperv::hcs { /** - * - * + * + * */ struct HCSWrapperInterface { @@ -51,6 +51,8 @@ struct HCSWrapperInterface const std::string& endpoint_guid) const = 0; virtual OperationResult resize_memory(const std::string& compute_system_name, const std::uint32_t new_size_mib) const = 0; + virtual OperationResult update_cpu_count(const std::string& compute_system_name, + const std::uint32_t new_core_count) const = 0; virtual OperationResult get_compute_system_state(const std::string& compute_system_name) const = 0; virtual ~HCSWrapperInterface() = default; }; diff --git a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp index e6e8219db3..b99ad30a10 100644 --- a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp @@ -18,6 +18,8 @@ #include "tests/common.h" #include "tests/mock_logger.h" +#include + #include namespace multipass::test @@ -76,7 +78,7 @@ TEST_F(HyperVHCSAPI, enumerate_properties) params.vhdx_path = ""; const auto c_result = uut.create_compute_system(params); - + ASSERT_TRUE(c_result); ASSERT_TRUE(c_result.status_msg.empty()); @@ -98,4 +100,45 @@ TEST_F(HyperVHCSAPI, enumerate_properties) ASSERT_FALSE(d_result.status_msg.empty()); } +TEST_F(HyperVHCSAPI, DISABLED_update_cpu_count) +{ + + uut_t uut{}; + + hyperv::hcs::CreateComputeSystemParameters params{}; + params.name = "test"; + params.memory_size_mb = 1024; + params.processor_count = 1; + params.cloudinit_iso_path = ""; + params.vhdx_path = ""; + + const auto c_result = uut.create_compute_system(params); + + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + const auto s_result = uut.start_compute_system(params.name); + ASSERT_TRUE(s_result); + ASSERT_TRUE(s_result.status_msg.empty()); + + const auto p_result = uut.get_compute_system_properties(params.name); + EXPECT_TRUE(p_result); + std::wprintf(L"%s\n", p_result.status_msg.c_str()); + + const auto u_result = uut.update_cpu_count(params.name, 8); + EXPECT_TRUE(u_result); + auto v = fmt::format("{}", u_result.code); + std::wprintf(L"%s\n", u_result.status_msg.c_str()); + std::printf("%s \n", v.c_str()); + + // const auto e_result = uut.enumerate_all_compute_systems(); + // EXPECT_TRUE(e_result); + // std::wprintf(L"%s\n", e_result.status_msg.c_str()); + + const auto d_result = uut.terminate_compute_system(params.name); + ASSERT_TRUE(d_result); + std::wprintf(L"%s\n", d_result.status_msg.c_str()); + ASSERT_FALSE(d_result.status_msg.empty()); +} + } // namespace multipass::test \ No newline at end of file From feb1b2613105a90279bb51ce45dc26326bc27952 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Fri, 14 Feb 2025 15:55:56 +0300 Subject: [PATCH 07/26] [hyperv-virtdisk] virtdisk api wrapper implementation - added three primary operations: create, resize and get_info - added initial set of integration tests --- .../backends/hyperv_api/CMakeLists.txt | 5 +- .../hcs/hyperv_hcs_compute_system_state.h | 6 +- .../hyperv_api/virtdisk/virtdisk_api_table.h | 75 +++++ .../virtdisk/virtdisk_api_wrapper.cpp | 305 ++++++++++++++++++ .../virtdisk/virtdisk_api_wrapper.h | 95 ++++++ .../virtdisk_create_virtual_disk_params.h | 57 ++++ .../hyperv_api/virtdisk/virtdisk_disk_info.h | 94 ++++++ .../virtdisk/virtdisk_wrapper_interface.h | 48 +++ tests/hyperv_api/CMakeLists.txt | 14 +- tests/hyperv_api/test_it_hyperv_virtdisk.cpp | 222 +++++++++++++ 10 files changed, 903 insertions(+), 18 deletions(-) create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_disk_info.h create mode 100644 src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h create mode 100644 tests/hyperv_api/test_it_hyperv_virtdisk.cpp diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt index b46c5eb3cd..2460189599 100644 --- a/src/platform/backends/hyperv_api/CMakeLists.txt +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -17,9 +17,10 @@ if(WIN32) add_library(hyperv_api_backend STATIC) target_sources(hyperv_api_backend PRIVATE - hyperv_api_common.cpp + hyperv_api_common.cpp hcn/hyperv_hcn_api_wrapper.cpp - hcs/hyperv_hcs_api_wrapper.cpp) + hcs/hyperv_hcs_api_wrapper.cpp + virtdisk/virtdisk_api_wrapper.cpp) target_link_libraries(hyperv_api_backend PRIVATE fmt diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h index 9ad1d78b74..5c138eede7 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h @@ -44,9 +44,9 @@ enum class ComputeSystemState : std::uint8_t /** * Translate host compute system state string to enum - * - * @param str - * @return ComputeSystemState + * + * @param str + * @return ComputeSystemState */ inline ComputeSystemState compute_system_state_from_string(std::string str) { diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h new file mode 100644 index 0000000000..612f196ee8 --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h @@ -0,0 +1,75 @@ +/* + * 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_HYPERV_API_VIRTDISK_API_TABLE +#define MULTIPASS_HYPERV_API_VIRTDISK_API_TABLE + +// clang-format off +#include +#include +#include +// clang-format on + +#include + +#include + +namespace multipass::hyperv::virtdisk +{ + +/** + * API function table for the virtdisk API + * @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/ + */ +struct VirtDiskAPITable +{ + // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcscreateoperation + std::function CreateVirtualDisk = &::CreateVirtualDisk; + std::function OpenVirtualDisk = &::OpenVirtualDisk; + std::function ResizeVirtualDisk = &::ResizeVirtualDisk; + std::function GetVirtualDiskInformation = &::GetVirtualDiskInformation; + std::function CloseHandle = &::CloseHandle; +}; + +} // namespace multipass::hyperv::virtdisk + +/** + * Formatter type specialization for VirtDiskAPITable + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::virtdisk::VirtDiskAPITable& api, FormatContext& ctx) const + { + return format_to(ctx.out(), + "CreateVirtualDisk: ({}) | OpenVirtualDisk ({}) | ResizeVirtualDisk: ({}) | " + "GetVirtualDiskInformation: ({}) | CloseHandle: ({})", + fmt::ptr(api.CreateVirtualDisk.target()), + fmt::ptr(api.OpenVirtualDisk.target()), + fmt::ptr(api.ResizeVirtualDisk.target()), + fmt::ptr(api.GetVirtualDiskInformation.target()), + fmt::ptr(api.CloseHandle.target())); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp new file mode 100644 index 0000000000..ee28359094 --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -0,0 +1,305 @@ +/* + * 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 "virtdisk_api_wrapper.h" + +#include +#include +#include + +#pragma comment(lib, "virtdisk.lib") + +#include + +#include + +namespace multipass::hyperv::virtdisk +{ + +namespace +{ + +using UniqueHandle = std::unique_ptr, decltype(VirtDiskAPITable::CloseHandle)>; + +namespace mpl = logging; +using lvl = mpl::Level; + +/** + * Category for the log messages. + */ +constexpr auto kLogCategory = "HyperV-VirtDisk-Wrapper"; + +UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesystem::path& vhdx_path) +{ + + // + // Specify UNKNOWN for both device and vendor so the system will use the + // file extension to determine the correct VHD format. + // + VIRTUAL_STORAGE_TYPE type{}; + type.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN; + type.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN; + + HANDLE handle{nullptr}; + const auto path_w = vhdx_path.wstring(); + + const auto result = api.OpenVirtualDisk( + // [in] PVIRTUAL_STORAGE_TYPE VirtualStorageType + &type, + // [in] PCWSTR Path + path_w.c_str(), + // [in] VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask + VIRTUAL_DISK_ACCESS_ALL, + // [in] OPEN_VIRTUAL_DISK_FLAG Flags + OPEN_VIRTUAL_DISK_FLAG_NONE, + // [in, optional] POPEN_VIRTUAL_DISK_PARAMETERS Parameters + nullptr, + // [out] PHANDLE Handle + &handle); + + if (!(result == ERROR_SUCCESS)) + { + mpl::log(lvl::error, + kLogCategory, + fmt::format("open_virtual_disk(...) > OpenVirtualDisk failed with: {}", result)); + return UniqueHandle{nullptr, api.CloseHandle}; + } + + return {handle, api.CloseHandle}; +} + +} // namespace + +// --------------------------------------------------------- + +VirtDiskWrapper::VirtDiskWrapper(const VirtDiskAPITable& api_table) : api{api_table} +{ + mpl::log(lvl::trace, kLogCategory, fmt::format("VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api)); +} + +// --------------------------------------------------------- + +OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskParameters& params) const +{ + // + // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/CreateVirtualDisk.cpp + // + VIRTUAL_STORAGE_TYPE type{}; + + // + // Specify UNKNOWN for both device and vendor so the system will use the + // file extension to determine the correct VHD format. + // + type.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN; + type.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN; + + CREATE_VIRTUAL_DISK_PARAMETERS parameters{}; + parameters.Version = CREATE_VIRTUAL_DISK_VERSION_2; + parameters.Version2 = {}; + parameters.Version2.MaximumSize = params.size_in_bytes; + + // + // Internal size of the virtual disk object blocks, in bytes. + // For VHDX this must be a multiple of 1 MB between 1 and 256 MB. + // For VHD 1 this must be set to one of the following values. + // parameters.Version2.BlockSizeInBytes + // + parameters.Version2.BlockSizeInBytes = 1048576; // 1024 KiB + + if (params.path.extension() == ".vhd") + { + parameters.Version2.BlockSizeInBytes = 524288; // 512 KiB + } + + const auto path_w = params.path.wstring(); + + HANDLE result_handle{nullptr}; + + const auto result = api.CreateVirtualDisk(&type, + // [in] PCWSTR Path + path_w.c_str(), + // [in] VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + VIRTUAL_DISK_ACCESS_NONE, + // [in, optional] PSECURITY_DESCRIPTOR SecurityDescriptor, + nullptr, + // [in] CREATE_VIRTUAL_DISK_FLAG Flags, + CREATE_VIRTUAL_DISK_FLAG_NONE, + // [in] ULONG ProviderSpecificFlags, + 0, + // [in] PCREATE_VIRTUAL_DISK_PARAMETERS Parameters, + ¶meters, + // [in, optional] LPOVERLAPPED Overlapped + nullptr, + // [out] PHANDLE Handle + &result_handle); + + if (result == ERROR_SUCCESS) + { + UniqueHandle _{result_handle, api.CloseHandle}; + (void)_; + return OperationResult{NOERROR, L""}; + } + + return OperationResult{E_FAIL, fmt::format(L"CreateVirtualDisk failed with {}!", result)}; +} + +// --------------------------------------------------------- + +OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path& vhdx_path, + std::uint64_t new_size_bytes) const +{ + + const auto disk_handle = open_virtual_disk(api, vhdx_path); + + if (nullptr == disk_handle) + { + return OperationResult{E_FAIL, L"open_virtual_disk failed!"}; + } + + RESIZE_VIRTUAL_DISK_PARAMETERS params{}; + params.Version = RESIZE_VIRTUAL_DISK_VERSION_1; + params.Version1 = {}; + params.Version1.NewSize = new_size_bytes; + + const auto resize_result = api.ResizeVirtualDisk( + // [in] HANDLE VirtualDiskHandle + disk_handle.get(), + // [in] RESIZE_VIRTUAL_DISK_FLAG Flags + RESIZE_VIRTUAL_DISK_FLAG_NONE, + // [in] PRESIZE_VIRTUAL_DISK_PARAMETERS Parameters + ¶ms, + // [in, optional] LPOVERLAPPED Overlapped + nullptr); + + if (ERROR_SUCCESS == resize_result) + { + return OperationResult{NOERROR, L""}; + } + + return OperationResult{E_FAIL, fmt::format(L"ResizeVirtualDisk failed with {}!", resize_result)}; +} + +// --------------------------------------------------------- + +OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::path& vhdx_path, + VirtualDiskInfo& vdinfo) const +{ + // + // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/GetVirtualDiskInformation.cpp + // + + const auto disk_handle = open_virtual_disk(api, vhdx_path); + + if (nullptr == disk_handle) + { + return OperationResult{E_FAIL, L"open_virtual_disk failed!"}; + } + + constexpr GET_VIRTUAL_DISK_INFO_VERSION what_to_get[] = {GET_VIRTUAL_DISK_INFO_SIZE, + GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE, + GET_VIRTUAL_DISK_INFO_SMALLEST_SAFE_VIRTUAL_SIZE, + GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE}; + + for (const auto version : what_to_get) + { + GET_VIRTUAL_DISK_INFO disk_info{}; + disk_info.Version = version; + + ULONG sz = sizeof(disk_info); + + const auto result = api.GetVirtualDiskInformation(disk_handle.get(), &sz, &disk_info, nullptr); + + if (ERROR_SUCCESS == result) + { + switch (disk_info.Version) + { + case GET_VIRTUAL_DISK_INFO_SIZE: + vdinfo.size = std::make_optional(); + vdinfo.size->virtual_ = disk_info.Size.VirtualSize; + vdinfo.size->block = disk_info.Size.BlockSize; + vdinfo.size->physical = disk_info.Size.PhysicalSize; + vdinfo.size->sector = disk_info.Size.SectorSize; + break; + case GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE: + { + switch (disk_info.VirtualStorageType.DeviceId) + { + case VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN: + vdinfo.virtual_storage_type = "unknown"; + break; + case VIRTUAL_STORAGE_TYPE_DEVICE_ISO: + vdinfo.virtual_storage_type = "iso"; + break; + case VIRTUAL_STORAGE_TYPE_DEVICE_VHD: + vdinfo.virtual_storage_type = "vhd"; + break; + case VIRTUAL_STORAGE_TYPE_DEVICE_VHDX: + vdinfo.virtual_storage_type = "vhdx"; + break; + case VIRTUAL_STORAGE_TYPE_DEVICE_VHDSET: + vdinfo.virtual_storage_type = "vhdset"; + break; + } + } + break; + case GET_VIRTUAL_DISK_INFO_SMALLEST_SAFE_VIRTUAL_SIZE: + vdinfo.smallest_safe_virtual_size = disk_info.SmallestSafeVirtualSize; + break; + case GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE: + { + enum class ProviderSubtype : std::uint8_t + { + fixed = 2, + dynamic = 3, + differencing = 4 + }; + + switch (static_cast(disk_info.ProviderSubtype)) + { + case ProviderSubtype::fixed: + vdinfo.provider_subtype = "fixed"; + break; + case ProviderSubtype::dynamic: + vdinfo.provider_subtype = "dynamic"; + + break; + case ProviderSubtype::differencing: + vdinfo.provider_subtype = "differencing"; + break; + default: + vdinfo.provider_subtype = "unknown"; + break; + } + } + break; + default: + assert(0); + break; + } + } + else + { + mpl::log(lvl::warning, + kLogCategory, + fmt::format("get_virtual_disk_info(...) > failed to get {}", fmt::underlying(version))); + } + } + + return {NOERROR, L""}; +} + +} // namespace multipass::hyperv::virtdisk \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h new file mode 100644 index 0000000000..97ce1f5d0d --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -0,0 +1,95 @@ +/* + * 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_HYPERV_API_HCS_WRAPPER +#define MULTIPASS_HYPERV_API_HCS_WRAPPER + +#include "virtdisk_api_table.h" +#include "virtdisk_wrapper_interface.h" +#include "virtdisk_disk_info.h" + +namespace multipass::hyperv::virtdisk +{ + +/** + * A high-level wrapper class that defines + * the common operations that Host Compute System + * API provide. + */ +struct VirtDiskWrapper : public VirtDiskWrapperInterface +{ + + /** + * Construct a new HCNWrapper + * + * @param api_table The HCN API table object (optional) + * + * The wrapper will use the real HCN API by default. + */ + VirtDiskWrapper(const VirtDiskAPITable& api_table = {}); + VirtDiskWrapper(const VirtDiskWrapper&) = default; + VirtDiskWrapper(VirtDiskWrapper&&) = default; + VirtDiskWrapper& operator=(const VirtDiskWrapper&) = delete; + VirtDiskWrapper& operator=(VirtDiskWrapper&&) = delete; + + // --------------------------------------------------------- + + /** + * Create a new Virtual Disk + * + * @param [in] params Parameters for the new virtual disk + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + [[nodiscard]] OperationResult create_virtual_disk(const CreateVirtualDiskParameters& params) const override; + + // --------------------------------------------------------- + + /** + * Resize an existing Virtual Disk + * + * @param [in] vhdx_path Path to the virtual disk + * @param [in] new_size New disk size, in bytes + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + virtual OperationResult resize_virtual_disk(const std::filesystem::path& vhdx_path, + std::uint64_t new_size_bytes) const override; + + // --------------------------------------------------------- + + /** + * Get information about an existing Virtual Disk + * + * @param [in] vhdx_path Path to the virtual disk + * @param [out] vdinfo Virtual disk info output object + * + * @return An object that evaluates to true on success, false otherwise. + * message() may contain details of failure when result is false. + */ + virtual OperationResult get_virtual_disk_info(const std::filesystem::path& vhdx_path, + VirtualDiskInfo& vdinfo) const override; + +private: + const VirtDiskAPITable api; +}; + +} // namespace multipass::hyperv::virtdisk + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h new file mode 100644 index 0000000000..45e0504f60 --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h @@ -0,0 +1,57 @@ +/* + * 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_HYPERV_API_VIRTDISK_CREATE_VIRTUAL_DISK_PARAMETERS_H +#define MULTIPASS_HYPERV_API_VIRTDISK_CREATE_VIRTUAL_DISK_PARAMETERS_H + +#include +#include + + +namespace multipass::hyperv::virtdisk +{ + +/** + * Parameters for creating a new virtual disk drive. + */ +struct CreateVirtualDiskParameters +{ + std::uint64_t size_in_bytes{}; + std::filesystem::path path{}; +}; + +} // namespace multipass::hyperv::virtdisk + +/** + * Formatter type specialization for CreateComputeSystemParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::virtdisk::CreateVirtualDiskParameters& params, FormatContext& ctx) const + { + return format_to(ctx.out(), "Size (in bytes): ({}) | Path: ({}) ", params.size_in_bytes, params.path.string()); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_disk_info.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_disk_info.h new file mode 100644 index 0000000000..05ccab6088 --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_disk_info.h @@ -0,0 +1,94 @@ +/* + * 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_HYPERV_API_VIRTDISK_DISK_INFO_H +#define MULTIPASS_HYPERV_API_VIRTDISK_DISK_INFO_H + +#include +#include + +#include +#include + +namespace multipass::hyperv::virtdisk +{ + +struct VirtualDiskInfo +{ + + struct size_info + { + std::uint64_t virtual_; + std::uint64_t physical; + std::uint64_t block; + std::uint64_t sector; + }; + std::optional size; + std::optional smallest_safe_virtual_size; + std::optional virtual_storage_type; + std::optional provider_subtype; +}; + +} // namespace multipass::hyperv::virtdisk + +/** + * Formatter type specialization for CreateComputeSystemParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::virtdisk::VirtualDiskInfo::size_info& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Virtual: ({}) | Physical: ({}) | Block: ({}) | Sector: ({})", + params.virtual_, + params.physical, + params.block, + params.sector); + } +}; + +/** + * Formatter type specialization for CreateComputeSystemParameters + */ +template +struct fmt::formatter +{ + constexpr auto parse(basic_format_parse_context& ctx) + { + return ctx.begin(); + } + + template + auto format(const multipass::hyperv::virtdisk::VirtualDiskInfo& params, FormatContext& ctx) const + { + return format_to(ctx.out(), + "Storage type: {} | Size: {} | Smallest safe size: {} | Provider subtype: {}", + params.virtual_storage_type, + params.size, + params.smallest_safe_virtual_size, + params.provider_subtype); + } +}; + +#endif \ No newline at end of file diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h new file mode 100644 index 0000000000..095162d0f8 --- /dev/null +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h @@ -0,0 +1,48 @@ +/* + * 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_HYPERV_API_VIRTDISK_WRAPPER_INTERFACE_H +#define MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_INTERFACE_H + +#include "../hyperv_api_operation_result.h" +#include "virtdisk_create_virtual_disk_params.h" +#include "virtdisk_disk_info.h" + +#include +#include + +namespace multipass::hyperv::virtdisk +{ + + + +/** + * + * + */ +struct VirtDiskWrapperInterface +{ + virtual OperationResult create_virtual_disk(const CreateVirtualDiskParameters& params) const = 0; + virtual OperationResult resize_virtual_disk(const std::filesystem::path& vhdx_path, + std::uint64_t new_size_bytes) const = 0; + virtual OperationResult get_virtual_disk_info(const std::filesystem::path& vhdx_path, + VirtualDiskInfo& vdinfo) const = 0; + virtual ~VirtDiskWrapperInterface() = default; +}; +} // namespace multipass::hyperv::virtdisk + +#endif \ No newline at end of file diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt index 59756821fe..794ebd3dac 100644 --- a/tests/hyperv_api/CMakeLists.txt +++ b/tests/hyperv_api/CMakeLists.txt @@ -18,21 +18,9 @@ if(WIN32) target_sources(multipass_tests PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcn_api.cpp - ) - - target_sources(multipass_tests - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcn_api.cpp - ) - - target_sources(multipass_tests - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcs_api.cpp - ) - - target_sources(multipass_tests - PRIVATE ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcs_api.cpp + ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_virtdisk.cpp ) endif() - diff --git a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp new file mode 100644 index 0000000000..b601010b6f --- /dev/null +++ b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp @@ -0,0 +1,222 @@ +/* + * 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 "tests/common.h" +#include "tests/mock_logger.h" + +#include + +#include + +#include +#include + +namespace multipass::test +{ + +using uut_t = hyperv::virtdisk::VirtDiskWrapper; + +struct HyperVVirtDisk_Integration : public ::testing::Test +{ +}; + +struct auto_remove_path +{ + + auto_remove_path(std::filesystem::path p) : path(p) + { + } + + ~auto_remove_path() + { + std::error_code ec{}; + std::filesystem::remove(path, ec); + } + + operator std::filesystem::path() const noexcept + { + return path; + } + +private: + const std::filesystem::path path; +}; + +auto_remove_path make_tempfile_path(std::string extension) +{ + char pattern[] = "temp-XXXXXX"; + _mktemp_s(pattern); + std::string f = pattern; + f.append(extension); + return {std::filesystem::temp_directory_path() / f}; +} + +TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhdx) +{ + auto temp_path = make_tempfile_path(".vhdx"); + std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); + + uut_t uut{}; + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = temp_path; + params.size_in_bytes = 1024 * 1024 * 1024; // 1 GiB + + const auto result = uut.create_virtual_disk(params); + ASSERT_TRUE(result); + ASSERT_TRUE(result.status_msg.empty()); +} + +TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhd) +{ + auto temp_path = make_tempfile_path(".vhd"); + std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); + + uut_t uut{}; + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = temp_path; + params.size_in_bytes = 1024 * 1024 * 1024; // 1 GiB + + const auto result = uut.create_virtual_disk(params); + ASSERT_TRUE(result); + ASSERT_TRUE(result.status_msg.empty()); +} + +TEST_F(HyperVVirtDisk_Integration, get_virtual_disk_properties) +{ + auto temp_path = make_tempfile_path(".vhdx"); + std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); + + uut_t uut{}; + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = temp_path; + params.size_in_bytes = 1024 * 1024 * 1024; // 1 GiB + + const auto c_result = uut.create_virtual_disk(params); + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + hyperv::virtdisk::VirtualDiskInfo info{}; + const auto g_result = uut.get_virtual_disk_info(temp_path, info); + + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.size.has_value()); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.size->virtual_, 1024 * 1024 * 1024); + ASSERT_EQ(info.size->block, 1024 * 1024); + ASSERT_EQ(info.size->sector, 512); + + fmt::print("{}", info); +} + +TEST_F(HyperVVirtDisk_Integration, resize_grow) +{ + auto temp_path = make_tempfile_path(".vhdx"); + std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); + + uut_t uut{}; + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = temp_path; + params.size_in_bytes = 1024 * 1024 * 1024; // 1 GiB + + const auto c_result = uut.create_virtual_disk(params); + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + hyperv::virtdisk::VirtualDiskInfo info{}; + const auto g_result = uut.get_virtual_disk_info(temp_path, info); + + ASSERT_TRUE(g_result); + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.size.has_value()); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.size->virtual_, params.size_in_bytes); + ASSERT_EQ(info.size->block, 1024 * 1024); + ASSERT_EQ(info.size->sector, 512); + + fmt::print("{}", info); + + const auto r_result = uut.resize_virtual_disk(temp_path, params.size_in_bytes * 2); + ASSERT_TRUE(r_result); + + info = {}; + + const auto g2_result = uut.get_virtual_disk_info(temp_path, info); + + ASSERT_TRUE(g2_result); + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.size.has_value()); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.size->virtual_, params.size_in_bytes * 2); + ASSERT_EQ(info.size->block, 1024 * 1024); + ASSERT_EQ(info.size->sector, 512); + + fmt::print("{}", info); +} + +TEST_F(HyperVVirtDisk_Integration, DISABLED_resize_shrink) +{ + auto temp_path = make_tempfile_path(".vhdx"); + std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); + + uut_t uut{}; + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = temp_path; + params.size_in_bytes = 1024 * 1024 * 1024; // 1 GiB + + const auto c_result = uut.create_virtual_disk(params); + ASSERT_TRUE(c_result); + ASSERT_TRUE(c_result.status_msg.empty()); + + hyperv::virtdisk::VirtualDiskInfo info{}; + const auto g_result = uut.get_virtual_disk_info(temp_path, info); + + ASSERT_TRUE(g_result); + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.size.has_value()); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.size->virtual_, params.size_in_bytes); + ASSERT_EQ(info.size->block, 1024 * 1024); + ASSERT_EQ(info.size->sector, 512); + + fmt::print("{}", info); + + const auto r_result = uut.resize_virtual_disk(temp_path, params.size_in_bytes / 2); + ASSERT_TRUE(r_result); + + info = {}; + + // SmallestSafeVirtualSize + + const auto g2_result = uut.get_virtual_disk_info(temp_path, info); + + ASSERT_TRUE(g2_result); + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.size.has_value()); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.size->virtual_, params.size_in_bytes / 2); + ASSERT_EQ(info.size->block, 1024 * 1024); + ASSERT_EQ(info.size->sector, 512); + + fmt::print("{}", info); +} + +} // namespace multipass::test \ No newline at end of file From f27fb0046084ccd710f486d475c1687c62b9470b Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Fri, 14 Feb 2025 18:29:15 +0300 Subject: [PATCH 08/26] [hyperv] fix EOF newline --- .gitignore | 2 +- .../backends/hyperv_api/hcn/hyperv_hcn_api_table.h | 2 +- .../backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h | 2 +- .../hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h | 2 +- .../hyperv_api/hcn/hyperv_hcn_create_network_params.h | 2 +- .../hyperv_api/hcn/hyperv_hcn_wrapper_interface.h | 2 +- .../hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h | 2 +- .../backends/hyperv_api/hcs/hyperv_hcs_api_table.h | 2 +- .../backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 2 +- .../backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h | 2 +- .../hyperv_api/hcs/hyperv_hcs_compute_system_state.h | 2 +- .../hcs/hyperv_hcs_create_compute_system_params.h | 2 +- .../hyperv_api/hcs/hyperv_hcs_wrapper_interface.h | 2 +- src/platform/backends/hyperv_api/hyperv_api_common.cpp | 2 +- src/platform/backends/hyperv_api/hyperv_api_common.h | 2 +- .../backends/hyperv_api/virtdisk/virtdisk_api_table.h | 8 ++++++-- .../backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp | 2 +- .../backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h | 2 +- .../virtdisk/virtdisk_create_virtual_disk_params.h | 2 +- .../backends/hyperv_api/virtdisk/virtdisk_disk_info.h | 2 +- .../hyperv_api/virtdisk/virtdisk_wrapper_interface.h | 5 +---- tests/hyperv_api/test_it_hyperv_hcn_api.cpp | 2 +- tests/hyperv_api/test_it_hyperv_hcs_api.cpp | 2 +- tests/hyperv_api/test_it_hyperv_virtdisk.cpp | 2 +- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 2 +- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 2 +- vcpkg.json | 2 +- 27 files changed, 32 insertions(+), 31 deletions(-) diff --git a/.gitignore b/.gitignore index 37a03b5484..84b41e0feb 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ packaging/windows/custom-actions/packages/* packaging/windows/custom-actions/x64/* # clangd cache path -.cache/ \ No newline at end of file +.cache/ diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index ec0e23cdea..c03a7d48d6 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -84,4 +84,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h index 2272aeb9e9..082a35c23e 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h @@ -91,4 +91,4 @@ struct HCNWrapper : public HCNWrapperInterface } // namespace multipass::hyperv::hcn -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h index 658c147e0b..aa522d7808 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h @@ -81,4 +81,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h index d863813667..dee75f7d83 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h @@ -76,4 +76,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h index 4ec4aef464..730f317aba 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h @@ -39,4 +39,4 @@ struct HCNWrapperInterface }; } // namespace multipass::hyperv::hcn -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h index 8e4c83d320..1e256caddf 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h @@ -70,4 +70,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index 08e9c1c5b7..3dc0722a85 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -120,4 +120,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 9c9f054710..ee8e16a72f 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -529,4 +529,4 @@ OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_ return {result.code, L"Unknown"}; } -} // namespace multipass::hyperv::hcs \ No newline at end of file +} // namespace multipass::hyperv::hcs diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h index fbc8e18827..02a21c0ecb 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h @@ -234,4 +234,4 @@ struct HCSWrapper : public HCSWrapperInterface } // namespace multipass::hyperv::hcs -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h index 5c138eede7..b5404fe2e2 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h @@ -71,4 +71,4 @@ inline ComputeSystemState compute_system_state_from_string(std::string str) } // namespace multipass::hyperv::hcs -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h index ea025cd91c..fd8eb48eb1 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h @@ -82,4 +82,4 @@ struct fmt::formatter std::wstring return fmt::format(L"{}", guid); } -} // namespace multipass::hyperv \ No newline at end of file +} // namespace multipass::hyperv diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.h b/src/platform/backends/hyperv_api/hyperv_api_common.h index f0225df4aa..068dfcb546 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.h +++ b/src/platform/backends/hyperv_api/hyperv_api_common.h @@ -78,4 +78,4 @@ namespace multipass::hyperv } // namespace multipass::hyperv -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h index 612f196ee8..7eab834b27 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h @@ -37,11 +37,15 @@ namespace multipass::hyperv::virtdisk */ struct VirtDiskAPITable { - // @ref https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcscreateoperation + // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-compactvirtualdisk std::function CreateVirtualDisk = &::CreateVirtualDisk; + // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-openvirtualdisk std::function OpenVirtualDisk = &::OpenVirtualDisk; + // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-resizevirtualdisk std::function ResizeVirtualDisk = &::ResizeVirtualDisk; + // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-getvirtualdiskinformation std::function GetVirtualDiskInformation = &::GetVirtualDiskInformation; + // @ref https://learn.microsoft.com/en-us/windows/win32/api/handleapi/nf-handleapi-closehandle std::function CloseHandle = &::CloseHandle; }; @@ -72,4 +76,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index ee28359094..f132d3f44d 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -302,4 +302,4 @@ OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::pa return {NOERROR, L""}; } -} // namespace multipass::hyperv::virtdisk \ No newline at end of file +} // namespace multipass::hyperv::virtdisk diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h index 97ce1f5d0d..eadd34d236 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -92,4 +92,4 @@ struct VirtDiskWrapper : public VirtDiskWrapperInterface } // namespace multipass::hyperv::virtdisk -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h index 45e0504f60..66afe511f6 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h @@ -54,4 +54,4 @@ struct fmt::formatter } }; -#endif \ No newline at end of file +#endif diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h index 095162d0f8..0b34d7046d 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h @@ -23,13 +23,10 @@ #include "virtdisk_disk_info.h" #include -#include namespace multipass::hyperv::virtdisk { - - /** * * @@ -45,4 +42,4 @@ struct VirtDiskWrapperInterface }; } // namespace multipass::hyperv::virtdisk -#endif \ No newline at end of file +#endif diff --git a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp index e7f6465870..97aeba0926 100644 --- a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp @@ -113,4 +113,4 @@ TEST_F(HyperVHCNAPI, create_delete_endpoint) } } -} // namespace multipass::test \ No newline at end of file +} // namespace multipass::test diff --git a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp index b99ad30a10..86d11f8b3e 100644 --- a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp @@ -141,4 +141,4 @@ TEST_F(HyperVHCSAPI, DISABLED_update_cpu_count) ASSERT_FALSE(d_result.status_msg.empty()); } -} // namespace multipass::test \ No newline at end of file +} // namespace multipass::test diff --git a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp index b601010b6f..52550a2d93 100644 --- a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp @@ -219,4 +219,4 @@ TEST_F(HyperVVirtDisk_Integration, DISABLED_resize_shrink) fmt::print("{}", info); } -} // namespace multipass::test \ No newline at end of file +} // namespace multipass::test diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index ad7b169178..f069e0b9f1 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -773,4 +773,4 @@ TEST_F(HyperVHCNAPI_UnitTests, delete_endpoint_failure) } } -} // namespace multipass::test \ No newline at end of file +} // namespace multipass::test diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp index 4dc971db5b..0931aea994 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -2713,4 +2713,4 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_wait_for_operation_resul expected_status_msg); } -} // namespace multipass::test \ No newline at end of file +} // namespace multipass::test diff --git a/vcpkg.json b/vcpkg.json index b3e25517cf..b1a6b72d8b 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -11,4 +11,4 @@ "version": "11.0.2" } ] -} \ No newline at end of file +} From 1d88683da099829b26b23c6fae5b826b65288758 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 10:46:48 +0300 Subject: [PATCH 09/26] [hyperv-hcn] remove the redundant "PortName" from endpoint JSON --- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 3 -- tests/hyperv_api/test_it_hyperv_hcn_api.cpp | 28 ++++--------------- tests/hyperv_api/test_it_hyperv_hcs_api.cpp | 20 +++---------- tests/hyperv_api/test_it_hyperv_virtdisk.cpp | 13 ++++----- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 6 ---- 5 files changed, 16 insertions(+), 54 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 444ae07f17..55a8fd1ba7 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -101,9 +101,6 @@ constexpr auto endpoint_settings_template = LR"( }}, "HostComputeNetwork": "{0}", "Policies": [ - {{ - "Type": "PortName" - }}, {{ "Type": "Firewall", "Settings": {{ diff --git a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp index 97aeba0926..780052d0be 100644 --- a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp @@ -16,36 +16,20 @@ */ #include "tests/common.h" -#include "tests/mock_logger.h" #include #include #include - -namespace mpt = multipass::test; - namespace multipass::test { using uut_t = hyperv::hcn::HCNWrapper; -struct HyperVHCNAPI : public ::testing::Test -{ - - // mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); - - void SetUp() override - { - // logger_scope.mock_logger. - } - - void TearDown() override - { - } -}; +struct HyperVHCNAPI_IntegrationTests : public ::testing::Test +{}; -TEST_F(HyperVHCNAPI, create_delete_network) +TEST_F(HyperVHCNAPI_IntegrationTests, create_delete_network) { uut_t uut; hyperv::hcn::CreateNetworkParameters params{}; @@ -54,7 +38,7 @@ TEST_F(HyperVHCNAPI, create_delete_network) params.subnet = "172.50.224.0/20"; params.gateway = "172.50.224.1"; - uut.delete_network(params.guid); + (void)uut.delete_network(params.guid); { const auto& [success, error_msg] = uut.create_network(params); @@ -69,7 +53,7 @@ TEST_F(HyperVHCNAPI, create_delete_network) } } -TEST_F(HyperVHCNAPI, create_delete_endpoint) +TEST_F(HyperVHCNAPI_IntegrationTests, create_delete_endpoint) { uut_t uut; hyperv::hcn::CreateNetworkParameters network_params{}; @@ -85,7 +69,7 @@ TEST_F(HyperVHCNAPI, create_delete_endpoint) endpoint_params.vm_creator_id = R"(456fe1ac-1e46-49ec-bb9f-5e44cc2de23b)"; endpoint_params.endpoint_ipvx_addr = "172.50.224.2"; - uut.delete_network(network_params.guid); + (void)uut.delete_network(network_params.guid); { const auto& [success, error_msg] = uut.create_network(network_params); diff --git a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp index 86d11f8b3e..2a3753ab98 100644 --- a/tests/hyperv_api/test_it_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcs_api.cpp @@ -16,7 +16,6 @@ */ #include "tests/common.h" -#include "tests/mock_logger.h" #include @@ -27,22 +26,11 @@ namespace multipass::test using uut_t = hyperv::hcs::HCSWrapper; -struct HyperVHCSAPI : public ::testing::Test +struct HyperVHCSAPI_IntegrationTests : public ::testing::Test { - - // mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); - - void SetUp() override - { - // logger_scope.mock_logger. - } - - void TearDown() override - { - } }; -TEST_F(HyperVHCSAPI, create_delete_compute_system) +TEST_F(HyperVHCSAPI_IntegrationTests, create_delete_compute_system) { uut_t uut{}; @@ -65,7 +53,7 @@ TEST_F(HyperVHCSAPI, create_delete_compute_system) ASSERT_FALSE(d_result.status_msg.empty()); } -TEST_F(HyperVHCSAPI, enumerate_properties) +TEST_F(HyperVHCSAPI_IntegrationTests, enumerate_properties) { uut_t uut{}; @@ -100,7 +88,7 @@ TEST_F(HyperVHCSAPI, enumerate_properties) ASSERT_FALSE(d_result.status_msg.empty()); } -TEST_F(HyperVHCSAPI, DISABLED_update_cpu_count) +TEST_F(HyperVHCSAPI_IntegrationTests, DISABLED_update_cpu_count) { uut_t uut{}; diff --git a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp index 52550a2d93..5e7719fde9 100644 --- a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp @@ -16,7 +16,6 @@ */ #include "tests/common.h" -#include "tests/mock_logger.h" #include @@ -30,7 +29,7 @@ namespace multipass::test using uut_t = hyperv::virtdisk::VirtDiskWrapper; -struct HyperVVirtDisk_Integration : public ::testing::Test +struct HyperVVirtDisk_IntegrationTests : public ::testing::Test { }; @@ -65,7 +64,7 @@ auto_remove_path make_tempfile_path(std::string extension) return {std::filesystem::temp_directory_path() / f}; } -TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhdx) +TEST_F(HyperVVirtDisk_IntegrationTests, create_virtual_disk_vhdx) { auto temp_path = make_tempfile_path(".vhdx"); std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); @@ -80,7 +79,7 @@ TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhdx) ASSERT_TRUE(result.status_msg.empty()); } -TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhd) +TEST_F(HyperVVirtDisk_IntegrationTests, create_virtual_disk_vhd) { auto temp_path = make_tempfile_path(".vhd"); std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); @@ -95,7 +94,7 @@ TEST_F(HyperVVirtDisk_Integration, create_virtual_disk_vhd) ASSERT_TRUE(result.status_msg.empty()); } -TEST_F(HyperVVirtDisk_Integration, get_virtual_disk_properties) +TEST_F(HyperVVirtDisk_IntegrationTests, get_virtual_disk_properties) { auto temp_path = make_tempfile_path(".vhdx"); std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); @@ -123,7 +122,7 @@ TEST_F(HyperVVirtDisk_Integration, get_virtual_disk_properties) fmt::print("{}", info); } -TEST_F(HyperVVirtDisk_Integration, resize_grow) +TEST_F(HyperVVirtDisk_IntegrationTests, resize_grow) { auto temp_path = make_tempfile_path(".vhdx"); std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); @@ -170,7 +169,7 @@ TEST_F(HyperVVirtDisk_Integration, resize_grow) fmt::print("{}", info); } -TEST_F(HyperVVirtDisk_Integration, DISABLED_resize_shrink) +TEST_F(HyperVVirtDisk_IntegrationTests, DISABLED_resize_shrink) { auto temp_path = make_tempfile_path(".vhdx"); std::wprintf(L"Path: %s\n", static_cast(temp_path).c_str()); diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index f069e0b9f1..c8cd045f75 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -433,9 +433,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) }, "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", "Policies": [ - { - "Type": "PortName" - }, { "Type": "Firewall", "Settings": { @@ -602,9 +599,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) }, "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", "Policies": [ - { - "Type": "PortName" - }, { "Type": "Firewall", "Settings": { From 83c7209c8636722dc7e02acd09de8b3ef86b730b Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 12:45:10 +0300 Subject: [PATCH 10/26] [hyperv-virtdisk] added unit tests --- .../hyperv_api/virtdisk/virtdisk_api_table.h | 2 +- .../virtdisk/virtdisk_api_wrapper.cpp | 10 +- .../virtdisk/virtdisk_api_wrapper.h | 3 +- .../virtdisk_create_virtual_disk_params.h | 1 - tests/hyperv_api/CMakeLists.txt | 1 + tests/hyperv_api/test_it_hyperv_hcn_api.cpp | 3 +- tests/hyperv_api/test_ut_hyperv_virtdisk.cpp | 661 ++++++++++++++++++ 7 files changed, 675 insertions(+), 6 deletions(-) create mode 100644 tests/hyperv_api/test_ut_hyperv_virtdisk.cpp diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h index 7eab834b27..0f794b66a0 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h @@ -37,7 +37,7 @@ namespace multipass::hyperv::virtdisk */ struct VirtDiskAPITable { - // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-compactvirtualdisk + // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-createvirtualdisk std::function CreateVirtualDisk = &::CreateVirtualDisk; // @ref https://learn.microsoft.com/en-us/windows/win32/api/virtdisk/nf-virtdisk-openvirtualdisk std::function OpenVirtualDisk = &::OpenVirtualDisk; diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index f132d3f44d..cb512955b2 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -45,7 +45,7 @@ constexpr auto kLogCategory = "HyperV-VirtDisk-Wrapper"; UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesystem::path& vhdx_path) { - + mpl::log(lvl::trace, kLogCategory, fmt::format("open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string())); // // Specify UNKNOWN for both device and vendor so the system will use the // file extension to determine the correct VHD format. @@ -95,6 +95,7 @@ VirtDiskWrapper::VirtDiskWrapper(const VirtDiskAPITable& api_table) : api{api_ta OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskParameters& params) const { + mpl::log(lvl::trace, kLogCategory, fmt::format("create_virtual_disk(...) > params: {}", params)); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/CreateVirtualDisk.cpp // @@ -162,7 +163,11 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path& vhdx_path, std::uint64_t new_size_bytes) const { - + mpl::log(lvl::trace, + kLogCategory, + fmt::format("resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", + vhdx_path.string(), + new_size_bytes)); const auto disk_handle = open_virtual_disk(api, vhdx_path); if (nullptr == disk_handle) @@ -198,6 +203,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::path& vhdx_path, VirtualDiskInfo& vdinfo) const { + mpl::log(lvl::trace, kLogCategory, fmt::format("get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string())); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/GetVirtualDiskInformation.cpp // diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h index eadd34d236..21d439f8b0 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -19,8 +19,9 @@ #define MULTIPASS_HYPERV_API_HCS_WRAPPER #include "virtdisk_api_table.h" -#include "virtdisk_wrapper_interface.h" #include "virtdisk_disk_info.h" +#include "virtdisk_wrapper_interface.h" + namespace multipass::hyperv::virtdisk { diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h index 66afe511f6..1b0cc941ca 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h @@ -21,7 +21,6 @@ #include #include - namespace multipass::hyperv::virtdisk { diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt index 794ebd3dac..5c21460858 100644 --- a/tests/hyperv_api/CMakeLists.txt +++ b/tests/hyperv_api/CMakeLists.txt @@ -22,5 +22,6 @@ if(WIN32) ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_hcs_api.cpp ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcs_api.cpp ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_virtdisk.cpp + ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_virtdisk.cpp ) endif() diff --git a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp index 780052d0be..e469a29fd7 100644 --- a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp @@ -27,7 +27,8 @@ namespace multipass::test using uut_t = hyperv::hcn::HCNWrapper; struct HyperVHCNAPI_IntegrationTests : public ::testing::Test -{}; +{ +}; TEST_F(HyperVHCNAPI_IntegrationTests, create_delete_network) { diff --git a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp new file mode 100644 index 0000000000..0c21687f20 --- /dev/null +++ b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp @@ -0,0 +1,661 @@ +/* + * 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 "tests/common.h" +#include "tests/mock_logger.h" + +#include + +#include + +#include +#include + +#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) +namespace mpt = multipass::test; +namespace mpl = multipass::logging; + +using testing::DoAll; +using testing::Return; + +namespace multipass::test +{ + +using uut_t = hyperv::virtdisk::VirtDiskWrapper; + +struct HyperVVirtDisk_UnitTests : public ::testing::Test +{ + mpt::MockLogger::Scope logger_scope = mpt::MockLogger::inject(); + + virtual void SetUp() override + { + + // Each of the unit tests are expected to have their own mock functions + // and override the mock_api_table with them. Hence, the stub mocks should + // not be called at all. + // If any of them do get called, then: + // + // a-) You have forgotten to mock something + // b-) The implementation is using a function that you didn't expect + // + // Either way, you should have a look. + EXPECT_NO_CALL(stub_mock_create_virtual_disk); + EXPECT_NO_CALL(stub_mock_open_virtual_disk); + EXPECT_NO_CALL(stub_mock_resize_virtual_disk); + EXPECT_NO_CALL(stub_mock_get_virtual_disk_information); + EXPECT_NO_CALL(stub_mock_close_handle); + } + + // Set of placeholder mocks in order to catch *unexpected* calls. + ::testing::MockFunction stub_mock_create_virtual_disk; + ::testing::MockFunction stub_mock_open_virtual_disk; + ::testing::MockFunction stub_mock_resize_virtual_disk; + ::testing::MockFunction stub_mock_get_virtual_disk_information; + ::testing::MockFunction stub_mock_close_handle; + + // Initialize the API table with stub functions, so if any of these fire without + // our will, we'll know. + hyperv::virtdisk::VirtDiskAPITable mock_api_table{ + stub_mock_create_virtual_disk.AsStdFunction(), + stub_mock_open_virtual_disk.AsStdFunction(), + stub_mock_resize_virtual_disk.AsStdFunction(), + stub_mock_get_virtual_disk_information.AsStdFunction(), + stub_mock_close_handle.AsStdFunction(), + }; + + // Sentinel values as mock API parameters. These handles are opaque handles and + // they're not being dereferenced in any way -- only address values are compared. + inline static auto mock_handle_object = reinterpret_cast(0xbadf00d); +}; + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhdx_happy_path) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_virtual_disk; + ::testing::MockFunction mock_close_handle; + + mock_api_table.CreateVirtualDisk = mock_create_virtual_disk.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + PSECURITY_DESCRIPTOR SecurityDescriptor, + CREATE_VIRTUAL_DISK_FLAG Flags, + ULONG ProviderSpecificFlags, + PCREATE_VIRTUAL_DISK_PARAMETERS Parameters, + LPOVERLAPPED Overlapped, + PHANDLE Handle + + ) { + ASSERT_NE(nullptr, VirtualStorageType); + ASSERT_EQ(VirtualStorageType->DeviceId, VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN); + ASSERT_EQ(VirtualStorageType->VendorId, VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN); + ASSERT_NE(nullptr, Path); + ASSERT_STREQ(Path, L"test.vhdx"); + + ASSERT_EQ(VirtualDiskAccessMask, VIRTUAL_DISK_ACCESS_NONE); + ASSERT_EQ(nullptr, SecurityDescriptor); + ASSERT_EQ(CREATE_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_EQ(0, ProviderSpecificFlags); + ASSERT_NE(nullptr, Parameters); + ASSERT_EQ(Parameters->Version, CREATE_VIRTUAL_DISK_VERSION_2); + ASSERT_EQ(Parameters->Version2.MaximumSize, 2097152); + ASSERT_EQ(Parameters->Version2.BlockSizeInBytes, 1048576); + + ASSERT_EQ(nullptr, Overlapped); + ASSERT_NE(nullptr, Handle); + ASSERT_EQ(nullptr, *Handle); + + *Handle = mock_handle_object; + }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = "test.vhdx"; + params.size_in_bytes = 2097152; + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.create_virtual_disk(params); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhd_happy_path) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_virtual_disk; + ::testing::MockFunction mock_close_handle; + + mock_api_table.CreateVirtualDisk = mock_create_virtual_disk.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + PSECURITY_DESCRIPTOR SecurityDescriptor, + CREATE_VIRTUAL_DISK_FLAG Flags, + ULONG ProviderSpecificFlags, + PCREATE_VIRTUAL_DISK_PARAMETERS Parameters, + LPOVERLAPPED Overlapped, + PHANDLE Handle + + ) { + ASSERT_NE(nullptr, VirtualStorageType); + ASSERT_EQ(VirtualStorageType->DeviceId, VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN); + ASSERT_EQ(VirtualStorageType->VendorId, VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN); + ASSERT_NE(nullptr, Path); + ASSERT_STREQ(Path, L"test.vhd"); + ASSERT_EQ(VirtualDiskAccessMask, VIRTUAL_DISK_ACCESS_NONE); + ASSERT_EQ(nullptr, SecurityDescriptor); + ASSERT_EQ(CREATE_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_EQ(0, ProviderSpecificFlags); + ASSERT_NE(nullptr, Parameters); + ASSERT_EQ(Parameters->Version, CREATE_VIRTUAL_DISK_VERSION_2); + ASSERT_EQ(Parameters->Version2.MaximumSize, 2097152); + ASSERT_EQ(Parameters->Version2.BlockSizeInBytes, 524288); + ASSERT_EQ(nullptr, Overlapped); + ASSERT_NE(nullptr, Handle); + ASSERT_EQ(nullptr, *Handle); + + *Handle = mock_handle_object; + }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = "test.vhd"; + params.size_in_bytes = 2097152; + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.create_virtual_disk(params); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_create_virtual_disk; + + mock_api_table.CreateVirtualDisk = mock_create_virtual_disk.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_create_virtual_disk, Call) + .WillOnce(DoAll([](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + PSECURITY_DESCRIPTOR SecurityDescriptor, + CREATE_VIRTUAL_DISK_FLAG Flags, + ULONG ProviderSpecificFlags, + PCREATE_VIRTUAL_DISK_PARAMETERS Parameters, + LPOVERLAPPED Overlapped, + PHANDLE Handle + + ) {}, + Return(ERROR_PATH_NOT_FOUND))); + } + + hyperv::virtdisk::CreateVirtualDiskParameters params{}; + params.path = "test.vhd"; + params.size_in_bytes = 2097152; + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.create_virtual_disk(params); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"CreateVirtualDisk failed with 3!"); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_happy_path) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_virtual_disk; + ::testing::MockFunction mock_resize_virtual_disk; + ::testing::MockFunction mock_close_handle; + + mock_api_table.OpenVirtualDisk = mock_open_virtual_disk.AsStdFunction(); + mock_api_table.ResizeVirtualDisk = mock_resize_virtual_disk.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + POPEN_VIRTUAL_DISK_PARAMETERS Parameters, + PHANDLE Handle) { + ASSERT_NE(nullptr, VirtualStorageType); + ASSERT_EQ(VirtualStorageType->DeviceId, VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN); + ASSERT_EQ(VirtualStorageType->VendorId, VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN); + ASSERT_NE(nullptr, Path); + ASSERT_STREQ(Path, L"test.vhdx"); + ASSERT_EQ(VIRTUAL_DISK_ACCESS_ALL, VirtualDiskAccessMask); + ASSERT_EQ(OPEN_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_EQ(nullptr, Parameters); + ASSERT_NE(nullptr, Handle); + ASSERT_EQ(nullptr, *Handle); + + *Handle = mock_handle_object; + }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_resize_virtual_disk, Call) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + RESIZE_VIRTUAL_DISK_FLAG Flags, + PRESIZE_VIRTUAL_DISK_PARAMETERS Parameters, + LPOVERLAPPED Overlapped) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_EQ(RESIZE_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_NE(nullptr, Parameters); + ASSERT_EQ(Parameters->Version, RESIZE_VIRTUAL_DISK_VERSION_1); + ASSERT_EQ(Parameters->Version1.NewSize, 1234567); + ASSERT_EQ(nullptr, Overlapped); + }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_open_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_virtual_disk; + + mock_api_table.OpenVirtualDisk = mock_open_virtual_disk.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + POPEN_VIRTUAL_DISK_PARAMETERS Parameters, + PHANDLE Handle) { + + }, + Return(ERROR_PATH_NOT_FOUND))); + } + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"open_virtual_disk failed!"); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_resize_failed) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_virtual_disk; + ::testing::MockFunction mock_resize_virtual_disk; + ::testing::MockFunction mock_close_handle; + + mock_api_table.OpenVirtualDisk = mock_open_virtual_disk.AsStdFunction(); + mock_api_table.ResizeVirtualDisk = mock_resize_virtual_disk.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_virtual_disk, Call) + .WillOnce(DoAll([](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + POPEN_VIRTUAL_DISK_PARAMETERS Parameters, + PHANDLE Handle) { *Handle = mock_handle_object; }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_resize_virtual_disk, Call) + .WillOnce(DoAll([](HANDLE VirtualDiskHandle, + RESIZE_VIRTUAL_DISK_FLAG Flags, + PRESIZE_VIRTUAL_DISK_PARAMETERS Parameters, + LPOVERLAPPED Overlapped) {}, + Return(ERROR_INVALID_PARAMETER))); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + { + uut_t uut{mock_api_table}; + const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); + ASSERT_FALSE(status); + ASSERT_FALSE(status_msg.empty()); + ASSERT_STREQ(status_msg.c_str(), L"ResizeVirtualDisk failed with 87!"); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_happy_path) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_virtual_disk; + ::testing::MockFunction mock_get_virtual_disk_information; + ::testing::MockFunction mock_close_handle; + + mock_api_table.OpenVirtualDisk = mock_open_virtual_disk.AsStdFunction(); + mock_api_table.GetVirtualDiskInformation = mock_get_virtual_disk_information.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + POPEN_VIRTUAL_DISK_PARAMETERS Parameters, + PHANDLE Handle) { + ASSERT_NE(nullptr, VirtualStorageType); + ASSERT_EQ(VirtualStorageType->DeviceId, VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN); + ASSERT_EQ(VirtualStorageType->VendorId, VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN); + ASSERT_NE(nullptr, Path); + ASSERT_STREQ(Path, L"test.vhdx"); + ASSERT_EQ(VIRTUAL_DISK_ACCESS_ALL, VirtualDiskAccessMask); + ASSERT_EQ(OPEN_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_EQ(nullptr, Parameters); + ASSERT_NE(nullptr, Handle); + ASSERT_EQ(nullptr, *Handle); + + *Handle = mock_handle_object; + }, + Return(ERROR_SUCCESS))); + + // The API will be called for several times. + EXPECT_CALL(mock_get_virtual_disk_information, Call) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_SIZE, VirtualDiskInfo->Version); + VirtualDiskInfo->Size.VirtualSize = 1111111; + VirtualDiskInfo->Size.BlockSize = 2222222; + VirtualDiskInfo->Size.PhysicalSize = 3333333; + VirtualDiskInfo->Size.SectorSize = 4444444; + }, + Return(ERROR_SUCCESS))) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_VIRTUAL_STORAGE_TYPE, VirtualDiskInfo->Version); + VirtualDiskInfo->VirtualStorageType.DeviceId = VIRTUAL_STORAGE_TYPE_DEVICE_VHDX; + VirtualDiskInfo->VirtualStorageType.VendorId = VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN; + }, + Return(ERROR_SUCCESS))) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_SMALLEST_SAFE_VIRTUAL_SIZE, VirtualDiskInfo->Version); + VirtualDiskInfo->SmallestSafeVirtualSize = 123456; + }, + Return(ERROR_SUCCESS))) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_PROVIDER_SUBTYPE, VirtualDiskInfo->Version); + VirtualDiskInfo->ProviderSubtype = 3; // dynamic + }, + Return(ERROR_SUCCESS))); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + { + uut_t uut{mock_api_table}; + hyperv::virtdisk::VirtualDiskInfo info{}; + const auto& [status, status_msg] = uut.get_virtual_disk_info("test.vhdx", info); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + + ASSERT_TRUE(info.size.has_value()); + ASSERT_TRUE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.smallest_safe_virtual_size.has_value()); + ASSERT_TRUE(info.provider_subtype.has_value()); + + ASSERT_EQ(info.size->virtual_, 1111111); + ASSERT_EQ(info.size->block, 2222222); + ASSERT_EQ(info.size->physical, 3333333); + ASSERT_EQ(info.size->sector, 4444444); + + ASSERT_STREQ(info.virtual_storage_type.value().c_str(), "vhdx"); + ASSERT_EQ(info.smallest_safe_virtual_size.value(), 123456); + ASSERT_STREQ(info.provider_subtype.value().c_str(), "dynamic"); + } +} + +// --------------------------------------------------------- + +TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_fail_some) +{ + /****************************************************** + * Override the default mock functions. + ******************************************************/ + ::testing::MockFunction mock_open_virtual_disk; + ::testing::MockFunction mock_get_virtual_disk_information; + ::testing::MockFunction mock_close_handle; + + mock_api_table.OpenVirtualDisk = mock_open_virtual_disk.AsStdFunction(); + mock_api_table.GetVirtualDiskInformation = mock_get_virtual_disk_information.AsStdFunction(); + mock_api_table.CloseHandle = mock_close_handle.AsStdFunction(); + + /****************************************************** + * Verify that the dependencies are called with right + * data. + ******************************************************/ + { + EXPECT_CALL(mock_open_virtual_disk, Call) + .WillOnce(DoAll( + [](PVIRTUAL_STORAGE_TYPE VirtualStorageType, + PCWSTR Path, + VIRTUAL_DISK_ACCESS_MASK VirtualDiskAccessMask, + OPEN_VIRTUAL_DISK_FLAG Flags, + POPEN_VIRTUAL_DISK_PARAMETERS Parameters, + PHANDLE Handle) { + ASSERT_NE(nullptr, VirtualStorageType); + ASSERT_EQ(VirtualStorageType->DeviceId, VIRTUAL_STORAGE_TYPE_DEVICE_UNKNOWN); + ASSERT_EQ(VirtualStorageType->VendorId, VIRTUAL_STORAGE_TYPE_VENDOR_UNKNOWN); + ASSERT_NE(nullptr, Path); + ASSERT_STREQ(Path, L"test.vhdx"); + ASSERT_EQ(VIRTUAL_DISK_ACCESS_ALL, VirtualDiskAccessMask); + ASSERT_EQ(OPEN_VIRTUAL_DISK_FLAG_NONE, Flags); + ASSERT_EQ(nullptr, Parameters); + ASSERT_NE(nullptr, Handle); + ASSERT_EQ(nullptr, *Handle); + + *Handle = mock_handle_object; + }, + Return(ERROR_SUCCESS))); + + // The API will be called for several times. + EXPECT_CALL(mock_get_virtual_disk_information, Call) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_SIZE, VirtualDiskInfo->Version); + VirtualDiskInfo->Size.VirtualSize = 1111111; + VirtualDiskInfo->Size.BlockSize = 2222222; + VirtualDiskInfo->Size.PhysicalSize = 3333333; + VirtualDiskInfo->Size.SectorSize = 4444444; + }, + Return(ERROR_SUCCESS))) + .WillOnce(Return(ERROR_INVALID_PARAMETER)) + .WillOnce(DoAll( + [](HANDLE VirtualDiskHandle, + PULONG VirtualDiskInfoSize, + PGET_VIRTUAL_DISK_INFO VirtualDiskInfo, + PULONG SizeUsed) { + ASSERT_EQ(mock_handle_object, VirtualDiskHandle); + ASSERT_NE(nullptr, VirtualDiskInfoSize); + ASSERT_EQ(sizeof(GET_VIRTUAL_DISK_INFO), *VirtualDiskInfoSize); + ASSERT_NE(nullptr, VirtualDiskInfo); + ASSERT_EQ(nullptr, SizeUsed); + ASSERT_EQ(GET_VIRTUAL_DISK_INFO_SMALLEST_SAFE_VIRTUAL_SIZE, VirtualDiskInfo->Version); + VirtualDiskInfo->SmallestSafeVirtualSize = 123456; + }, + Return(ERROR_SUCCESS))) + .WillOnce(Return(ERROR_INVALID_PARAMETER)); + + EXPECT_CALL(mock_close_handle, Call) + .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + } + + { + uut_t uut{mock_api_table}; + hyperv::virtdisk::VirtualDiskInfo info{}; + const auto& [status, status_msg] = uut.get_virtual_disk_info("test.vhdx", info); + ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); + + ASSERT_TRUE(info.size.has_value()); + ASSERT_FALSE(info.virtual_storage_type.has_value()); + ASSERT_TRUE(info.smallest_safe_virtual_size.has_value()); + ASSERT_FALSE(info.provider_subtype.has_value()); + + ASSERT_EQ(info.size->virtual_, 1111111); + ASSERT_EQ(info.size->block, 2222222); + ASSERT_EQ(info.size->physical, 3333333); + ASSERT_EQ(info.size->sector, 4444444); + + ASSERT_EQ(info.smallest_safe_virtual_size.value(), 123456); + } +} + +} // namespace multipass::test From 42581845b8b77739019e432c3c19c25a9327d49c Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 12:54:03 +0300 Subject: [PATCH 11/26] [hyperv-hcn] removed firewall vm-creator-id parameter it's redundant for multipass's use case and it's not present in older versions of the API --- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 10 +------ .../hcn/hyperv_hcn_create_endpoint_params.h | 12 ++------- tests/hyperv_api/test_it_hyperv_hcn_api.cpp | 1 - tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 26 +++---------------- 4 files changed, 6 insertions(+), 43 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 55a8fd1ba7..9abc206149 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -101,17 +101,10 @@ constexpr auto endpoint_settings_template = LR"( }}, "HostComputeNetwork": "{0}", "Policies": [ - {{ - "Type": "Firewall", - "Settings": {{ - "VmCreatorId": "{1}", - "PolicyFlags": 0 - }} - }} ], "IpConfigurations": [ {{ - "IpAddress": "{2}" + "IpAddress": "{1}" }} ] }})"; @@ -254,7 +247,6 @@ OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& para // Render the template const auto endpoint_settings = fmt::format(endpoint_settings_template, string_to_wstring(params.network_guid), - string_to_wstring(params.vm_creator_id), string_to_wstring(params.endpoint_ipvx_addr)); HCN_ENDPOINT endpoint{nullptr}; const auto result = perform_hcn_operation(api, diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h index aa522d7808..9d8e23a044 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h @@ -43,13 +43,6 @@ struct CreateEndpointParameters */ std::string endpoint_guid; - /** - * An unique identifier that distinguishes the creator of - * this endpoint. This allows tracing the created endpoints - * back to the its' creator for diagnostics. - */ - std::string vm_creator_id; - /** * The IPv[4-6] address to assign to the endpoint. */ @@ -73,11 +66,10 @@ struct fmt::formatter auto format(const multipass::hyperv::hcn::CreateEndpointParameters& params, FormatContext& ctx) const { return format_to(ctx.out(), - "Endpoint GUID: ({}) | Network GUID: ({}) | Endpoint IPvX Addr.: ({}) | VM Creator ID: ({})", + "Endpoint GUID: ({}) | Network GUID: ({}) | Endpoint IPvX Addr.: ({})", params.endpoint_guid, params.network_guid, - params.endpoint_ipvx_addr, - params.vm_creator_id); + params.endpoint_ipvx_addr); } }; diff --git a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp index e469a29fd7..947d2469aa 100644 --- a/tests/hyperv_api/test_it_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_it_hyperv_hcn_api.cpp @@ -67,7 +67,6 @@ TEST_F(HyperVHCNAPI_IntegrationTests, create_delete_endpoint) endpoint_params.network_guid = network_params.guid; endpoint_params.endpoint_guid = "b70c479d-f808-4053-aafa-705bc15b6d70"; - endpoint_params.vm_creator_id = R"(456fe1ac-1e46-49ec-bb9f-5e44cc2de23b)"; endpoint_params.endpoint_ipvx_addr = "172.50.224.2"; (void)uut.delete_network(network_params.guid); diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index c8cd045f75..8c7b3f805d 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -433,13 +433,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) }, "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", "Policies": [ - { - "Type": "Firewall", - "Settings": { - "VmCreatorId": "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e", - "PolicyFlags": 0 - } - } ], "IpConfigurations": [ { @@ -484,8 +477,7 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) logger_scope.mock_logger->expect_log( mpl::Level::trace, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " - "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " - "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); logger_scope.mock_logger->expect_log(mpl::Level::trace, @@ -502,7 +494,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; params.endpoint_ipvx_addr = "172.50.224.27"; - params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; const auto& [success, error_msg] = uut.create_endpoint(params); ASSERT_TRUE(success); @@ -535,8 +526,7 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_open_network_failed) logger_scope.mock_logger->expect_log( mpl::Level::trace, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " - "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " - "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); logger_scope.mock_logger->expect_log(mpl::Level::error, @@ -553,7 +543,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_open_network_failed) params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; params.endpoint_ipvx_addr = "172.50.224.27"; - params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; const auto& [status, error_msg] = uut.create_endpoint(params); ASSERT_FALSE(status); @@ -599,13 +588,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) }, "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", "Policies": [ - { - "Type": "Firewall", - "Settings": { - "VmCreatorId": "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e", - "PolicyFlags": 0 - } - } ], "IpConfigurations": [ { @@ -647,8 +629,7 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) logger_scope.mock_logger->expect_log( mpl::Level::trace, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " - "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27) | VM " - "Creator ID: (df8dff56-0ab7-4cbb-869b-b8e2220d4b3e)"); + "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); @@ -664,7 +645,6 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) params.endpoint_guid = "77c27c1e-8204-437d-a7cc-fb4ce1614819"; params.network_guid = "b70c479d-f808-4053-aafa-705bc15b6d68"; params.endpoint_ipvx_addr = "172.50.224.27"; - params.vm_creator_id = "df8dff56-0ab7-4cbb-869b-b8e2220d4b3e"; const auto& [success, error_msg] = uut.create_endpoint(params); ASSERT_FALSE(success); From a0641557734eb320947e9aa941a53500749c06ea Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 14:17:12 +0300 Subject: [PATCH 12/26] [hyperv-virtdisk] verify logs in unit tests --- .../virtdisk/virtdisk_api_wrapper.cpp | 7 +++ .../virtdisk/virtdisk_api_wrapper.h | 1 - tests/hyperv_api/test_ut_hyperv_virtdisk.cpp | 49 +++++++++++++++++++ 3 files changed, 56 insertions(+), 1 deletion(-) diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index cb512955b2..881c44930e 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -155,6 +155,9 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara return OperationResult{NOERROR, L""}; } + mpl::log(lvl::error, + kLogCategory, + fmt::format("create_virtual_disk(...) > CreateVirtualDisk failed with {}!", result)); return OperationResult{E_FAIL, fmt::format(L"CreateVirtualDisk failed with {}!", result)}; } @@ -195,6 +198,10 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path return OperationResult{NOERROR, L""}; } + mpl::log(lvl::error, + kLogCategory, + fmt::format("resize_virtual_disk(...) > ResizeVirtualDisk failed with {}!", resize_result)); + return OperationResult{E_FAIL, fmt::format(L"ResizeVirtualDisk failed with {}!", resize_result)}; } diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h index 21d439f8b0..73d215c512 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -22,7 +22,6 @@ #include "virtdisk_disk_info.h" #include "virtdisk_wrapper_interface.h" - namespace multipass::hyperv::virtdisk { diff --git a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp index 0c21687f20..7c6a7bbfb6 100644 --- a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp @@ -138,6 +138,11 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhdx_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhdx)"); } hyperv::virtdisk::CreateVirtualDiskParameters params{}; @@ -206,6 +211,11 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhd_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhd)"); } hyperv::virtdisk::CreateVirtualDiskParameters params{}; @@ -249,6 +259,13 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_failed) ) {}, Return(ERROR_PATH_NOT_FOUND))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhd)"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "create_virtual_disk(...) > CreateVirtualDisk failed with 3!"); } hyperv::virtdisk::CreateVirtualDiskParameters params{}; @@ -324,6 +341,12 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); } { @@ -361,6 +384,14 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_open_failed) }, Return(ERROR_PATH_NOT_FOUND))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "open_virtual_disk(...) > OpenVirtualDisk failed with: 3"); } { @@ -410,6 +441,14 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_resize_failed) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log( + mpl::Level::trace, + "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::error, + "resize_virtual_disk(...) > ResizeVirtualDisk failed with 87!"); } { @@ -529,6 +568,10 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); } { @@ -635,6 +678,12 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_fail_some) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + + logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::warning, "get_virtual_disk_info(...) > failed to get 6"); + logger_scope.mock_logger->expect_log(mpl::Level::warning, "get_virtual_disk_info(...) > failed to get 7"); } { From de8aaac1de1cca7c496ab264a306e5669b1a3033 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 15:45:34 +0300 Subject: [PATCH 13/26] [hyperv-virtdisk] fix header guard name for virtdisk_api_wrapper.h --- .../backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h index 73d215c512..95b5e38529 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -15,8 +15,8 @@ * */ -#ifndef MULTIPASS_HYPERV_API_HCS_WRAPPER -#define MULTIPASS_HYPERV_API_HCS_WRAPPER +#ifndef MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_H +#define MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_H #include "virtdisk_api_table.h" #include "virtdisk_disk_info.h" From 04e1da1737bd6e7d92a42aff690dce3f245742ae Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Mon, 17 Feb 2025 17:58:52 +0300 Subject: [PATCH 14/26] [hyperv-api-tests] add initial component integration tests move common test utilitiest to hyperv_test_utils.h --- tests/hyperv_api/CMakeLists.txt | 1 + tests/hyperv_api/hyperv_test_utils.h | 71 ++++++++++ tests/hyperv_api/test_bb_cit_hyperv.cpp | 138 +++++++++++++++++++ tests/hyperv_api/test_it_hyperv_virtdisk.cpp | 32 +---- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 3 +- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 10 +- tests/hyperv_api/test_ut_hyperv_virtdisk.cpp | 4 +- 7 files changed, 214 insertions(+), 45 deletions(-) create mode 100644 tests/hyperv_api/hyperv_test_utils.h create mode 100644 tests/hyperv_api/test_bb_cit_hyperv.cpp diff --git a/tests/hyperv_api/CMakeLists.txt b/tests/hyperv_api/CMakeLists.txt index 5c21460858..c55bc03c34 100644 --- a/tests/hyperv_api/CMakeLists.txt +++ b/tests/hyperv_api/CMakeLists.txt @@ -23,5 +23,6 @@ if(WIN32) ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_hcs_api.cpp ${CMAKE_CURRENT_LIST_DIR}/test_it_hyperv_virtdisk.cpp ${CMAKE_CURRENT_LIST_DIR}/test_ut_hyperv_virtdisk.cpp + ${CMAKE_CURRENT_LIST_DIR}/test_bb_cit_hyperv.cpp ) endif() diff --git a/tests/hyperv_api/hyperv_test_utils.h b/tests/hyperv_api/hyperv_test_utils.h new file mode 100644 index 0000000000..0aa9829a38 --- /dev/null +++ b/tests/hyperv_api/hyperv_test_utils.h @@ -0,0 +1,71 @@ +/* + * 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_TESTS_HYPERV_API_HYPERV_TEST_UTILS_H +#define MULTIPASS_TESTS_HYPERV_API_HYPERV_TEST_UTILS_H + +#include +#include + +#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) + +namespace multipass::test +{ + +inline auto trim_whitespace(const wchar_t* input) +{ + std::wstring str{input}; + str.erase(std::remove_if(str.begin(), str.end(), ::iswspace), str.end()); + return str; +} + +inline auto make_tempfile_path(std::string extension) +{ + + struct auto_remove_path + { + + auto_remove_path(std::filesystem::path p) : path(p) + { + } + + ~auto_remove_path() + { + std::error_code ec{}; + std::filesystem::remove(path, ec); + } + + operator const std::filesystem::path&() const& noexcept + { + return path; + } + + operator const std::filesystem::path&() const&& noexcept = delete; + + private: + const std::filesystem::path path; + }; + char pattern[] = "temp-XXXXXX"; + _mktemp_s(pattern); + std::string f = pattern; + f.append(extension); + return auto_remove_path{std::filesystem::temp_directory_path() / f}; +} + +} // namespace multipass::test + +#endif diff --git a/tests/hyperv_api/test_bb_cit_hyperv.cpp b/tests/hyperv_api/test_bb_cit_hyperv.cpp new file mode 100644 index 0000000000..3d12950733 --- /dev/null +++ b/tests/hyperv_api/test_bb_cit_hyperv.cpp @@ -0,0 +1,138 @@ +/* + * 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 "hyperv_test_utils.h" +#include "tests/common.h" + +#include + +#include +#include +#include +#include +#include + +namespace multipass::test +{ + +using hcn_wrapper_t = hyperv::hcn::HCNWrapper; +using hcs_wrapper_t = hyperv::hcs::HCSWrapper; +using virtdisk_wrapper_t = multipass::hyperv::virtdisk::VirtDiskWrapper; + +// Component level big bang integration tests for Hyper-V HCN/HCS + virtdisk API's. +// These tests ensure that the API's working together as expected. +struct HyperV_ComponentIntegrationTests : public ::testing::Test +{ +}; + +TEST_F(HyperV_ComponentIntegrationTests, spawn_empty_test_vm) +{ + hcn_wrapper_t hcn{}; + hcs_wrapper_t hcs{}; + virtdisk_wrapper_t virtdisk{}; + // 10.0. 0.0 to 10.255. 255.255. + const auto network_parameters = []() { + hyperv::hcn::CreateNetworkParameters network_parameters{}; + network_parameters.name = "multipass-hyperv-cit"; + network_parameters.guid = "b4d77a0e-2507-45f0-99aa-c638f3e47486"; + network_parameters.subnet = "10.99.99.0/24"; + network_parameters.gateway = "10.99.99.1"; + return network_parameters; + }(); + + const auto endpoint_parameters = [&network_parameters]() { + hyperv::hcn::CreateEndpointParameters endpoint_parameters{}; + endpoint_parameters.network_guid = network_parameters.guid; + endpoint_parameters.endpoint_guid = "aee79cf9-54d1-4653-81fb-8110db97029f"; + endpoint_parameters.endpoint_ipvx_addr = "10.99.99.10"; + return endpoint_parameters; + }(); + + // Remove remnants from previous tests, if any. + (void)hcn.delete_endpoint(endpoint_parameters.endpoint_guid); + (void)hcn.delete_network(network_parameters.guid); + + const auto temp_path = make_tempfile_path(".vhdx"); + + const auto create_disk_parameters = [&temp_path]() { + hyperv::virtdisk::CreateVirtualDiskParameters create_disk_parameters{}; + create_disk_parameters.path = temp_path; + create_disk_parameters.size_in_bytes = (1024 * 1024) * 512; // 512 MiB + return create_disk_parameters; + }(); + + const auto create_vm_parameters = []() { + hyperv::hcs::CreateComputeSystemParameters vm_parameters{}; + vm_parameters.name = "multipass-hyperv-cit-vm"; + vm_parameters.processor_count = 1; + vm_parameters.memory_size_mb = 512; + return vm_parameters; + }(); + + (void)hcs.terminate_compute_system(create_vm_parameters.name); + + const auto add_endpoint_parameters = [&create_vm_parameters, &endpoint_parameters]() { + hyperv::hcs::AddEndpointParameters add_endpoint_parameters{}; + add_endpoint_parameters.endpoint_guid = endpoint_parameters.endpoint_guid; + add_endpoint_parameters.target_compute_system_name = create_vm_parameters.name; + add_endpoint_parameters.nic_mac_address = "00-15-5D-9D-CF-69"; + return add_endpoint_parameters; + }(); + + // Create the test network + { + const auto& [status, status_msg] = hcn.create_network(network_parameters); + ASSERT_TRUE(status); + } + + // Create the test endpoint + { + const auto& [status, status_msg] = hcn.create_endpoint(endpoint_parameters); + ASSERT_TRUE(status); + } + + // Create the test VHDX (empty) + { + const auto& [status, status_msg] = virtdisk.create_virtual_disk(create_disk_parameters); + ASSERT_TRUE(status); + } + + // Create test VM + { + const auto& [status, status_msg] = hcs.create_compute_system(create_vm_parameters); + ASSERT_TRUE(status); + } + + // Start test VM + { + const auto& [status, status_msg] = hcs.start_compute_system(create_vm_parameters.name); + ASSERT_TRUE(status); + } + + // Add endpoint + { + const auto& [status, status_msg] = hcs.add_endpoint(add_endpoint_parameters); + fmt::print(L"{}", status_msg.c_str()); + ASSERT_TRUE(status); + } + + (void)hcs.terminate_compute_system(create_vm_parameters.name); + (void)hcn.delete_endpoint(endpoint_parameters.endpoint_guid); + (void)hcn.delete_network(network_parameters.guid); +} + +} // namespace multipass::test diff --git a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp index 5e7719fde9..a55e7256a4 100644 --- a/tests/hyperv_api/test_it_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_it_hyperv_virtdisk.cpp @@ -15,6 +15,7 @@ * */ +#include "hyperv_test_utils.h" #include "tests/common.h" #include @@ -33,37 +34,6 @@ struct HyperVVirtDisk_IntegrationTests : public ::testing::Test { }; -struct auto_remove_path -{ - - auto_remove_path(std::filesystem::path p) : path(p) - { - } - - ~auto_remove_path() - { - std::error_code ec{}; - std::filesystem::remove(path, ec); - } - - operator std::filesystem::path() const noexcept - { - return path; - } - -private: - const std::filesystem::path path; -}; - -auto_remove_path make_tempfile_path(std::string extension) -{ - char pattern[] = "temp-XXXXXX"; - _mktemp_s(pattern); - std::string f = pattern; - f.append(extension); - return {std::filesystem::temp_directory_path() / f}; -} - TEST_F(HyperVVirtDisk_IntegrationTests, create_virtual_disk_vhdx) { auto temp_path = make_tempfile_path(".vhdx"); diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index 8c7b3f805d..01366b4730 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -16,6 +16,7 @@ */ #include "hyperv_api/hcn/hyperv_hcn_api_table.h" +#include "hyperv_test_utils.h" #include "tests/mock_logger.h" #include "gmock/gmock.h" @@ -34,8 +35,6 @@ namespace mpl = multipass::logging; using testing::DoAll; using testing::Return; -#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) - namespace multipass::test { diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp index 0931aea994..cf4a9b7713 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -18,6 +18,7 @@ #include "hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h" #include "hyperv_api/hcs/hyperv_hcs_api_wrapper.h" #include "hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h" +#include "hyperv_test_utils.h" #include "tests/mock_logger.h" #include "gmock/gmock.h" @@ -33,15 +34,6 @@ namespace mpl = multipass::logging; using testing::DoAll; using testing::Return; -#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) - -static auto trim_whitespace(const wchar_t* input) -{ - std::wstring str{input}; - str.erase(std::remove_if(str.begin(), str.end(), ::iswspace), str.end()); - return str; -} - namespace multipass::test { using uut_t = hyperv::hcs::HCSWrapper; diff --git a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp index 7c6a7bbfb6..e0c041a72d 100644 --- a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp @@ -15,17 +15,15 @@ * */ +#include "hyperv_test_utils.h" #include "tests/common.h" #include "tests/mock_logger.h" #include -#include - #include #include -#define EXPECT_NO_CALL(mock) EXPECT_CALL(mock, Call).Times(0) namespace mpt = multipass::test; namespace mpl = multipass::logging; From 00d345c74e22d97cd98a2553f1e1d4f549287582 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Feb 2025 10:36:11 +0300 Subject: [PATCH 15/26] [hyperv-api-table] do not rely on rtti for printing api function bind status --- .../hyperv_api/hcn/hyperv_hcn_api_table.h | 12 +++---- .../hyperv_api/hcs/hyperv_hcs_api_table.h | 36 +++++++++---------- .../hyperv_api/virtdisk/virtdisk_api_table.h | 10 +++--- 3 files changed, 28 insertions(+), 30 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index c03a7d48d6..08a31e4227 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -75,12 +75,12 @@ struct fmt::formatter return format_to(ctx.out(), "CreateNetwork: ({}) | OpenNetwork: ({}) | DeleteNetwork: ({}) | CreateEndpoint: ({}) | " "OpenEndpoint: ({}) | DeleteEndpoint: ({})", - fmt::ptr(api.CreateNetwork.target()), - fmt::ptr(api.OpenNetwork.target()), - fmt::ptr(api.DeleteNetwork.target()), - fmt::ptr(api.CreateEndpoint.target()), - fmt::ptr(api.OpenEndpoint.target()), - fmt::ptr(api.DeleteEndpoint.target())); + static_cast(api.CreateNetwork), + static_cast(api.OpenNetwork), + static_cast(api.DeleteNetwork), + static_cast(api.CreateEndpoint), + static_cast(api.OpenEndpoint), + static_cast(api.DeleteEndpoint)); } }; diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index 3dc0722a85..279bd7c5d3 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -98,25 +98,23 @@ struct fmt::formatter { return format_to(ctx.out(), "CreateOperation: ({}) | WaitForOperationResult: ({}) | CreateComputeSystem: ({}) | " - "OpenComputeSystem: ({}) | " - "StartComputeSystem: ({}) | ShutDownComputeSystem: ({}) | PauseComputeSystem: ({}) | " - "ResumeComputeSystem: ({}) | ModifyComputeSystem: ({}) | GetComputeSystemProperties: ({}) | " - "GrantVmAccess: ({}) | RevokeVmAccess: ({}) | EnumerateComputeSystems: ({})", - fmt::ptr(api.CreateOperation.target()), - fmt::ptr(api.WaitForOperationResult.target()), - fmt::ptr(api.CreateComputeSystem.target()), - fmt::ptr(api.OpenComputeSystem.target()), - fmt::ptr(api.StartComputeSystem.target()), - fmt::ptr(api.ShutDownComputeSystem.target()), - fmt::ptr(api.PauseComputeSystem.target()), - fmt::ptr(api.ResumeComputeSystem.target()), - fmt::ptr(api.ModifyComputeSystem.target()), - fmt::ptr(api.GetComputeSystemProperties.target()), - fmt::ptr(api.GrantVmAccess.target()), - fmt::ptr(api.RevokeVmAccess.target()), - fmt::ptr(api.EnumerateComputeSystems.target()) - - ); + "OpenComputeSystem: ({}) | StartComputeSystem: ({}) | ShutDownComputeSystem: ({}) | " + "PauseComputeSystem: ({}) | ResumeComputeSystem: ({}) | ModifyComputeSystem: ({}) | " + "GetComputeSystemProperties: ({}) | GrantVmAccess: ({}) | RevokeVmAccess: ({}) | " + "EnumerateComputeSystems: ({})", + static_cast(api.CreateOperation), + static_cast(api.WaitForOperationResult), + static_cast(api.CreateComputeSystem), + static_cast(api.OpenComputeSystem), + static_cast(api.StartComputeSystem), + static_cast(api.ShutDownComputeSystem), + static_cast(api.PauseComputeSystem), + static_cast(api.ResumeComputeSystem), + static_cast(api.ModifyComputeSystem), + static_cast(api.GetComputeSystemProperties), + static_cast(api.GrantVmAccess), + static_cast(api.RevokeVmAccess), + static_cast(api.EnumerateComputeSystems)); } }; diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h index 0f794b66a0..b62f5f3bf0 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h @@ -68,11 +68,11 @@ struct fmt::formatter return format_to(ctx.out(), "CreateVirtualDisk: ({}) | OpenVirtualDisk ({}) | ResizeVirtualDisk: ({}) | " "GetVirtualDiskInformation: ({}) | CloseHandle: ({})", - fmt::ptr(api.CreateVirtualDisk.target()), - fmt::ptr(api.OpenVirtualDisk.target()), - fmt::ptr(api.ResizeVirtualDisk.target()), - fmt::ptr(api.GetVirtualDiskInformation.target()), - fmt::ptr(api.CloseHandle.target())); + static_cast(api.CreateVirtualDisk), + static_cast(api.OpenVirtualDisk), + static_cast(api.ResizeVirtualDisk), + static_cast(api.GetVirtualDiskInformation), + static_cast(api.CloseHandle)); } }; From 1deb854d751b8ca023a9e1ac02efa318e3f794cd Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Feb 2025 11:19:52 +0300 Subject: [PATCH 16/26] [hyperv-api] more grooming --- src/platform/CMakeLists.txt | 1 - .../hyperv_api/hcn/hyperv_hcn_api_table.h | 5 +- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 90 ++++++++------- .../hcn/hyperv_hcn_wrapper_interface.h | 1 - .../hyperv_api/hcs/hyperv_hcs_api_table.h | 5 +- .../virtdisk/virtdisk_wrapper_interface.h | 3 +- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 104 +++++++++--------- 7 files changed, 105 insertions(+), 104 deletions(-) diff --git a/src/platform/CMakeLists.txt b/src/platform/CMakeLists.txt index c3f50c168f..445f933fa7 100644 --- a/src/platform/CMakeLists.txt +++ b/src/platform/CMakeLists.txt @@ -12,7 +12,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . - function(add_target TARGET_NAME) if(LINUX) add_library(${TARGET_NAME} STATIC diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index 08a31e4227..9222879b2e 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -74,13 +74,14 @@ struct fmt::formatter { return format_to(ctx.out(), "CreateNetwork: ({}) | OpenNetwork: ({}) | DeleteNetwork: ({}) | CreateEndpoint: ({}) | " - "OpenEndpoint: ({}) | DeleteEndpoint: ({})", + "OpenEndpoint: ({}) | DeleteEndpoint: ({}) | CoTaskMemFree: ({})", static_cast(api.CreateNetwork), static_cast(api.OpenNetwork), static_cast(api.DeleteNetwork), static_cast(api.CreateEndpoint), static_cast(api.OpenEndpoint), - static_cast(api.DeleteEndpoint)); + static_cast(api.DeleteEndpoint), + static_cast(api.CoTaskMemFree)); } }; diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 9abc206149..d876e91a6a 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -26,7 +26,6 @@ #include // clang-format off -#include #include #include #include @@ -42,6 +41,7 @@ #include #include +#include namespace multipass::hyperv::hcn { @@ -65,52 +65,6 @@ constexpr auto kLogCategory = "HyperV-HCN-Wrapper"; // --------------------------------------------------------- -/** - * HcnCreateNetwork settings JSON template - */ -constexpr auto network_settings_template = LR"""( -{{ - "Name": "{0}", - "Type": "ICS", - "Subnets" : [ - {{ - "GatewayAddress": "{2}", - "AddressPrefix" : "{1}", - "IpSubnets" : [ - {{ - "IpAddressPrefix": "{1}" - }} - ] - }} - ], - "IsolateSwitch": true, - "Flags" : 265 -}} -)"""; - -// --------------------------------------------------------- - -/** - * HcnCreateEndpoint settings JSON template - */ -constexpr auto endpoint_settings_template = LR"( -{{ - "SchemaVersion": {{ - "Major": 2, - "Minor": 16 - }}, - "HostComputeNetwork": "{0}", - "Policies": [ - ], - "IpConfigurations": [ - {{ - "IpAddress": "{1}" - }} - ] -}})"; - -// --------------------------------------------------------- - /** * Perform a Host Compute Network API operation * @@ -198,6 +152,29 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params { mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); + /** + * HcnCreateNetwork settings JSON template + */ + constexpr auto network_settings_template = LR"""( + {{ + "Name": "{0}", + "Type": "ICS", + "Subnets" : [ + {{ + "GatewayAddress": "{2}", + "AddressPrefix" : "{1}", + "IpSubnets" : [ + {{ + "IpAddressPrefix": "{1}" + }} + ] + }} + ], + "IsolateSwitch": true, + "Flags" : 265 + }} + )"""; + // Render the template const auto network_settings = fmt::format(network_settings_template, string_to_wstring(params.name), @@ -244,6 +221,25 @@ OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& para return {E_POINTER, L"Could not open the network!"}; } + /** + * HcnCreateEndpoint settings JSON template + */ + constexpr auto endpoint_settings_template = LR"( + {{ + "SchemaVersion": {{ + "Major": 2, + "Minor": 16 + }}, + "HostComputeNetwork": "{0}", + "Policies": [ + ], + "IpConfigurations": [ + {{ + "IpAddress": "{1}" + }} + ] + }})"; + // Render the template const auto endpoint_settings = fmt::format(endpoint_settings_template, string_to_wstring(params.network_guid), diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h index 730f317aba..7281fc1719 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h @@ -21,7 +21,6 @@ #include "../hyperv_api_operation_result.h" #include -#include namespace multipass::hyperv::hcn { diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index 279bd7c5d3..2368fea7ab 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -101,7 +101,7 @@ struct fmt::formatter "OpenComputeSystem: ({}) | StartComputeSystem: ({}) | ShutDownComputeSystem: ({}) | " "PauseComputeSystem: ({}) | ResumeComputeSystem: ({}) | ModifyComputeSystem: ({}) | " "GetComputeSystemProperties: ({}) | GrantVmAccess: ({}) | RevokeVmAccess: ({}) | " - "EnumerateComputeSystems: ({})", + "EnumerateComputeSystems: ({}) | LocalFree: ({})", static_cast(api.CreateOperation), static_cast(api.WaitForOperationResult), static_cast(api.CreateComputeSystem), @@ -114,7 +114,8 @@ struct fmt::formatter static_cast(api.GetComputeSystemProperties), static_cast(api.GrantVmAccess), static_cast(api.RevokeVmAccess), - static_cast(api.EnumerateComputeSystems)); + static_cast(api.EnumerateComputeSystems), + static_cast(api.LocalFree)); } }; diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h index 0b34d7046d..2e2b15dfcb 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h @@ -28,8 +28,7 @@ namespace multipass::hyperv::virtdisk { /** - * - * + * Abstract interface for the virtdisk wrapper. */ struct VirtDiskWrapperInterface { diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index 01366b4730..54efd75d5a 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -131,29 +131,31 @@ TEST_F(HyperVHCNAPI_UnitTests, create_network_success) .WillOnce(DoAll( [&](REFGUID id, PCWSTR settings, PHCN_NETWORK network, PWSTR* error_record) { constexpr auto expected_network_settings = LR"""( -{ - "Name": "multipass-hyperv-api-hcn-create-test", - "Type": "ICS", - "Subnets" : [ - { - "GatewayAddress": "172.50.224.1", - "AddressPrefix" : "172.50.224.0/20", - "IpSubnets" : [ - { - "IpAddressPrefix": "172.50.224.0/20" - } - ] - } - ], - "IsolateSwitch": true, - "Flags" : 265 -} -)"""; + { + "Name": "multipass-hyperv-api-hcn-create-test", + "Type": "ICS", + "Subnets" : [ + { + "GatewayAddress": "172.50.224.1", + "AddressPrefix" : "172.50.224.0/20", + "IpSubnets" : [ + { + "IpAddressPrefix": "172.50.224.0/20" + } + ] + } + ], + "IsolateSwitch": true, + "Flags" : 265 + } + )"""; ASSERT_NE(nullptr, network); ASSERT_EQ(nullptr, *network); ASSERT_NE(nullptr, error_record); ASSERT_EQ(nullptr, *error_record); - ASSERT_STREQ(settings, expected_network_settings); + const auto config_no_whitespace = trim_whitespace(settings); + const auto expected_no_whitespace = trim_whitespace(expected_network_settings); + ASSERT_STREQ(config_no_whitespace.c_str(), expected_no_whitespace.c_str()); const auto guid_str = hyperv::guid_to_string(id); ASSERT_EQ("b70c479d-f808-4053-aafa-705bc15b6d68", guid_str); *network = mock_network_object; @@ -425,20 +427,20 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) .WillOnce(DoAll( [&](HCN_NETWORK network, REFGUID id, PCWSTR settings, PHCN_ENDPOINT endpoint, PWSTR* error_record) { constexpr auto expected_endpoint_settings = LR"""( -{ - "SchemaVersion": { - "Major": 2, - "Minor": 16 - }, - "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", - "Policies": [ - ], - "IpConfigurations": [ - { - "IpAddress": "172.50.224.27" - } - ] -})"""; + { + "SchemaVersion": { + "Major": 2, + "Minor": 16 + }, + "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", + "Policies": [ + ], + "IpConfigurations": [ + { + "IpAddress": "172.50.224.27" + } + ] + })"""; ASSERT_NE(nullptr, network); ASSERT_EQ(mock_network_object, network); @@ -446,7 +448,9 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) ASSERT_EQ(nullptr, *error_record); ASSERT_NE(nullptr, endpoint); ASSERT_EQ(nullptr, *endpoint); - ASSERT_STREQ(settings, expected_endpoint_settings); + const auto config_no_whitespace = trim_whitespace(settings); + const auto expected_no_whitespace = trim_whitespace(expected_endpoint_settings); + ASSERT_STREQ(config_no_whitespace.c_str(), expected_no_whitespace.c_str()); const auto endpoint_guid_str = hyperv::guid_to_string(id); ASSERT_EQ("77c27c1e-8204-437d-a7cc-fb4ce1614819", endpoint_guid_str); *endpoint = mock_endpoint_object; @@ -580,24 +584,26 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) .WillOnce(DoAll( [&](HCN_NETWORK network, REFGUID id, PCWSTR settings, PHCN_ENDPOINT endpoint, PWSTR* error_record) { constexpr auto expected_endpoint_settings = LR"""( -{ - "SchemaVersion": { - "Major": 2, - "Minor": 16 - }, - "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", - "Policies": [ - ], - "IpConfigurations": [ - { - "IpAddress": "172.50.224.27" - } - ] -})"""; + { + "SchemaVersion": { + "Major": 2, + "Minor": 16 + }, + "HostComputeNetwork": "b70c479d-f808-4053-aafa-705bc15b6d68", + "Policies": [ + ], + "IpConfigurations": [ + { + "IpAddress": "172.50.224.27" + } + ] + })"""; ASSERT_EQ(mock_network_object, network); ASSERT_NE(nullptr, error_record); - ASSERT_STREQ(settings, expected_endpoint_settings); + const auto config_no_whitespace = trim_whitespace(settings); + const auto expected_no_whitespace = trim_whitespace(expected_endpoint_settings); + ASSERT_STREQ(config_no_whitespace.c_str(), expected_no_whitespace.c_str()); const auto expected_endpoint_guid_str = hyperv::guid_to_string(id); ASSERT_EQ("77c27c1e-8204-437d-a7cc-fb4ce1614819", expected_endpoint_guid_str); *endpoint = mock_endpoint_object; From abb1b62be7a45f5ab48b5b4371bd332c38b17869 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 4 Mar 2025 17:35:20 +0300 Subject: [PATCH 17/26] [hyperv-api] switch to the format overload of mpl::log --- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 25 +++--- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 82 +++++++++++-------- .../virtdisk/virtdisk_api_wrapper.cpp | 29 +++---- 3 files changed, 70 insertions(+), 66 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index d876e91a6a..d0a4e593e4 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -101,9 +101,9 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, mpl::log(lvl::trace, kLogCategory, - fmt::format("perform_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result))); + "perform_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result)); // Error message is only valid when the operation resulted in an error. // Passing a nullptr is well-defined in "< C++23", but it's going to be @@ -126,13 +126,13 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, */ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) { - mpl::log(lvl::trace, kLogCategory, fmt::format("open_network(...) > network_guid: {} ", network_guid)); + mpl::log(lvl::trace, kLogCategory, "open_network(...) > network_guid: {} ", network_guid); HCN_NETWORK network{nullptr}; const auto result = perform_hcn_operation(api, api.OpenNetwork, guid_from_string(network_guid), &network); if (!result) { - mpl::log(lvl::error, kLogCategory, fmt::format("open_network() > HcnOpenNetwork failed with {}!", result.code)); + mpl::log(lvl::error, kLogCategory, "open_network() > HcnOpenNetwork failed with {}!", result.code); } return UniqueHcnNetwork{network, api.CloseNetwork}; } @@ -143,14 +143,14 @@ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network HCNWrapper::HCNWrapper(const HCNAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::HCNWrapper(...): api_table: {}", api)); + mpl::log(lvl::trace, kLogCategory, "HCNWrapper::HCNWrapper(...): api_table: {}", api); } // --------------------------------------------------------- OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::create_network(...) > params: {} ", params)); + mpl::log(lvl::trace, kLogCategory, "HCNWrapper::create_network(...) > params: {} ", params); /** * HcnCreateNetwork settings JSON template @@ -193,7 +193,8 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params // FIXME: Also include the result error message, if any. mpl::log(lvl::error, kLogCategory, - fmt::format("HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}!", result.code)); + "HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}!", + result.code); } [[maybe_unused]] UniqueHcnNetwork _{network, api.CloseNetwork}; @@ -204,7 +205,7 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params OperationResult HCNWrapper::delete_network(const std::string& network_guid) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::delete_network(...) > network_guid: {}", network_guid)); + mpl::log(lvl::trace, kLogCategory, "HCNWrapper::delete_network(...) > network_guid: {}", network_guid); return perform_hcn_operation(api, api.DeleteNetwork, guid_from_string(network_guid)); } @@ -212,7 +213,7 @@ OperationResult HCNWrapper::delete_network(const std::string& network_guid) cons OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& params) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCNWrapper::create_endpoint(...) > params: {} ", params)); + mpl::log(lvl::trace, kLogCategory, "HCNWrapper::create_endpoint(...) > params: {} ", params); const auto network = open_network(api, params.network_guid); @@ -259,9 +260,7 @@ OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& para OperationResult HCNWrapper::delete_endpoint(const std::string& endpoint_guid) const { - mpl::log(lvl::trace, - kLogCategory, - fmt::format("HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid)); + mpl::log(lvl::trace, kLogCategory, "HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid); return perform_hcn_operation(api, api.DeleteEndpoint, guid_from_string(endpoint_guid)); } diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index ee8e16a72f..ddb1bc7971 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -67,7 +67,7 @@ constexpr auto kDefaultOperationTimeout = std::chrono::seconds{240}; */ UniqueHcsOperation create_operation(const HCSAPITable& api) { - mpl::log(lvl::trace, kLogCategory, fmt::format("create_operation(...)")); + mpl::log(lvl::trace, kLogCategory, "create_operation(...)"); return UniqueHcsOperation{api.CreateOperation(nullptr, nullptr), api.CloseOperation}; } @@ -87,7 +87,9 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, { mpl::log(lvl::trace, kLogCategory, - fmt::format("wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), timeout.count())); + "wait_for_operation_result(...) > ({}), timeout: {} ms", + fmt::ptr(op.get()), + timeout.count()); wchar_t* result_msg_out{nullptr}; const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg_out); @@ -98,8 +100,8 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, // TODO: Convert from wstring to ascii and log this // mpl::log(lvl::trace, // kLogCategory, - // fmt::format("wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), - // result, result_msg)); + // "wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), + // result, result_msg); return OperationResult{result, result_msg.get()}; } return OperationResult{result, L""}; @@ -117,7 +119,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, */ UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::string& name) { - mpl::log(lvl::trace, kLogCategory, fmt::format("open_host_compute_system(...) > name: ({})", name)); + mpl::log(lvl::trace, kLogCategory, "open_host_compute_system(...) > name: ({})", name); // Windows API uses wide strings. const auto name_w = string_to_wstring(name); @@ -130,7 +132,9 @@ UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::stri { mpl::log(lvl::error, kLogCategory, - fmt::format("open_host_compute_system(...) > failed to open ({}), result code: ({})", name, result)); + "open_host_compute_system(...) > failed to open ({}), result code: ({})", + name, + result); } return UniqueHcsSystem{system, api.CloseComputeSystem}; } @@ -172,7 +176,8 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, { mpl::log(lvl::error, kLogCategory, - fmt::format("perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", target_hcs_system_name)); + "perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", + target_hcs_system_name); return OperationResult{E_POINTER, L"HcsOpenComputeSystem failed!"}; } @@ -182,7 +187,8 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, { mpl::log(lvl::error, kLogCategory, - fmt::format("perform_hcs_operation(...) > HcsCreateOperation failed! {}", target_hcs_system_name)); + "perform_hcs_operation(...) > HcsCreateOperation failed! {}", + target_hcs_system_name); return OperationResult{E_POINTER, L"HcsCreateOperation failed!"}; } @@ -192,17 +198,17 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, { mpl::log(lvl::error, kLogCategory, - fmt::format("perform_hcs_operation(...) > Operation failed! {} Result code {}", - target_hcs_system_name, - result)); + "perform_hcs_operation(...) > Operation failed! {} Result code {}", + target_hcs_system_name, + result); return OperationResult{result, L"HCS operation failed!"}; } mpl::log(lvl::trace, kLogCategory, - fmt::format("perform_hcs_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result))); + "perform_hcs_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result)); return wait_for_operation_result(api, std::move(operation)); } @@ -213,14 +219,14 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCSWrapper::HCSWrapper(...) > api_table: {}", api)); + mpl::log(lvl::trace, kLogCategory, "HCSWrapper::HCSWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("HCSWrapper::create_compute_system(...) > params: {} ", params)); + mpl::log(lvl::trace, kLogCategory, "HCSWrapper::create_compute_system(...) > params: {} ", params); // Fill the SCSI devices template depending on // available drives. @@ -336,7 +342,7 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("start_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "start_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); } @@ -344,7 +350,7 @@ OperationResult HCSWrapper::start_compute_system(const std::string& compute_syst OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("shutdown_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "shutdown_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); } @@ -352,7 +358,7 @@ OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_s OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("terminate_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "terminate_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); } @@ -360,7 +366,7 @@ OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("pause_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "pause_compute_system(...) > name: ({})", compute_system_name); static constexpr wchar_t c_pauseOption[] = LR"( { "SuspensionLevel": "Suspend", @@ -375,7 +381,7 @@ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_syst OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("resume_compute_system(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "resume_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); } @@ -383,7 +389,7 @@ OperationResult HCSWrapper::resume_compute_system(const std::string& compute_sys OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("add_endpoint(...) > params: {}", params)); + mpl::log(lvl::trace, kLogCategory, "add_endpoint(...) > params: {}", params); constexpr auto add_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -413,7 +419,9 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na { mpl::log(lvl::trace, kLogCategory, - fmt::format("remove_endpoint(...) > name: ({}), endpoint_guid: ({})", compute_system_name, endpoint_guid)); + "remove_endpoint(...) > name: ({}), endpoint_guid: ({})", + compute_system_name, + endpoint_guid); constexpr auto remove_endpoint_settings_template = LR"( {{ @@ -433,7 +441,9 @@ OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name // Machine must be booted up. mpl::log(lvl::trace, kLogCategory, - fmt::format("resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib)); + "resize_memory(...) > name: ({}), new_size_mb: ({})", + compute_system_name, + new_size_mib); // https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsmodifycomputesystem#remarks constexpr auto resize_memory_settings_template = LR"( {{ @@ -459,9 +469,7 @@ OperationResult HCSWrapper::update_cpu_count(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) const { - mpl::log(lvl::trace, - kLogCategory, - fmt::format("get_compute_system_properties(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "get_compute_system_properties(...) > name: ({})", compute_system_name); // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType static constexpr wchar_t c_VmQuery[] = LR"( @@ -477,10 +485,11 @@ OperationResult HCSWrapper::get_compute_system_properties(const std::string& com OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log( - lvl::trace, - kLogCategory, - fmt::format("grant_vm_access(...) > name: ({}), file_path: ({})", compute_system_name, file_path.string())); + mpl::log(lvl::trace, + kLogCategory, + "grant_vm_access(...) > name: ({}), file_path: ({})", + compute_system_name, + file_path.string()); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); @@ -493,10 +502,11 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log( - lvl::trace, - kLogCategory, - fmt::format("revoke_vm_access(...) > name: ({}), file_path: ({}) ", compute_system_name, file_path.string())); + mpl::log(lvl::trace, + kLogCategory, + "revoke_vm_access(...) > name: ({}), file_path: ({}) ", + compute_system_name, + file_path.string()); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); @@ -508,7 +518,7 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("get_compute_system_state(...) > name: ({})", compute_system_name)); + mpl::log(lvl::trace, kLogCategory, "get_compute_system_state(...) > name: ({})", compute_system_name); const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index 881c44930e..a9e79b25f5 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -45,7 +45,7 @@ constexpr auto kLogCategory = "HyperV-VirtDisk-Wrapper"; UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesystem::path& vhdx_path) { - mpl::log(lvl::trace, kLogCategory, fmt::format("open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string())); + mpl::log(lvl::trace, kLogCategory, "open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string()); // // Specify UNKNOWN for both device and vendor so the system will use the // file extension to determine the correct VHD format. @@ -73,9 +73,7 @@ UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesyste if (!(result == ERROR_SUCCESS)) { - mpl::log(lvl::error, - kLogCategory, - fmt::format("open_virtual_disk(...) > OpenVirtualDisk failed with: {}", result)); + mpl::log(lvl::error, kLogCategory, "open_virtual_disk(...) > OpenVirtualDisk failed with: {}", result); return UniqueHandle{nullptr, api.CloseHandle}; } @@ -88,14 +86,14 @@ UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesyste VirtDiskWrapper::VirtDiskWrapper(const VirtDiskAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, fmt::format("VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api)); + mpl::log(lvl::trace, kLogCategory, "VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskParameters& params) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("create_virtual_disk(...) > params: {}", params)); + mpl::log(lvl::trace, kLogCategory, "create_virtual_disk(...) > params: {}", params); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/CreateVirtualDisk.cpp // @@ -155,9 +153,7 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara return OperationResult{NOERROR, L""}; } - mpl::log(lvl::error, - kLogCategory, - fmt::format("create_virtual_disk(...) > CreateVirtualDisk failed with {}!", result)); + mpl::log(lvl::error, kLogCategory, "create_virtual_disk(...) > CreateVirtualDisk failed with {}!", result); return OperationResult{E_FAIL, fmt::format(L"CreateVirtualDisk failed with {}!", result)}; } @@ -168,9 +164,9 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path { mpl::log(lvl::trace, kLogCategory, - fmt::format("resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", - vhdx_path.string(), - new_size_bytes)); + "resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", + vhdx_path.string(), + new_size_bytes); const auto disk_handle = open_virtual_disk(api, vhdx_path); if (nullptr == disk_handle) @@ -198,9 +194,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path return OperationResult{NOERROR, L""}; } - mpl::log(lvl::error, - kLogCategory, - fmt::format("resize_virtual_disk(...) > ResizeVirtualDisk failed with {}!", resize_result)); + mpl::log(lvl::error, kLogCategory, "resize_virtual_disk(...) > ResizeVirtualDisk failed with {}!", resize_result); return OperationResult{E_FAIL, fmt::format(L"ResizeVirtualDisk failed with {}!", resize_result)}; } @@ -210,7 +204,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::path& vhdx_path, VirtualDiskInfo& vdinfo) const { - mpl::log(lvl::trace, kLogCategory, fmt::format("get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string())); + mpl::log(lvl::trace, kLogCategory, "get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string()); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/GetVirtualDiskInformation.cpp // @@ -308,7 +302,8 @@ OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::pa { mpl::log(lvl::warning, kLogCategory, - fmt::format("get_virtual_disk_info(...) > failed to get {}", fmt::underlying(version))); + "get_virtual_disk_info(...) > failed to get {}", + fmt::underlying(version)); } } From aed777fce88bd05ddcc930689aa76531a47033a1 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 4 Mar 2025 18:59:20 +0300 Subject: [PATCH 18/26] [hyperv-api] even more grooming - Fix CMakeLists parens newline - Switch to angle bracket include for headers - Use brace-initializers for members - Add header guard #endif comments - Add a few class description comments - compute_system_from_state() now returns std::optional - Add trailing comma to last enum/list items --- .../backends/hyperv_api/CMakeLists.txt | 6 ++++-- .../hyperv_api/hcn/hyperv_hcn_api_table.h | 2 +- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 12 ++++++------ .../hyperv_api/hcn/hyperv_hcn_api_wrapper.h | 8 ++++---- .../hcn/hyperv_hcn_create_endpoint_params.h | 8 ++++---- .../hcn/hyperv_hcn_create_network_params.h | 10 +++++----- .../hcn/hyperv_hcn_wrapper_interface.h | 6 +++--- .../hcs/hyperv_hcs_add_endpoint_params.h | 8 ++++---- .../hyperv_api/hcs/hyperv_hcs_api_table.h | 2 +- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 15 ++++++++------- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.h | 10 +++++----- .../hcs/hyperv_hcs_compute_system_state.h | 18 ++++++++---------- .../hyperv_hcs_create_compute_system_params.h | 12 ++++++------ .../hcs/hyperv_hcs_wrapper_interface.h | 12 +++++------- .../backends/hyperv_api/hyperv_api_common.cpp | 2 +- .../backends/hyperv_api/hyperv_api_common.h | 5 +++-- .../hyperv_api/hyperv_api_operation_result.h | 5 +++-- .../hyperv_api/virtdisk/virtdisk_api_table.h | 2 +- .../virtdisk/virtdisk_api_wrapper.cpp | 2 +- .../hyperv_api/virtdisk/virtdisk_api_wrapper.h | 10 +++++----- .../virtdisk_create_virtual_disk_params.h | 3 ++- .../hyperv_api/virtdisk/virtdisk_disk_info.h | 18 +++++++++--------- .../virtdisk/virtdisk_wrapper_interface.h | 8 ++++---- 23 files changed, 93 insertions(+), 91 deletions(-) diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt index 2460189599..698fc74b13 100644 --- a/src/platform/backends/hyperv_api/CMakeLists.txt +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -20,9 +20,11 @@ if(WIN32) hyperv_api_common.cpp hcn/hyperv_hcn_api_wrapper.cpp hcs/hyperv_hcs_api_wrapper.cpp - virtdisk/virtdisk_api_wrapper.cpp) + virtdisk/virtdisk_api_wrapper.cpp + ) target_link_libraries(hyperv_api_backend PRIVATE fmt - utils) + utils + ) endif() diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h index 9222879b2e..171d5ec134 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_table.h @@ -85,4 +85,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_HCN_API_TABLE diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index d0a4e593e4..d3a6e2e848 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -15,12 +15,12 @@ * */ -#include "hyperv_hcn_api_wrapper.h" -#include "../hyperv_api_common.h" -#include "hyperv_hcn_api_table.h" -#include "hyperv_hcn_create_endpoint_params.h" -#include "hyperv_hcn_create_network_params.h" -#include "hyperv_hcn_wrapper_interface.h" +#include +#include +#include +#include +#include +#include #include #include diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h index 082a35c23e..8990f593e9 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.h @@ -18,8 +18,8 @@ #ifndef MULTIPASS_HYPERV_API_HCN_WRAPPER #define MULTIPASS_HYPERV_API_HCN_WRAPPER -#include "hyperv_hcn_api_table.h" -#include "hyperv_hcn_wrapper_interface.h" +#include +#include namespace multipass::hyperv::hcn { @@ -86,9 +86,9 @@ struct HCNWrapper : public HCNWrapperInterface [[nodiscard]] OperationResult delete_endpoint(const std::string& endpoint_guid) const override; private: - const HCNAPITable api; + const HCNAPITable api{}; }; } // namespace multipass::hyperv::hcn -#endif +#endif // MULTIPASS_HYPERV_API_HCN_WRAPPER diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h index 9d8e23a044..478f401da0 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_endpoint_params.h @@ -34,19 +34,19 @@ struct CreateEndpointParameters * * The network must already exist. */ - std::string network_guid; + std::string network_guid{}; /** * GUID for the new endpoint. * * Must be unique. */ - std::string endpoint_guid; + std::string endpoint_guid{}; /** * The IPv[4-6] address to assign to the endpoint. */ - std::string endpoint_ipvx_addr; + std::string endpoint_ipvx_addr{}; }; } // namespace multipass::hyperv::hcn @@ -73,4 +73,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_HCN_CREATE_ENDPOINT_PARAMETERS_H diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h index dee75f7d83..feb0a32a86 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_create_network_params.h @@ -32,23 +32,23 @@ struct CreateNetworkParameters /** * Name for the network */ - std::string name; + std::string name{}; /** * RFC4122 unique identifier for the network. */ - std::string guid; + std::string guid{}; /** * Subnet CIDR that defines the address space of * the network. */ - std::string subnet; + std::string subnet{}; /** * The default gateway address for the network. */ - std::string gateway; + std::string gateway{}; }; } // namespace multipass::hyperv::hcn @@ -76,4 +76,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_HCN_CREATE_NETWORK_PARAMETERS_H diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h index 7281fc1719..14048cdf22 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_wrapper_interface.h @@ -18,7 +18,7 @@ #ifndef MULTIPASS_HYPERV_API_HCN_WRAPPER_INTERFACE_H #define MULTIPASS_HYPERV_API_HCN_WRAPPER_INTERFACE_H -#include "../hyperv_api_operation_result.h" +#include #include @@ -26,7 +26,7 @@ namespace multipass::hyperv::hcn { /** - * Abstract interface for Hyper-V Host Compute Networking wrapper. + * Abstract interface for Host Compute Network API wrapper. */ struct HCNWrapperInterface { @@ -38,4 +38,4 @@ struct HCNWrapperInterface }; } // namespace multipass::hyperv::hcn -#endif +#endif // MULTIPASS_HYPERV_API_HCN_WRAPPER_INTERFACE_H diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h index 1e256caddf..198eb39f8e 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_add_endpoint_params.h @@ -33,17 +33,17 @@ struct AddEndpointParameters /** * Name of the target host compute system */ - std::string target_compute_system_name; + std::string target_compute_system_name{}; /** * GUID of the endpoint to add. */ - std::string endpoint_guid; + std::string endpoint_guid{}; /** * MAC address to assign to the NIC */ - std::string nic_mac_address; + std::string nic_mac_address{}; }; } // namespace multipass::hyperv::hcs @@ -70,4 +70,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_HCS_ADD_ENDPOINT_PARAMETERS_H diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h index 2368fea7ab..34bb23735e 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_table.h @@ -119,4 +119,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_HCS_API_TABLE diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index ddb1bc7971..2c4c4bcb06 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -15,12 +15,12 @@ * */ -#include "hyperv_hcs_api_wrapper.h" -#include "../hyperv_api_common.h" -#include "hyperv_api/hyperv_api_operation_result.h" -#include "hyperv_hcs_add_endpoint_params.h" -#include "hyperv_hcs_api_table.h" -#include "hyperv_hcs_create_compute_system_params.h" +#include +#include +#include +#include +#include +#include #include @@ -28,11 +28,12 @@ #include #include -#include #include #include +#include + namespace multipass::hyperv::hcs { diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h index 02a21c0ecb..d08f7a0753 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.h @@ -18,9 +18,9 @@ #ifndef MULTIPASS_HYPERV_API_HCS_WRAPPER #define MULTIPASS_HYPERV_API_HCS_WRAPPER -#include "hyperv_hcs_api_table.h" -#include "hyperv_hcs_create_compute_system_params.h" -#include "hyperv_hcs_wrapper_interface.h" +#include +#include +#include namespace multipass::hyperv::hcs { @@ -229,9 +229,9 @@ struct HCSWrapper : public HCSWrapperInterface [[nodiscard]] OperationResult get_compute_system_state(const std::string& compute_system_name) const override; private: - const HCSAPITable api; + const HCSAPITable api{}; }; } // namespace multipass::hyperv::hcs -#endif +#endif // MULTIPASS_HYPERV_API_HCS_WRAPPER diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h index b5404fe2e2..a1dc83bb37 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_compute_system_state.h @@ -20,7 +20,7 @@ #include #include -#include +#include #include #include @@ -39,7 +39,7 @@ enum class ComputeSystemState : std::uint8_t paused, stopped, saved_as_template, - unknown + unknown, }; /** @@ -48,7 +48,7 @@ enum class ComputeSystemState : std::uint8_t * @param str * @return ComputeSystemState */ -inline ComputeSystemState compute_system_state_from_string(std::string str) +inline std::optional compute_system_state_from_string(std::string str) { std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) { return std::tolower(c); }); // std::unordered_map @@ -58,15 +58,13 @@ inline ComputeSystemState compute_system_state_from_string(std::string str) {"paused", ComputeSystemState::paused}, {"stopped", ComputeSystemState::stopped}, {"savedastemplate", ComputeSystemState::saved_as_template}, - {"unknown", ComputeSystemState::unknown}}; + {"unknown", ComputeSystemState::unknown}, + }; - const auto itr = translation_map.find(str); - if (translation_map.end() == itr) - { - throw std::domain_error{""}; - } + if (const auto itr = translation_map.find(str); translation_map.end() != itr) + return itr->second; - return itr->second; + return std::nullopt; } } // namespace multipass::hyperv::hcs diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h index fd8eb48eb1..365069f56d 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_create_compute_system_params.h @@ -32,27 +32,27 @@ struct CreateComputeSystemParameters /** * Unique name for the compute system */ - std::string name; + std::string name{}; /** * Memory size, in megabytes */ - std::uint32_t memory_size_mb; + std::uint32_t memory_size_mb{}; /** * vCPU count */ - std::uint32_t processor_count; + std::uint32_t processor_count{}; /** * Path to the cloud-init ISO file */ - std::string cloudinit_iso_path; + std::string cloudinit_iso_path{}; /** * Path to the Primary (boot) VHDX file */ - std::string vhdx_path; + std::string vhdx_path{}; }; } // namespace multipass::hyperv::hcs @@ -82,4 +82,4 @@ struct fmt::formatter +#include +#include #include #include @@ -30,8 +29,7 @@ namespace multipass::hyperv::hcs { /** - * - * + * Abstract interface for the Host Compute System API wrapper. */ struct HCSWrapperInterface { @@ -58,4 +56,4 @@ struct HCSWrapperInterface }; } // namespace multipass::hyperv::hcs -#endif +#endif // MULTIPASS_HYPERV_API_HCS_WRAPPER_INTERFACE_H diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp index acb490541a..17fa65acd9 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.cpp +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -15,7 +15,7 @@ * */ -#include "hyperv_api_common.h" +#include #include diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.h b/src/platform/backends/hyperv_api/hyperv_api_common.h index 068dfcb546..8bc0b021f8 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.h +++ b/src/platform/backends/hyperv_api/hyperv_api_common.h @@ -18,9 +18,10 @@ #ifndef MULTIPASS_HYPERV_API_COMMON_H #define MULTIPASS_HYPERV_API_COMMON_H -#include #include +#include + namespace multipass::hyperv { @@ -78,4 +79,4 @@ namespace multipass::hyperv } // namespace multipass::hyperv -#endif +#endif // MULTIPASS_HYPERV_API_COMMON_H diff --git a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h index 2d29a6313a..9c1c49a9b0 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h +++ b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h @@ -18,9 +18,10 @@ #ifndef MULTIPASS_HYPERV_API_OPERATION_RESULT_H #define MULTIPASS_HYPERV_API_OPERATION_RESULT_H -#include #include +#include + #include namespace multipass::hyperv @@ -125,4 +126,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_OPERATION_RESULT_H diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h index b62f5f3bf0..e77fe0d8cf 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_table.h @@ -76,4 +76,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_VIRTDISK_API_TABLE diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index a9e79b25f5..fe9fec1fe7 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -15,7 +15,7 @@ * */ -#include "virtdisk_api_wrapper.h" +#include #include #include diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h index 95b5e38529..aedfee658d 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.h @@ -18,9 +18,9 @@ #ifndef MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_H #define MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_H -#include "virtdisk_api_table.h" -#include "virtdisk_disk_info.h" -#include "virtdisk_wrapper_interface.h" +#include +#include +#include namespace multipass::hyperv::virtdisk { @@ -87,9 +87,9 @@ struct VirtDiskWrapper : public VirtDiskWrapperInterface VirtualDiskInfo& vdinfo) const override; private: - const VirtDiskAPITable api; + const VirtDiskAPITable api{}; }; } // namespace multipass::hyperv::virtdisk -#endif +#endif // MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_H diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h index 1b0cc941ca..953157184c 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_create_virtual_disk_params.h @@ -19,6 +19,7 @@ #define MULTIPASS_HYPERV_API_VIRTDISK_CREATE_VIRTUAL_DISK_PARAMETERS_H #include + #include namespace multipass::hyperv::virtdisk @@ -53,4 +54,4 @@ struct fmt::formatter size; - std::optional smallest_safe_virtual_size; - std::optional virtual_storage_type; - std::optional provider_subtype; + std::optional size{}; + std::optional smallest_safe_virtual_size{}; + std::optional virtual_storage_type{}; + std::optional provider_subtype{}; }; } // namespace multipass::hyperv::virtdisk @@ -91,4 +91,4 @@ struct fmt::formatter } }; -#endif +#endif // MULTIPASS_HYPERV_API_VIRTDISK_DISK_INFO_H diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h index 2e2b15dfcb..566f98a12b 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_wrapper_interface.h @@ -18,9 +18,9 @@ #ifndef MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_INTERFACE_H #define MULTIPASS_HYPERV_API_VIRTDISK_WRAPPER_INTERFACE_H -#include "../hyperv_api_operation_result.h" -#include "virtdisk_create_virtual_disk_params.h" -#include "virtdisk_disk_info.h" +#include +#include +#include #include @@ -28,7 +28,7 @@ namespace multipass::hyperv::virtdisk { /** - * Abstract interface for the virtdisk wrapper. + * Abstract interface for the virtdisk API wrapper. */ struct VirtDiskWrapperInterface { From 69c97002c52a2926330fa9755e1d59716b519d0e Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Fri, 7 Mar 2025 15:16:30 +0300 Subject: [PATCH 19/26] [hyperv-api] set all trace log levels to debug The trace option should be reserved for more verbose logging than those log calls. --- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 14 +- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 38 ++-- .../virtdisk/virtdisk_api_wrapper.cpp | 10 +- tests/hyperv_api/test_ut_hyperv_hcn_api.cpp | 62 +++--- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 206 +++++++++--------- tests/hyperv_api/test_ut_hyperv_virtdisk.cpp | 42 ++-- 6 files changed, 186 insertions(+), 186 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index d3a6e2e848..4c6259bbc5 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -99,7 +99,7 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, UniqueCotaskmemString result_msgbuf{result_msg_out, api.CoTaskMemFree}; - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "perform_operation(...) > fn: {}, result: {}", fmt::ptr(fn.template target()), @@ -126,7 +126,7 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, */ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) { - mpl::log(lvl::trace, kLogCategory, "open_network(...) > network_guid: {} ", network_guid); + mpl::log(lvl::debug, kLogCategory, "open_network(...) > network_guid: {} ", network_guid); HCN_NETWORK network{nullptr}; const auto result = perform_hcn_operation(api, api.OpenNetwork, guid_from_string(network_guid), &network); @@ -143,14 +143,14 @@ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network HCNWrapper::HCNWrapper(const HCNAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, "HCNWrapper::HCNWrapper(...): api_table: {}", api); + mpl::log(lvl::debug, kLogCategory, "HCNWrapper::HCNWrapper(...): api_table: {}", api); } // --------------------------------------------------------- OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params) const { - mpl::log(lvl::trace, kLogCategory, "HCNWrapper::create_network(...) > params: {} ", params); + mpl::log(lvl::debug, kLogCategory, "HCNWrapper::create_network(...) > params: {} ", params); /** * HcnCreateNetwork settings JSON template @@ -205,7 +205,7 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params OperationResult HCNWrapper::delete_network(const std::string& network_guid) const { - mpl::log(lvl::trace, kLogCategory, "HCNWrapper::delete_network(...) > network_guid: {}", network_guid); + mpl::log(lvl::debug, kLogCategory, "HCNWrapper::delete_network(...) > network_guid: {}", network_guid); return perform_hcn_operation(api, api.DeleteNetwork, guid_from_string(network_guid)); } @@ -213,7 +213,7 @@ OperationResult HCNWrapper::delete_network(const std::string& network_guid) cons OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& params) const { - mpl::log(lvl::trace, kLogCategory, "HCNWrapper::create_endpoint(...) > params: {} ", params); + mpl::log(lvl::debug, kLogCategory, "HCNWrapper::create_endpoint(...) > params: {} ", params); const auto network = open_network(api, params.network_guid); @@ -260,7 +260,7 @@ OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& para OperationResult HCNWrapper::delete_endpoint(const std::string& endpoint_guid) const { - mpl::log(lvl::trace, kLogCategory, "HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid); + mpl::log(lvl::debug, kLogCategory, "HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid); return perform_hcn_operation(api, api.DeleteEndpoint, guid_from_string(endpoint_guid)); } diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 2c4c4bcb06..0540c530e2 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -68,7 +68,7 @@ constexpr auto kDefaultOperationTimeout = std::chrono::seconds{240}; */ UniqueHcsOperation create_operation(const HCSAPITable& api) { - mpl::log(lvl::trace, kLogCategory, "create_operation(...)"); + mpl::log(lvl::debug, kLogCategory, "create_operation(...)"); return UniqueHcsOperation{api.CreateOperation(nullptr, nullptr), api.CloseOperation}; } @@ -86,7 +86,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, UniqueHcsOperation op, std::chrono::milliseconds timeout = kDefaultOperationTimeout) { - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "wait_for_operation_result(...) > ({}), timeout: {} ms", fmt::ptr(op.get()), @@ -99,7 +99,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, if (result_msg) { // TODO: Convert from wstring to ascii and log this - // mpl::log(lvl::trace, + // mpl::log(lvl::debug, // kLogCategory, // "wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), // result, result_msg); @@ -120,7 +120,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, */ UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::string& name) { - mpl::log(lvl::trace, kLogCategory, "open_host_compute_system(...) > name: ({})", name); + mpl::log(lvl::debug, kLogCategory, "open_host_compute_system(...) > name: ({})", name); // Windows API uses wide strings. const auto name_w = string_to_wstring(name); @@ -205,7 +205,7 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, return OperationResult{result, L"HCS operation failed!"}; } - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "perform_hcs_operation(...) > fn: {}, result: {}", fmt::ptr(fn.template target()), @@ -220,14 +220,14 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, "HCSWrapper::HCSWrapper(...) > api_table: {}", api); + mpl::log(lvl::debug, kLogCategory, "HCSWrapper::HCSWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) const { - mpl::log(lvl::trace, kLogCategory, "HCSWrapper::create_compute_system(...) > params: {} ", params); + mpl::log(lvl::debug, kLogCategory, "HCSWrapper::create_compute_system(...) > params: {} ", params); // Fill the SCSI devices template depending on // available drives. @@ -343,7 +343,7 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "start_compute_system(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "start_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); } @@ -351,7 +351,7 @@ OperationResult HCSWrapper::start_compute_system(const std::string& compute_syst OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "shutdown_compute_system(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "shutdown_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); } @@ -359,7 +359,7 @@ OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_s OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "terminate_compute_system(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "terminate_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); } @@ -367,7 +367,7 @@ OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "pause_compute_system(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "pause_compute_system(...) > name: ({})", compute_system_name); static constexpr wchar_t c_pauseOption[] = LR"( { "SuspensionLevel": "Suspend", @@ -382,7 +382,7 @@ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_syst OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "resume_compute_system(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "resume_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); } @@ -390,7 +390,7 @@ OperationResult HCSWrapper::resume_compute_system(const std::string& compute_sys OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) const { - mpl::log(lvl::trace, kLogCategory, "add_endpoint(...) > params: {}", params); + mpl::log(lvl::debug, kLogCategory, "add_endpoint(...) > params: {}", params); constexpr auto add_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -418,7 +418,7 @@ OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) co OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) const { - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "remove_endpoint(...) > name: ({}), endpoint_guid: ({})", compute_system_name, @@ -440,7 +440,7 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) const { // Machine must be booted up. - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, @@ -470,7 +470,7 @@ OperationResult HCSWrapper::update_cpu_count(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "get_compute_system_properties(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "get_compute_system_properties(...) > name: ({})", compute_system_name); // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType static constexpr wchar_t c_VmQuery[] = LR"( @@ -486,7 +486,7 @@ OperationResult HCSWrapper::get_compute_system_properties(const std::string& com OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "grant_vm_access(...) > name: ({}), file_path: ({})", compute_system_name, @@ -503,7 +503,7 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "revoke_vm_access(...) > name: ({}), file_path: ({}) ", compute_system_name, @@ -519,7 +519,7 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) const { - mpl::log(lvl::trace, kLogCategory, "get_compute_system_state(...) > name: ({})", compute_system_name); + mpl::log(lvl::debug, kLogCategory, "get_compute_system_state(...) > name: ({})", compute_system_name); const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index fe9fec1fe7..d6ac20e2f2 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -45,7 +45,7 @@ constexpr auto kLogCategory = "HyperV-VirtDisk-Wrapper"; UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesystem::path& vhdx_path) { - mpl::log(lvl::trace, kLogCategory, "open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string()); + mpl::log(lvl::debug, kLogCategory, "open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string()); // // Specify UNKNOWN for both device and vendor so the system will use the // file extension to determine the correct VHD format. @@ -86,14 +86,14 @@ UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesyste VirtDiskWrapper::VirtDiskWrapper(const VirtDiskAPITable& api_table) : api{api_table} { - mpl::log(lvl::trace, kLogCategory, "VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api); + mpl::log(lvl::debug, kLogCategory, "VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskParameters& params) const { - mpl::log(lvl::trace, kLogCategory, "create_virtual_disk(...) > params: {}", params); + mpl::log(lvl::debug, kLogCategory, "create_virtual_disk(...) > params: {}", params); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/CreateVirtualDisk.cpp // @@ -162,7 +162,7 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path& vhdx_path, std::uint64_t new_size_bytes) const { - mpl::log(lvl::trace, + mpl::log(lvl::debug, kLogCategory, "resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", vhdx_path.string(), @@ -204,7 +204,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::path& vhdx_path, VirtualDiskInfo& vdinfo) const { - mpl::log(lvl::trace, kLogCategory, "get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string()); + mpl::log(lvl::debug, kLogCategory, "get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string()); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/GetVirtualDiskInformation.cpp // diff --git a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp index 54efd75d5a..fe44f67fbb 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcn_api.cpp @@ -214,9 +214,9 @@ TEST_F(HyperVHCNAPI_UnitTests, create_network_close_network_failed) EXPECT_CALL(mock_close_network, Call) .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(E_POINTER))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::create_network(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::create_network(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...)"); } /****************************************************** @@ -272,9 +272,9 @@ TEST_F(HyperVHCNAPI_UnitTests, create_network_failed) EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([&](void* ptr) { EXPECT_EQ(ptr, mock_error_msg); }); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::create_network(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::create_network(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...)"); logger_scope.mock_logger->expect_log( mpl::Level::error, "HCNWrapper::create_network(...) > HcnCreateNetwork failed with 0x80004003!"); @@ -329,11 +329,11 @@ TEST_F(HyperVHCNAPI_UnitTests, delete_network_success) Return(NOERROR))); // Expected logs - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::delete_network(...) > network_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: true"); } /****************************************************** @@ -379,11 +379,11 @@ TEST_F(HyperVHCNAPI_UnitTests, delete_network_failed) EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([&](void* ptr) { EXPECT_EQ(ptr, mock_error_msg); }); // Expected logs - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::delete_network(...) > network_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: false"); } /****************************************************** @@ -476,14 +476,14 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_success) EXPECT_CALL(mock_close_network, Call) .WillOnce(DoAll([&](HCN_NETWORK n) { ASSERT_EQ(n, mock_network_object); }, Return(NOERROR))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: true", testing::Exactly(2)); } @@ -525,16 +525,16 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_open_network_failed) { EXPECT_CALL(mock_open_network, Call).WillOnce(Return(E_POINTER)); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); logger_scope.mock_logger->expect_log(mpl::Level::error, "open_network() > HcnOpenNetwork failed with 0x80004003!"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: false"); } /****************************************************** @@ -630,15 +630,15 @@ TEST_F(HyperVHCNAPI_UnitTests, create_endpoint_failure) EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([](const void* ptr) { ASSERT_EQ(ptr, mock_error_msg); }); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::create_endpoint(...) > params: Endpoint GUID: (77c27c1e-8204-437d-a7cc-fb4ce1614819) | " "Network GUID: (b70c479d-f808-4053-aafa-705bc15b6d68) | Endpoint IPvX Addr.: (172.50.224.27)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_network(...) > network_guid: b70c479d-f808-4053-aafa-705bc15b6d68"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: true"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: false"); } /****************************************************** @@ -688,11 +688,11 @@ TEST_F(HyperVHCNAPI_UnitTests, delete_endpoint_success) Return(NOERROR))); // Expected logs - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::delete_endpoint(...) > endpoint_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: true"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: true"); } /****************************************************** @@ -733,11 +733,11 @@ TEST_F(HyperVHCNAPI_UnitTests, delete_endpoint_failure) EXPECT_CALL(mock_cotaskmemfree, Call).WillOnce([](const void* ptr) { ASSERT_EQ(ptr, mock_error_msg); }); // Expected logs - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCNWrapper::HCNWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCNWrapper::HCNWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCNWrapper::delete_endpoint(...) > endpoint_guid: af3fb745-2f23-463c-8ded-443f876d9e81"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_operation(...) > fn: 0x0, result: false"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_operation(...) > fn: 0x0, result: false"); } /****************************************************** diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp index cf4a9b7713..36effca062 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -298,13 +298,13 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_happy_path) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " "Memory size: (16384 MiB) | cloud-init ISO path: (cloudinit iso path) | VHDX path: (virtual disk path)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -455,13 +455,13 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " "Memory size: (16384 MiB) | cloud-init ISO path: () | VHDX path: (virtual disk path)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -612,13 +612,13 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_vhdx) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " "Memory size: (16384 MiB) | cloud-init ISO path: (cloudinit iso path) | VHDX path: ()"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -760,13 +760,13 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit_and_vhdx) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_success_msg); }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "HCSWrapper::create_compute_system(...) > params: Compute System name: (test_vm) | vCPU count: (8) | " "Memory size: (16384 MiB) | cloud-init ISO path: () | VHDX path: ()"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -815,9 +815,9 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_create_operation_fail) }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); } /****************************************************** @@ -952,9 +952,9 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_fail) }, Return(E_POINTER))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); } /****************************************************** @@ -1114,10 +1114,10 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wait_for_operation_fail) EXPECT_CALL(mock_local_free, Call) .WillOnce(DoAll([](HLOCAL ptr) { ASSERT_EQ(ptr, mock_error_msg); }, Return(nullptr))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::create_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::create_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -1168,8 +1168,8 @@ TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_success) }, Return(NOERROR))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "grant_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } @@ -1211,8 +1211,8 @@ TEST_F(HyperVHCSAPI_UnitTests, grant_vm_access_fail) }, Return(E_POINTER))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "grant_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } @@ -1258,8 +1258,8 @@ TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_success) }, Return(NOERROR))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "revoke_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } @@ -1300,8 +1300,8 @@ TEST_F(HyperVHCSAPI_UnitTests, revoke_vm_access_fail) ASSERT_STREQ(filePath, L"this is a path"); }, Return(E_POINTER))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "revoke_vm_access(...) > name: (test_vm), file_path: (this is a path)"); } @@ -1403,11 +1403,11 @@ void HyperVHCSAPI_UnitTests::generic_operation_happy_path(ApiFnT& target_api_fun Return(nullptr))); } - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_hcs_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_hcs_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -1459,8 +1459,8 @@ void HyperVHCSAPI_UnitTests::generic_operation_hcs_open_fail(ApiFnT& target_api_ }, Return(E_POINTER))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_host_compute_system(...) > name: (test_vm)"); logger_scope.mock_logger->expect_log( mpl::Level::error, "open_host_compute_system(...) > failed to open (test_vm), result code: (0x80004003)"); @@ -1535,9 +1535,9 @@ void HyperVHCSAPI_UnitTests::generic_operation_create_operation_fail(ApiFnT& tar ASSERT_EQ(mock_compute_system_object, computeSystem); }); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); logger_scope.mock_logger->expect_log(mpl::Level::error, "perform_hcs_operation(...) > HcsCreateOperation failed!"); } @@ -1618,9 +1618,9 @@ void HyperVHCSAPI_UnitTests::generic_operation_fail(ApiFnT& target_api_function, ASSERT_EQ(mock_compute_system_object, computeSystem); }); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); logger_scope.mock_logger->expect_log(mpl::Level::error, "perform_hcs_operation(...) > Operation failed!"); } @@ -1722,11 +1722,11 @@ void HyperVHCSAPI_UnitTests::generic_operation_wait_for_operation_fail(ApiFnT& t Return(nullptr))); } - logger_scope.mock_logger->expect_log(mpl::Level::trace, "HCSWrapper::HCSWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_host_compute_system(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "create_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "perform_hcs_operation(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "wait_for_operation_result(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "HCSWrapper::HCSWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_host_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "create_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "perform_hcs_operation(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "wait_for_operation_result(...)"); } /****************************************************** @@ -1757,7 +1757,7 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_happy_path) generic_operation_happy_path( mock_api_table.StartComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "start_compute_system(...) > name: (test_vm)"); return wrapper.start_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1774,7 +1774,7 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.StartComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "start_compute_system(...)"); return wrapper.start_compute_system("test_vm"); }); } @@ -1787,7 +1787,7 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.StartComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "start_compute_system(...)"); return wrapper.start_compute_system("test_vm"); }); } @@ -1799,7 +1799,7 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_fail) generic_operation_fail( mock_api_table.StartComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "start_compute_system(...)"); return wrapper.start_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1816,7 +1816,7 @@ TEST_F(HyperVHCSAPI_UnitTests, start_compute_system_wait_for_operation_result_fa generic_operation_wait_for_operation_fail( mock_api_table.StartComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "start_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "start_compute_system(...)"); return wrapper.start_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1833,7 +1833,7 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ShutDownComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "shutdown_compute_system(...) > name: (test_vm)"); return wrapper.shutdown_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1850,7 +1850,7 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.ShutDownComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "shutdown_compute_system(...)"); return wrapper.shutdown_compute_system("test_vm"); }); } @@ -1863,7 +1863,7 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.ShutDownComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "shutdown_compute_system(...)"); return wrapper.shutdown_compute_system("test_vm"); }); } @@ -1875,7 +1875,7 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_fail) generic_operation_fail( mock_api_table.ShutDownComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "shutdown_compute_system(...)"); return wrapper.shutdown_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1892,7 +1892,7 @@ TEST_F(HyperVHCSAPI_UnitTests, shutdown_compute_system_wait_for_operation_result generic_operation_wait_for_operation_fail( mock_api_table.ShutDownComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "shutdown_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "shutdown_compute_system(...)"); return wrapper.shutdown_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1909,7 +1909,7 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_happy_path) generic_operation_happy_path( mock_api_table.TerminateComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "terminate_compute_system(...) > name: (test_vm)"); return wrapper.terminate_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1926,7 +1926,7 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.TerminateComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "terminate_compute_system(...)"); return wrapper.terminate_compute_system("test_vm"); }); } @@ -1939,7 +1939,7 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.TerminateComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "terminate_compute_system(...)"); return wrapper.terminate_compute_system("test_vm"); }); } @@ -1951,7 +1951,7 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_fail) generic_operation_fail( mock_api_table.TerminateComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "terminate_compute_system(...)"); return wrapper.terminate_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1968,7 +1968,7 @@ TEST_F(HyperVHCSAPI_UnitTests, terminate_compute_system_wait_for_operation_resul generic_operation_wait_for_operation_fail( mock_api_table.TerminateComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "terminate_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "terminate_compute_system(...)"); return wrapper.terminate_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -1993,7 +1993,7 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_happy_path) generic_operation_happy_path( mock_api_table.PauseComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "pause_compute_system(...) > name: (test_vm)"); return wrapper.pause_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2012,7 +2012,7 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.PauseComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "pause_compute_system(...)"); return wrapper.pause_compute_system("test_vm"); }); } @@ -2025,7 +2025,7 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.PauseComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "pause_compute_system(...)"); return wrapper.pause_compute_system("test_vm"); }); } @@ -2044,7 +2044,7 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_fail) generic_operation_fail( mock_api_table.PauseComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "pause_compute_system(...)"); return wrapper.pause_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2070,7 +2070,7 @@ TEST_F(HyperVHCSAPI_UnitTests, pause_compute_system_wait_for_operation_result_fa generic_operation_wait_for_operation_fail( mock_api_table.PauseComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "pause_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "pause_compute_system(...)"); return wrapper.pause_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2089,7 +2089,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ResumeComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resume_compute_system(...) > name: (test_vm)"); return wrapper.resume_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2106,7 +2106,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.ResumeComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resume_compute_system(...)"); return wrapper.resume_compute_system("test_vm"); }); } @@ -2119,7 +2119,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.ResumeComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resume_compute_system(...)"); return wrapper.resume_compute_system("test_vm"); }); } @@ -2131,7 +2131,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_fail) generic_operation_fail( mock_api_table.ResumeComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resume_compute_system(...)"); return wrapper.resume_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2148,7 +2148,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_wait_for_operation_result_f generic_operation_wait_for_operation_fail( mock_api_table.ResumeComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resume_compute_system(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resume_compute_system(...)"); return wrapper.resume_compute_system("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR options) { @@ -2177,7 +2177,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_happy_path) mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "add_endpoint(...) > params: Host Compute System Name: (test_vm) | Endpoint GUID: " "(288cc1ac-8f31-4a09-9e90-30ad0bcfdbca) | NIC MAC Address: (00:00:00:00:00:00)"); hyperv::hcs::AddEndpointParameters params{}; @@ -2202,7 +2202,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.target_compute_system_name = "test_vm"; return wrapper.add_endpoint(params); @@ -2216,7 +2216,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_create_operation_f generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.target_compute_system_name = "test_vm"; return wrapper.add_endpoint(params); @@ -2241,7 +2241,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; params.nic_mac_address = "00:00:00:00:00:00"; @@ -2275,7 +2275,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_wait_for_operation generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "add_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "add_endpoint(...)"); hyperv::hcs::AddEndpointParameters params{}; params.endpoint_guid = "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"; params.nic_mac_address = "00:00:00:00:00:00"; @@ -2305,7 +2305,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_happy_path) mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "remove_endpoint(...) > name: (test_vm), endpoint_guid: (288cc1ac-8f31-4a09-9e90-30ad0bcfdbca)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, @@ -2325,7 +2325,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_hcs_open_fail generic_operation_hcs_open_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }); } @@ -2337,7 +2337,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_create_operat generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }); } @@ -2355,7 +2355,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2380,7 +2380,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_wait_for_oper generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "remove_endpoint(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "remove_endpoint(...)"); return wrapper.remove_endpoint("test_vm", "288cc1ac-8f31-4a09-9e90-30ad0bcfdbca"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2406,7 +2406,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_happy_path) generic_operation_happy_path( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resize_memory(...) > name: (test_vm), new_size_mb: (16384)"); return wrapper.resize_memory("test_vm", 16384); }, @@ -2426,7 +2426,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resize_memory(...)"); return wrapper.resize_memory("test_vm", 16384); }); } @@ -2438,7 +2438,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_create_operation_ generic_operation_create_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resize_memory(...)"); return wrapper.resize_memory("test_vm", 16384); }); } @@ -2457,7 +2457,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_fail) generic_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resize_memory(...)"); return wrapper.resize_memory("test_vm", 16384); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2483,7 +2483,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_wait_for_operatio generic_operation_wait_for_operation_fail( mock_api_table.ModifyComputeSystem, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "resize_memory(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "resize_memory(...)"); return wrapper.resize_memory("test_vm", 16384); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR configuration, HANDLE identity) { @@ -2507,7 +2507,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_happy_path) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_properties(...) > name: (test_vm)"); return wrapper.get_compute_system_properties("test_vm"); }, @@ -2527,7 +2527,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_properties(...)"); return wrapper.get_compute_system_properties("test_vm"); }); } @@ -2539,7 +2539,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_create_operation_fa generic_operation_create_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_properties(...)"); return wrapper.get_compute_system_properties("test_vm"); }); } @@ -2556,7 +2556,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_fail) generic_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_properties(...)"); return wrapper.get_compute_system_properties("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { @@ -2580,7 +2580,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_wait_for_operation_ generic_operation_wait_for_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_properties(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_properties(...)"); return wrapper.get_compute_system_properties("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { @@ -2602,7 +2602,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_happy_path) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...) > name: (test_vm)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...) > name: (test_vm)"); return wrapper.get_compute_system_state("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { @@ -2624,7 +2624,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_no_state) generic_operation_happy_path( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...)"); return wrapper.get_compute_system_state("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { @@ -2644,7 +2644,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_hcs_open_fail) generic_operation_hcs_open_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...)"); return wrapper.get_compute_system_state("test_vm"); }, expected_status_msg); @@ -2658,7 +2658,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_create_operation_fail) generic_operation_create_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...)"); return wrapper.get_compute_system_state("test_vm"); }, expected_status_msg); @@ -2673,7 +2673,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_fail) generic_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...)"); return wrapper.get_compute_system_state("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { @@ -2693,7 +2693,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_state_wait_for_operation_resul generic_operation_wait_for_operation_fail( mock_api_table.GetComputeSystemProperties, [&](hyperv::hcs::HCSWrapper& wrapper) { - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_compute_system_state(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_compute_system_state(...)"); return wrapper.get_compute_system_state("test_vm"); }, [](HCS_SYSTEM computeSystem, HCS_OPERATION operation, PCWSTR propertyQuery) { diff --git a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp index e0c041a72d..4bf8d3b209 100644 --- a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp @@ -137,9 +137,9 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhdx_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhdx)"); } @@ -210,9 +210,9 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhd_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhd)"); } @@ -258,9 +258,9 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_failed) ) {}, Return(ERROR_PATH_NOT_FOUND))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "create_virtual_disk(...) > params: Size (in bytes): (2097152) | Path: (test.vhd)"); logger_scope.mock_logger->expect_log(mpl::Level::error, "create_virtual_disk(...) > CreateVirtualDisk failed with 3!"); @@ -340,11 +340,11 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); } { @@ -383,11 +383,11 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_open_failed) }, Return(ERROR_PATH_NOT_FOUND))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); logger_scope.mock_logger->expect_log(mpl::Level::error, "open_virtual_disk(...) > OpenVirtualDisk failed with: 3"); } @@ -440,11 +440,11 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_resize_failed) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( - mpl::Level::trace, + mpl::Level::debug, "resize_virtual_disk(...) > vhdx_path: test.vhdx, new_size_bytes: 1234567"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); logger_scope.mock_logger->expect_log(mpl::Level::error, "resize_virtual_disk(...) > ResizeVirtualDisk failed with 87!"); } @@ -567,9 +567,9 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_happy_path) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); } { @@ -677,9 +677,9 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_fail_some) EXPECT_CALL(mock_close_handle, Call) .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "VirtDiskWrapper::VirtDiskWrapper(...)"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); - logger_scope.mock_logger->expect_log(mpl::Level::trace, "open_virtual_disk(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); + logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); logger_scope.mock_logger->expect_log(mpl::Level::warning, "get_virtual_disk_info(...) > failed to get 6"); logger_scope.mock_logger->expect_log(mpl::Level::warning, "get_virtual_disk_info(...) > failed to get 7"); } From 525d6ba54246377911417722ea305776a716f212 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 11 Mar 2025 17:37:02 +0300 Subject: [PATCH 20/26] [hyperv-api] switch to log-level functions --- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 28 ++--- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 108 ++++++++---------- .../virtdisk/virtdisk_api_wrapper.cpp | 28 ++--- 3 files changed, 70 insertions(+), 94 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 4c6259bbc5..8ade1a325c 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -99,11 +99,10 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, UniqueCotaskmemString result_msgbuf{result_msg_out, api.CoTaskMemFree}; - mpl::log(lvl::debug, - kLogCategory, - "perform_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result)); + mpl::debug(kLogCategory, + "perform_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result)); // Error message is only valid when the operation resulted in an error. // Passing a nullptr is well-defined in "< C++23", but it's going to be @@ -126,13 +125,13 @@ OperationResult perform_hcn_operation(const HCNAPITable& api, const FnType& fn, */ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network_guid) { - mpl::log(lvl::debug, kLogCategory, "open_network(...) > network_guid: {} ", network_guid); + mpl::debug(kLogCategory, "open_network(...) > network_guid: {} ", network_guid); HCN_NETWORK network{nullptr}; const auto result = perform_hcn_operation(api, api.OpenNetwork, guid_from_string(network_guid), &network); if (!result) { - mpl::log(lvl::error, kLogCategory, "open_network() > HcnOpenNetwork failed with {}!", result.code); + mpl::error(kLogCategory, "open_network() > HcnOpenNetwork failed with {}!", result.code); } return UniqueHcnNetwork{network, api.CloseNetwork}; } @@ -143,14 +142,14 @@ UniqueHcnNetwork open_network(const HCNAPITable& api, const std::string& network HCNWrapper::HCNWrapper(const HCNAPITable& api_table) : api{api_table} { - mpl::log(lvl::debug, kLogCategory, "HCNWrapper::HCNWrapper(...): api_table: {}", api); + mpl::debug(kLogCategory, "HCNWrapper::HCNWrapper(...): api_table: {}", api); } // --------------------------------------------------------- OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params) const { - mpl::log(lvl::debug, kLogCategory, "HCNWrapper::create_network(...) > params: {} ", params); + mpl::debug(kLogCategory, "HCNWrapper::create_network(...) > params: {} ", params); /** * HcnCreateNetwork settings JSON template @@ -191,10 +190,7 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params if (!result) { // FIXME: Also include the result error message, if any. - mpl::log(lvl::error, - kLogCategory, - "HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}!", - result.code); + mpl::error(kLogCategory, "HCNWrapper::create_network(...) > HcnCreateNetwork failed with {}!", result.code); } [[maybe_unused]] UniqueHcnNetwork _{network, api.CloseNetwork}; @@ -205,7 +201,7 @@ OperationResult HCNWrapper::create_network(const CreateNetworkParameters& params OperationResult HCNWrapper::delete_network(const std::string& network_guid) const { - mpl::log(lvl::debug, kLogCategory, "HCNWrapper::delete_network(...) > network_guid: {}", network_guid); + mpl::debug(kLogCategory, "HCNWrapper::delete_network(...) > network_guid: {}", network_guid); return perform_hcn_operation(api, api.DeleteNetwork, guid_from_string(network_guid)); } @@ -213,7 +209,7 @@ OperationResult HCNWrapper::delete_network(const std::string& network_guid) cons OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& params) const { - mpl::log(lvl::debug, kLogCategory, "HCNWrapper::create_endpoint(...) > params: {} ", params); + mpl::debug(kLogCategory, "HCNWrapper::create_endpoint(...) > params: {} ", params); const auto network = open_network(api, params.network_guid); @@ -260,7 +256,7 @@ OperationResult HCNWrapper::create_endpoint(const CreateEndpointParameters& para OperationResult HCNWrapper::delete_endpoint(const std::string& endpoint_guid) const { - mpl::log(lvl::debug, kLogCategory, "HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid); + mpl::debug(kLogCategory, "HCNWrapper::delete_endpoint(...) > endpoint_guid: {} ", endpoint_guid); return perform_hcn_operation(api, api.DeleteEndpoint, guid_from_string(endpoint_guid)); } diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 0540c530e2..3c262f3ab3 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -68,7 +68,7 @@ constexpr auto kDefaultOperationTimeout = std::chrono::seconds{240}; */ UniqueHcsOperation create_operation(const HCSAPITable& api) { - mpl::log(lvl::debug, kLogCategory, "create_operation(...)"); + mpl::debug(kLogCategory, "create_operation(...)"); return UniqueHcsOperation{api.CreateOperation(nullptr, nullptr), api.CloseOperation}; } @@ -86,11 +86,10 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, UniqueHcsOperation op, std::chrono::milliseconds timeout = kDefaultOperationTimeout) { - mpl::log(lvl::debug, - kLogCategory, - "wait_for_operation_result(...) > ({}), timeout: {} ms", - fmt::ptr(op.get()), - timeout.count()); + mpl::debug(kLogCategory, + "wait_for_operation_result(...) > ({}), timeout: {} ms", + fmt::ptr(op.get()), + timeout.count()); wchar_t* result_msg_out{nullptr}; const auto result = api.WaitForOperationResult(op.get(), timeout.count(), &result_msg_out); @@ -99,8 +98,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, if (result_msg) { // TODO: Convert from wstring to ascii and log this - // mpl::log(lvl::debug, - // kLogCategory, + // mpl::debug(kLogCategory, // "wait_for_operation_result(...): ({}), result: {}, result_msg: {}", fmt::ptr(op.get()), // result, result_msg); return OperationResult{result, result_msg.get()}; @@ -120,7 +118,7 @@ OperationResult wait_for_operation_result(const HCSAPITable& api, */ UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::string& name) { - mpl::log(lvl::debug, kLogCategory, "open_host_compute_system(...) > name: ({})", name); + mpl::debug(kLogCategory, "open_host_compute_system(...) > name: ({})", name); // Windows API uses wide strings. const auto name_w = string_to_wstring(name); @@ -131,11 +129,10 @@ UniqueHcsSystem open_host_compute_system(const HCSAPITable& api, const std::stri if (!result) { - mpl::log(lvl::error, - kLogCategory, - "open_host_compute_system(...) > failed to open ({}), result code: ({})", - name, - result); + mpl::error(kLogCategory, + "open_host_compute_system(...) > failed to open ({}), result code: ({})", + name, + result); } return UniqueHcsSystem{system, api.CloseComputeSystem}; } @@ -175,10 +172,9 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == system) { - mpl::log(lvl::error, - kLogCategory, - "perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", - target_hcs_system_name); + mpl::error(kLogCategory, + "perform_hcs_operation(...) > HcsOpenComputeSystem failed! {}", + target_hcs_system_name); return OperationResult{E_POINTER, L"HcsOpenComputeSystem failed!"}; } @@ -186,10 +182,7 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, if (nullptr == operation) { - mpl::log(lvl::error, - kLogCategory, - "perform_hcs_operation(...) > HcsCreateOperation failed! {}", - target_hcs_system_name); + mpl::error(kLogCategory, "perform_hcs_operation(...) > HcsCreateOperation failed! {}", target_hcs_system_name); return OperationResult{E_POINTER, L"HcsCreateOperation failed!"}; } @@ -197,19 +190,17 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, if (!result) { - mpl::log(lvl::error, - kLogCategory, - "perform_hcs_operation(...) > Operation failed! {} Result code {}", - target_hcs_system_name, - result); + mpl::error(kLogCategory, + "perform_hcs_operation(...) > Operation failed! {} Result code {}", + target_hcs_system_name, + result); return OperationResult{result, L"HCS operation failed!"}; } - mpl::log(lvl::debug, - kLogCategory, - "perform_hcs_operation(...) > fn: {}, result: {}", - fmt::ptr(fn.template target()), - static_cast(result)); + mpl::debug(kLogCategory, + "perform_hcs_operation(...) > fn: {}, result: {}", + fmt::ptr(fn.template target()), + static_cast(result)); return wait_for_operation_result(api, std::move(operation)); } @@ -220,14 +211,14 @@ OperationResult perform_hcs_operation(const HCSAPITable& api, HCSWrapper::HCSWrapper(const HCSAPITable& api_table) : api{api_table} { - mpl::log(lvl::debug, kLogCategory, "HCSWrapper::HCSWrapper(...) > api_table: {}", api); + mpl::debug(kLogCategory, "HCSWrapper::HCSWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParameters& params) const { - mpl::log(lvl::debug, kLogCategory, "HCSWrapper::create_compute_system(...) > params: {} ", params); + mpl::debug(kLogCategory, "HCSWrapper::create_compute_system(...) > params: {} ", params); // Fill the SCSI devices template depending on // available drives. @@ -343,7 +334,7 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam OperationResult HCSWrapper::start_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "start_compute_system(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "start_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.StartComputeSystem, compute_system_name, nullptr); } @@ -351,7 +342,7 @@ OperationResult HCSWrapper::start_compute_system(const std::string& compute_syst OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "shutdown_compute_system(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "shutdown_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ShutDownComputeSystem, compute_system_name, nullptr); } @@ -359,7 +350,7 @@ OperationResult HCSWrapper::shutdown_compute_system(const std::string& compute_s OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "terminate_compute_system(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "terminate_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.TerminateComputeSystem, compute_system_name, nullptr); } @@ -367,7 +358,7 @@ OperationResult HCSWrapper::terminate_compute_system(const std::string& compute_ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "pause_compute_system(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "pause_compute_system(...) > name: ({})", compute_system_name); static constexpr wchar_t c_pauseOption[] = LR"( { "SuspensionLevel": "Suspend", @@ -382,7 +373,7 @@ OperationResult HCSWrapper::pause_compute_system(const std::string& compute_syst OperationResult HCSWrapper::resume_compute_system(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "resume_compute_system(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "resume_compute_system(...) > name: ({})", compute_system_name); return perform_hcs_operation(api, api.ResumeComputeSystem, compute_system_name, nullptr); } @@ -390,7 +381,7 @@ OperationResult HCSWrapper::resume_compute_system(const std::string& compute_sys OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) const { - mpl::log(lvl::debug, kLogCategory, "add_endpoint(...) > params: {}", params); + mpl::debug(kLogCategory, "add_endpoint(...) > params: {}", params); constexpr auto add_endpoint_settings_template = LR"( {{ "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{{{0}}}", @@ -418,11 +409,10 @@ OperationResult HCSWrapper::add_endpoint(const AddEndpointParameters& params) co OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_name, const std::string& endpoint_guid) const { - mpl::log(lvl::debug, - kLogCategory, - "remove_endpoint(...) > name: ({}), endpoint_guid: ({})", - compute_system_name, - endpoint_guid); + mpl::debug(kLogCategory, + "remove_endpoint(...) > name: ({}), endpoint_guid: ({})", + compute_system_name, + endpoint_guid); constexpr auto remove_endpoint_settings_template = LR"( {{ @@ -440,11 +430,7 @@ OperationResult HCSWrapper::remove_endpoint(const std::string& compute_system_na OperationResult HCSWrapper::resize_memory(const std::string& compute_system_name, std::uint32_t new_size_mib) const { // Machine must be booted up. - mpl::log(lvl::debug, - kLogCategory, - "resize_memory(...) > name: ({}), new_size_mb: ({})", - compute_system_name, - new_size_mib); + mpl::debug(kLogCategory, "resize_memory(...) > name: ({}), new_size_mb: ({})", compute_system_name, new_size_mib); // https://learn.microsoft.com/en-us/virtualization/api/hcs/reference/hcsmodifycomputesystem#remarks constexpr auto resize_memory_settings_template = LR"( {{ @@ -470,7 +456,7 @@ OperationResult HCSWrapper::update_cpu_count(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_properties(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "get_compute_system_properties(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "get_compute_system_properties(...) > name: ({})", compute_system_name); // https://learn.microsoft.com/en-us/virtualization/api/hcs/schemareference#System_PropertyType static constexpr wchar_t c_VmQuery[] = LR"( @@ -486,11 +472,10 @@ OperationResult HCSWrapper::get_compute_system_properties(const std::string& com OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log(lvl::debug, - kLogCategory, - "grant_vm_access(...) > name: ({}), file_path: ({})", - compute_system_name, - file_path.string()); + mpl::debug(kLogCategory, + "grant_vm_access(...) > name: ({}), file_path: ({})", + compute_system_name, + file_path.string()); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); @@ -503,11 +488,10 @@ OperationResult HCSWrapper::grant_vm_access(const std::string& compute_system_na OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_name, const std::filesystem::path& file_path) const { - mpl::log(lvl::debug, - kLogCategory, - "revoke_vm_access(...) > name: ({}), file_path: ({}) ", - compute_system_name, - file_path.string()); + mpl::debug(kLogCategory, + "revoke_vm_access(...) > name: ({}), file_path: ({}) ", + compute_system_name, + file_path.string()); const auto path_as_wstring = file_path.wstring(); const auto csname_as_wstring = string_to_wstring(compute_system_name); @@ -519,7 +503,7 @@ OperationResult HCSWrapper::revoke_vm_access(const std::string& compute_system_n OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_system_name) const { - mpl::log(lvl::debug, kLogCategory, "get_compute_system_state(...) > name: ({})", compute_system_name); + mpl::debug(kLogCategory, "get_compute_system_state(...) > name: ({})", compute_system_name); const auto result = perform_hcs_operation(api, api.GetComputeSystemProperties, compute_system_name, nullptr); if (!result) diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index d6ac20e2f2..65f11237f5 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -45,7 +45,7 @@ constexpr auto kLogCategory = "HyperV-VirtDisk-Wrapper"; UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesystem::path& vhdx_path) { - mpl::log(lvl::debug, kLogCategory, "open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string()); + mpl::debug(kLogCategory, "open_virtual_disk(...) > vhdx_path: {}", vhdx_path.string()); // // Specify UNKNOWN for both device and vendor so the system will use the // file extension to determine the correct VHD format. @@ -73,7 +73,7 @@ UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesyste if (!(result == ERROR_SUCCESS)) { - mpl::log(lvl::error, kLogCategory, "open_virtual_disk(...) > OpenVirtualDisk failed with: {}", result); + mpl::error(kLogCategory, "open_virtual_disk(...) > OpenVirtualDisk failed with: {}", result); return UniqueHandle{nullptr, api.CloseHandle}; } @@ -86,14 +86,14 @@ UniqueHandle open_virtual_disk(const VirtDiskAPITable& api, const std::filesyste VirtDiskWrapper::VirtDiskWrapper(const VirtDiskAPITable& api_table) : api{api_table} { - mpl::log(lvl::debug, kLogCategory, "VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api); + mpl::debug(kLogCategory, "VirtDiskWrapper::VirtDiskWrapper(...) > api_table: {}", api); } // --------------------------------------------------------- OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskParameters& params) const { - mpl::log(lvl::debug, kLogCategory, "create_virtual_disk(...) > params: {}", params); + mpl::debug(kLogCategory, "create_virtual_disk(...) > params: {}", params); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/CreateVirtualDisk.cpp // @@ -153,7 +153,7 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara return OperationResult{NOERROR, L""}; } - mpl::log(lvl::error, kLogCategory, "create_virtual_disk(...) > CreateVirtualDisk failed with {}!", result); + mpl::error(kLogCategory, "create_virtual_disk(...) > CreateVirtualDisk failed with {}!", result); return OperationResult{E_FAIL, fmt::format(L"CreateVirtualDisk failed with {}!", result)}; } @@ -162,11 +162,10 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path& vhdx_path, std::uint64_t new_size_bytes) const { - mpl::log(lvl::debug, - kLogCategory, - "resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", - vhdx_path.string(), - new_size_bytes); + mpl::debug(kLogCategory, + "resize_virtual_disk(...) > vhdx_path: {}, new_size_bytes: {}", + vhdx_path.string(), + new_size_bytes); const auto disk_handle = open_virtual_disk(api, vhdx_path); if (nullptr == disk_handle) @@ -194,7 +193,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path return OperationResult{NOERROR, L""}; } - mpl::log(lvl::error, kLogCategory, "resize_virtual_disk(...) > ResizeVirtualDisk failed with {}!", resize_result); + mpl::error(kLogCategory, "resize_virtual_disk(...) > ResizeVirtualDisk failed with {}!", resize_result); return OperationResult{E_FAIL, fmt::format(L"ResizeVirtualDisk failed with {}!", resize_result)}; } @@ -204,7 +203,7 @@ OperationResult VirtDiskWrapper::resize_virtual_disk(const std::filesystem::path OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::path& vhdx_path, VirtualDiskInfo& vdinfo) const { - mpl::log(lvl::debug, kLogCategory, "get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string()); + mpl::debug(kLogCategory, "get_virtual_disk_info(...) > vhdx_path: {}", vhdx_path.string()); // // https://github.com/microsoft/Windows-classic-samples/blob/main/Samples/Hyper-V/Storage/cpp/GetVirtualDiskInformation.cpp // @@ -300,10 +299,7 @@ OperationResult VirtDiskWrapper::get_virtual_disk_info(const std::filesystem::pa } else { - mpl::log(lvl::warning, - kLogCategory, - "get_virtual_disk_info(...) > failed to get {}", - fmt::underlying(version)); + mpl::warn(kLogCategory, "get_virtual_disk_info(...) > failed to get {}", fmt::underlying(version)); } } From 3497b6c9d37f310b197b8ee2b606e772170f6f68 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Mar 2025 20:38:11 +0300 Subject: [PATCH 21/26] [hyperv-api] link required libraries via CMake Also validate whether the libraries and their main headers are present in the environment. --- .../backends/hyperv_api/CMakeLists.txt | 31 +++++++++++++++++-- .../hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp | 3 -- .../virtdisk/virtdisk_api_wrapper.cpp | 2 -- 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt index 698fc74b13..79210a2e67 100644 --- a/src/platform/backends/hyperv_api/CMakeLists.txt +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -14,9 +14,33 @@ # if(WIN32) - add_library(hyperv_api_backend STATIC) - target_sources(hyperv_api_backend PRIVATE + include(CheckCXXSourceRuns) + + macro(check_pragma_lib LIB_NAME HEADER_NAME OUT_VAR) + check_cxx_source_runs(" + #pragma comment(lib, \"${LIB_NAME}\") + #define WIN32_LEAN_AND_MEAN + #include + #include <${HEADER_NAME}> + int main(void){ return 0; } + " ${OUT_VAR}) + endmacro() + + check_pragma_lib("computecore.lib" "computecore.h" HAS_COMPUTECORE) + check_pragma_lib("computenetwork.lib" "computenetwork.h" HAS_COMPUTENETWORK) + check_pragma_lib("virtdisk.lib" "virtdisk.h" HAS_VIRTDISK) + + if(NOT (HAS_COMPUTECORE AND HAS_COMPUTENETWORK AND HAS_VIRTDISK)) + message(FATAL_ERROR + "[hyperv_api] One or more required libraries are missing:\n" + " HAS_COMPUTECORE_LIB=${HAS_COMPUTECORE_LIB}\n" + " HAS_COMPUTENETWORK_LIB=${HAS_COMPUTENETWORK_LIB}\n" + " HAS_VIRTDISK_LIB=${HAS_VIRTDISK_LIB}\n" + ) + endif() + + add_library(hyperv_api_backend STATIC hyperv_api_common.cpp hcn/hyperv_hcn_api_wrapper.cpp hcs/hyperv_hcs_api_wrapper.cpp @@ -26,5 +50,8 @@ if(WIN32) target_link_libraries(hyperv_api_backend PRIVATE fmt utils + computecore.lib + computenetwork.lib + virtdisk.lib ) endif() diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp index 8ade1a325c..7a5394bff2 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_api_wrapper.cpp @@ -32,9 +32,6 @@ #include #include #include // HCN API uses CoTaskMem* functions to allocate memory. - -#pragma comment(lib, "computecore.lib") -#pragma comment(lib, "computenetwork.lib") // clang-format on #include diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index 65f11237f5..a58d3b5ab8 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -21,10 +21,8 @@ #include #include -#pragma comment(lib, "virtdisk.lib") #include - #include namespace multipass::hyperv::virtdisk From 2597a3fc425f90146a3f1ad2b3d3675a634c1991 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Mar 2025 21:19:18 +0300 Subject: [PATCH 22/26] [hyperv-api] review changes - scsi_devices: explicitly capture params - use maybe_unused and _ for the legitimate use of unused variables - constify get_compute_system_state - add an alias for std::make_unsigned_t - remove redundant index specifiers from format strings - mark auto_remove_path dtor noexcept - check the return value of _mktemp_s - check the return values of all calls in component integration tests --- .../hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp | 15 ++++---- .../hyperv_api/hyperv_api_operation_result.h | 9 ++--- .../virtdisk/virtdisk_api_wrapper.cpp | 3 +- tests/hyperv_api/hyperv_test_utils.h | 13 ++++--- tests/hyperv_api/test_bb_cit_hyperv.cpp | 34 ++++++++++++++----- 5 files changed, 46 insertions(+), 28 deletions(-) diff --git a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp index 3c262f3ab3..34c6482e87 100644 --- a/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/hcs/hyperv_hcs_api_wrapper.cpp @@ -222,7 +222,7 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam // Fill the SCSI devices template depending on // available drives. - const auto scsi_devices = [&]() { + const auto scsi_devices = [¶ms]() { constexpr auto scsi_device_template = LR"( "{0}": {{ "Attachments": {{ @@ -319,8 +319,7 @@ OperationResult HCSWrapper::create_compute_system(const CreateComputeSystemParam ResultCode{api.CreateComputeSystem(name_w.c_str(), vm_settings.c_str(), operation.get(), nullptr, &system)}; // Auto-release the system handle - UniqueHcsSystem system_u{system, api.CloseComputeSystem}; - (void)system_u; + [[maybe_unused]] UniqueHcsSystem _{system, api.CloseComputeSystem}; if (!result) { @@ -511,13 +510,13 @@ OperationResult HCSWrapper::get_compute_system_state(const std::string& compute_ return {result.code, L"Unknown"}; } - QString qstr{QString::fromStdWString(result.status_msg)}; - auto doc = QJsonDocument::fromJson(qstr.toUtf8()); - auto obj = doc.object(); + const QString qstr{QString::fromStdWString(result.status_msg)}; + const auto doc = QJsonDocument::fromJson(qstr.toUtf8()); + const auto obj = doc.object(); if (obj.contains("State")) { - auto state = obj["State"]; - auto state_str = state.toString(); + const auto state = obj["State"]; + const auto state_str = state.toString(); return {result.code, state_str.toStdWString()}; } diff --git a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h index 9c1c49a9b0..0a906df477 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_operation_result.h +++ b/src/platform/backends/hyperv_api/hyperv_api_operation_result.h @@ -33,6 +33,7 @@ namespace multipass::hyperv */ struct ResultCode { + using unsigned_hresult_t = std::make_unsigned_t; ResultCode(HRESULT r) noexcept : result(r) { @@ -53,9 +54,9 @@ struct ResultCode return result; } - [[nodiscard]] explicit operator std::make_unsigned_t() const noexcept + [[nodiscard]] explicit operator unsigned_hresult_t() const noexcept { - return static_cast(result); + return static_cast(result); } private: @@ -104,7 +105,7 @@ struct fmt::formatter template auto format(const multipass::hyperv::ResultCode& rc, FormatContext& ctx) const { - return format_to(ctx.out(), "{0:#x}", static_cast>(rc)); + return format_to(ctx.out(), "{:#x}", static_cast>(rc)); } }; @@ -122,7 +123,7 @@ struct fmt::formatter template auto format(const multipass::hyperv::OperationResult& opr, FormatContext& ctx) const { - return format_to(ctx.out(), "{0:#x}", opr.code); + return format_to(ctx.out(), "{:#x}", opr.code); } }; diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index a58d3b5ab8..eb8c2ed778 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -146,8 +146,7 @@ OperationResult VirtDiskWrapper::create_virtual_disk(const CreateVirtualDiskPara if (result == ERROR_SUCCESS) { - UniqueHandle _{result_handle, api.CloseHandle}; - (void)_; + [[maybe_unused]] UniqueHandle _{result_handle, api.CloseHandle}; return OperationResult{NOERROR, L""}; } diff --git a/tests/hyperv_api/hyperv_test_utils.h b/tests/hyperv_api/hyperv_test_utils.h index 0aa9829a38..f301aba5e1 100644 --- a/tests/hyperv_api/hyperv_test_utils.h +++ b/tests/hyperv_api/hyperv_test_utils.h @@ -43,9 +43,10 @@ inline auto make_tempfile_path(std::string extension) { } - ~auto_remove_path() + ~auto_remove_path() noexcept { std::error_code ec{}; + // Use the noexcept overload std::filesystem::remove(path, ec); } @@ -60,10 +61,12 @@ inline auto make_tempfile_path(std::string extension) const std::filesystem::path path; }; char pattern[] = "temp-XXXXXX"; - _mktemp_s(pattern); - std::string f = pattern; - f.append(extension); - return auto_remove_path{std::filesystem::temp_directory_path() / f}; + if (_mktemp_s(pattern) != 0) + { + throw std::runtime_error{"Incorrect format for _mktemp_s."}; + } + const auto filename = pattern + extension; + return auto_remove_path{std::filesystem::temp_directory_path() / filename}; } } // namespace multipass::test diff --git a/tests/hyperv_api/test_bb_cit_hyperv.cpp b/tests/hyperv_api/test_bb_cit_hyperv.cpp index 3d12950733..1fa8740676 100644 --- a/tests/hyperv_api/test_bb_cit_hyperv.cpp +++ b/tests/hyperv_api/test_bb_cit_hyperv.cpp @@ -62,10 +62,6 @@ TEST_F(HyperV_ComponentIntegrationTests, spawn_empty_test_vm) return endpoint_parameters; }(); - // Remove remnants from previous tests, if any. - (void)hcn.delete_endpoint(endpoint_parameters.endpoint_guid); - (void)hcn.delete_network(network_parameters.guid); - const auto temp_path = make_tempfile_path(".vhdx"); const auto create_disk_parameters = [&temp_path]() { @@ -83,7 +79,22 @@ TEST_F(HyperV_ComponentIntegrationTests, spawn_empty_test_vm) return vm_parameters; }(); - (void)hcs.terminate_compute_system(create_vm_parameters.name); + // Remove remnants from previous tests, if any. + { + if (hcn.delete_endpoint(endpoint_parameters.endpoint_guid)) + { + GTEST_LOG_(WARNING) << "The test endpoint was already present, deleted it."; + } + if (hcn.delete_network(network_parameters.guid)) + { + GTEST_LOG_(WARNING) << "The test network was already present, deleted it."; + } + + if (hcs.terminate_compute_system(create_vm_parameters.name)) + { + GTEST_LOG_(WARNING) << "The test system was already present, terminated it."; + } + } const auto add_endpoint_parameters = [&create_vm_parameters, &endpoint_parameters]() { hyperv::hcs::AddEndpointParameters add_endpoint_parameters{}; @@ -97,42 +108,47 @@ TEST_F(HyperV_ComponentIntegrationTests, spawn_empty_test_vm) { const auto& [status, status_msg] = hcn.create_network(network_parameters); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } // Create the test endpoint { const auto& [status, status_msg] = hcn.create_endpoint(endpoint_parameters); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } // Create the test VHDX (empty) { const auto& [status, status_msg] = virtdisk.create_virtual_disk(create_disk_parameters); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } // Create test VM { const auto& [status, status_msg] = hcs.create_compute_system(create_vm_parameters); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } // Start test VM { const auto& [status, status_msg] = hcs.start_compute_system(create_vm_parameters.name); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } // Add endpoint { const auto& [status, status_msg] = hcs.add_endpoint(add_endpoint_parameters); - fmt::print(L"{}", status_msg.c_str()); ASSERT_TRUE(status); + ASSERT_TRUE(status_msg.empty()); } - (void)hcs.terminate_compute_system(create_vm_parameters.name); - (void)hcn.delete_endpoint(endpoint_parameters.endpoint_guid); - (void)hcn.delete_network(network_parameters.guid); + EXPECT_TRUE(hcs.terminate_compute_system(create_vm_parameters.name)) << "Terminate system failed!"; + EXPECT_TRUE(hcn.delete_endpoint(endpoint_parameters.endpoint_guid)) << "Delete endpoint failed!"; + EXPECT_TRUE(hcn.delete_network(network_parameters.guid)) << "Delete network failed!"; } } // namespace multipass::test From 6ecb885949b20540ce43c9913b52be475efe472e Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Mar 2025 21:37:17 +0300 Subject: [PATCH 23/26] [hyperv-virtdisk] code review changes for the unit tests --- tests/hyperv_api/test_ut_hyperv_virtdisk.cpp | 45 ++++++++------------ 1 file changed, 17 insertions(+), 28 deletions(-) diff --git a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp index 4bf8d3b209..0365eb6637 100644 --- a/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp +++ b/tests/hyperv_api/test_ut_hyperv_virtdisk.cpp @@ -27,8 +27,7 @@ namespace mpt = multipass::test; namespace mpl = multipass::logging; -using testing::DoAll; -using testing::Return; +using namespace testing; namespace multipass::test { @@ -134,8 +133,7 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhdx_happy_path) }, Return(ERROR_SUCCESS))); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( @@ -150,8 +148,8 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhdx_happy_path) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.create_virtual_disk(params); - ASSERT_TRUE(status); - ASSERT_TRUE(status_msg.empty()); + EXPECT_TRUE(status); + EXPECT_TRUE(status_msg.empty()); } } @@ -207,8 +205,7 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhd_happy_path) }, Return(ERROR_SUCCESS))); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( @@ -223,8 +220,8 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_vhd_happy_path) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.create_virtual_disk(params); - ASSERT_TRUE(status); - ASSERT_TRUE(status_msg.empty()); + EXPECT_TRUE(status); + EXPECT_TRUE(status_msg.empty()); } } @@ -273,7 +270,7 @@ TEST_F(HyperVVirtDisk_UnitTests, create_virtual_disk_failed) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.create_virtual_disk(params); - ASSERT_FALSE(status); + EXPECT_FALSE(status); ASSERT_FALSE(status_msg.empty()); ASSERT_STREQ(status_msg.c_str(), L"CreateVirtualDisk failed with 3!"); } @@ -337,9 +334,7 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_happy_path) }, Return(ERROR_SUCCESS))); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( mpl::Level::debug, @@ -350,8 +345,8 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_happy_path) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); - ASSERT_TRUE(status); - ASSERT_TRUE(status_msg.empty()); + EXPECT_TRUE(status); + EXPECT_TRUE(status_msg.empty()); } } @@ -395,7 +390,7 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_open_failed) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); - ASSERT_FALSE(status); + EXPECT_FALSE(status); ASSERT_FALSE(status_msg.empty()); ASSERT_STREQ(status_msg.c_str(), L"open_virtual_disk failed!"); } @@ -437,9 +432,7 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_resize_failed) LPOVERLAPPED Overlapped) {}, Return(ERROR_INVALID_PARAMETER))); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log( mpl::Level::debug, @@ -452,7 +445,7 @@ TEST_F(HyperVVirtDisk_UnitTests, resize_virtual_disk_resize_failed) { uut_t uut{mock_api_table}; const auto& [status, status_msg] = uut.resize_virtual_disk("test.vhdx", 1234567); - ASSERT_FALSE(status); + EXPECT_FALSE(status); ASSERT_FALSE(status_msg.empty()); ASSERT_STREQ(status_msg.c_str(), L"ResizeVirtualDisk failed with 87!"); } @@ -564,9 +557,7 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_happy_path) }, Return(ERROR_SUCCESS))); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); @@ -580,9 +571,9 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_happy_path) ASSERT_TRUE(status_msg.empty()); ASSERT_TRUE(info.size.has_value()); - ASSERT_TRUE(info.virtual_storage_type.has_value()); ASSERT_TRUE(info.smallest_safe_virtual_size.has_value()); ASSERT_TRUE(info.provider_subtype.has_value()); + ASSERT_TRUE(info.virtual_storage_type.has_value()); ASSERT_EQ(info.size->virtual_, 1111111); ASSERT_EQ(info.size->block, 2222222); @@ -674,9 +665,7 @@ TEST_F(HyperVVirtDisk_UnitTests, get_virtual_disk_info_fail_some) Return(ERROR_SUCCESS))) .WillOnce(Return(ERROR_INVALID_PARAMETER)); - EXPECT_CALL(mock_close_handle, Call) - .WillOnce(DoAll([](HANDLE hObject) { ASSERT_EQ(hObject, mock_handle_object); }, Return(true))); - + EXPECT_CALL(mock_close_handle, Call(Eq(mock_handle_object))).WillOnce(Return(true)); logger_scope.mock_logger->expect_log(mpl::Level::debug, "VirtDiskWrapper::VirtDiskWrapper(...)"); logger_scope.mock_logger->expect_log(mpl::Level::debug, "get_virtual_disk_info(...) > vhdx_path: test.vhdx"); logger_scope.mock_logger->expect_log(mpl::Level::debug, "open_virtual_disk(...) > vhdx_path: test.vhdx"); From b705973913763ceebd241d341fdb80b6b827580f Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Tue, 18 Mar 2025 22:20:59 +0300 Subject: [PATCH 24/26] [hyperv-api-common] guid_from_wstring: switch to CLSIDFromString --- .../backends/hyperv_api/hyperv_api_common.cpp | 70 ++++++++----------- .../virtdisk/virtdisk_api_wrapper.cpp | 7 +- 2 files changed, 33 insertions(+), 44 deletions(-) diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp index 17fa65acd9..133745ea59 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.cpp +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -16,13 +16,14 @@ */ #include +#include #include -#include +#include // for CLSIDFromString + #include #include -#include /** * Formatter for GUID type @@ -65,57 +66,44 @@ struct fmt::formatter<::GUID, Char> namespace multipass::hyperv { -auto guid_from_wstring(const std::wstring& guid_wstr) -> ::GUID +struct GuidParseError : FormattedExceptionBase<> { + using FormattedExceptionBase<>::FormattedExceptionBase; +}; - // Since we're using the windows.h in LEAN_AND_MEAN mode, COM-provided - // GUID parsing functions such as CLSIDFromString are not available. - - auto iterator = guid_wstr.begin(); - ::GUID result = {}; - +auto guid_from_wstring(const std::wstring& guid_wstr) -> ::GUID +{ constexpr auto kGUIDLength = 36; constexpr auto kGUIDLengthWithBraces = kGUIDLength + 2; - constexpr auto kGUIDFormatString = L"%8x-%4hx-%4hx-%2hhx%2hhx-%2hhx%2hhx%2hhx%2hhx%2hhx%2hhx"; - constexpr auto kGUIDFieldCount = 11; - if (guid_wstr.length() == kGUIDLengthWithBraces) - { - - if (guid_wstr[0] != L'{' || guid_wstr[guid_wstr.length() - 1] != L'}') + const auto input = [&guid_wstr]() { + switch (guid_wstr.length()) + { + case kGUIDLength: + // CLSIDFromString requires GUIDs to be wrapped with braces. + return fmt::format(L"{{{}}}", guid_wstr); + case kGUIDLengthWithBraces: { - throw std::invalid_argument{"GUID string with brances either does not start or end with a brace."}; + if (*guid_wstr.begin() != L'{' || *std::prev(guid_wstr.end()) != L'}') + { + throw GuidParseError{"GUID string either does not start or end with a brace."}; + } + return guid_wstr; } + } + throw GuidParseError{"Invalid length for a GUID string ({}).", guid_wstr.length()}; + }(); - // Skip the initial brace character - std::advance(iterator, 1); - } - else if (guid_wstr.length() != kGUIDLength) - { - throw std::domain_error(fmt::format("Invalid GUID string {}.", guid_wstr.length())); - } + ::GUID guid = {}; + + const auto result = CLSIDFromString(input.c_str(), &guid); - const auto match_count = swscanf_s(&(*iterator), - kGUIDFormatString, - &result.Data1, - &result.Data2, - &result.Data3, - &result.Data4[0], - &result.Data4[1], - &result.Data4[2], - &result.Data4[3], - &result.Data4[4], - &result.Data4[5], - &result.Data4[6], - &result.Data4[7]); - - // Ensure that the swscanf parsed the exact amount of fields - if (!(match_count == kGUIDFieldCount)) + if (FAILED(result)) { - throw std::runtime_error("Failed to parse GUID string"); + throw GuidParseError{"Failed to parse the GUID string ({}).", result}; } - return result; + return guid; } // --------------------------------------------------------- diff --git a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp index eb8c2ed778..b419919fd8 100644 --- a/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp +++ b/src/platform/backends/hyperv_api/virtdisk/virtdisk_api_wrapper.cpp @@ -17,13 +17,14 @@ #include +// clang-format off +#include #include #include -#include - +// clang-format on -#include #include +#include namespace multipass::hyperv::virtdisk { From f4731029216a1e7067cc93415fbbb59cd460fcee Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Thu, 8 May 2025 22:51:59 +0300 Subject: [PATCH 25/26] [hyperv-cmake] update fmt reference with fmt::fmt-header-only --- src/platform/backends/hyperv_api/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/platform/backends/hyperv_api/CMakeLists.txt b/src/platform/backends/hyperv_api/CMakeLists.txt index 79210a2e67..ccfdf570df 100644 --- a/src/platform/backends/hyperv_api/CMakeLists.txt +++ b/src/platform/backends/hyperv_api/CMakeLists.txt @@ -48,7 +48,7 @@ if(WIN32) ) target_link_libraries(hyperv_api_backend PRIVATE - fmt + fmt::fmt-header-only utils computecore.lib computenetwork.lib From c02b4823d8a4fa013a3e4beb977bfe9f7efb2353 Mon Sep 17 00:00:00 2001 From: Mustafa Kemal Gilor Date: Wed, 9 Jul 2025 14:26:16 +0300 Subject: [PATCH 26/26] [hyperv-hcs] `constexpr` -> `constexpr static` to make msvc 19 happy Signed-off-by: Mustafa Kemal Gilor --- .../backends/hyperv_api/hyperv_api_common.cpp | 4 +-- tests/hyperv_api/test_ut_hyperv_hcs_api.cpp | 36 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/platform/backends/hyperv_api/hyperv_api_common.cpp b/src/platform/backends/hyperv_api/hyperv_api_common.cpp index 133745ea59..3c1c37b213 100644 --- a/src/platform/backends/hyperv_api/hyperv_api_common.cpp +++ b/src/platform/backends/hyperv_api/hyperv_api_common.cpp @@ -73,8 +73,8 @@ struct GuidParseError : FormattedExceptionBase<> auto guid_from_wstring(const std::wstring& guid_wstr) -> ::GUID { - constexpr auto kGUIDLength = 36; - constexpr auto kGUIDLengthWithBraces = kGUIDLength + 2; + constexpr static auto kGUIDLength = 36; + constexpr static auto kGUIDLengthWithBraces = kGUIDLength + 2; const auto input = [&guid_wstr]() { switch (guid_wstr.length()) diff --git a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp index 36effca062..b09ad775fb 100644 --- a/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp +++ b/tests/hyperv_api/test_ut_hyperv_hcs_api.cpp @@ -184,7 +184,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_happy_path) mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); mock_api_table.LocalFree = mock_local_free.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -350,7 +350,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit) mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); mock_api_table.LocalFree = mock_local_free.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -507,7 +507,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_vhdx) mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); mock_api_table.LocalFree = mock_local_free.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -664,7 +664,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wo_cloudinit_and_vhdx) mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); mock_api_table.LocalFree = mock_local_free.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -857,7 +857,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_fail) mock_api_table.CloseOperation = mock_close_operation.AsStdFunction(); mock_api_table.CreateComputeSystem = mock_create_compute_system.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -1000,7 +1000,7 @@ TEST_F(HyperVHCSAPI_UnitTests, create_compute_system_wait_for_operation_fail) mock_api_table.CloseComputeSystem = mock_close_compute_system.AsStdFunction(); mock_api_table.LocalFree = mock_local_free.AsStdFunction(); - constexpr auto expected_vm_settings_json = LR"( + constexpr static auto expected_vm_settings_json = LR"( { "SchemaVersion": { "Major": 2, @@ -2162,7 +2162,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resume_compute_system_wait_for_operation_result_f TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_happy_path) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Add", @@ -2227,7 +2227,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_create_operation_f TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Add", @@ -2261,7 +2261,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_fail) TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_wait_for_operation_result_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Add", @@ -2295,7 +2295,7 @@ TEST_F(HyperVHCSAPI_UnitTests, add_endpoint_to_compute_system_wait_for_operation TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_happy_path) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Remove" @@ -2346,7 +2346,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_create_operat TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Remove" @@ -2371,7 +2371,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_fail) TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_wait_for_operation_result_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/Devices/NetworkAdapters/{288cc1ac-8f31-4a09-9e90-30ad0bcfdbca}", "RequestType": "Remove" @@ -2396,7 +2396,7 @@ TEST_F(HyperVHCSAPI_UnitTests, remove_endpoint_from_compute_system_wait_for_oper TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_happy_path) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", "RequestType": "Update", @@ -2447,7 +2447,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_create_operation_ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", "RequestType": "Update", @@ -2473,7 +2473,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_fail) TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_wait_for_operation_result_fail) { - constexpr auto expected_modify_compute_system_configuration = LR"( + constexpr static auto expected_modify_compute_system_configuration = LR"( { "ResourcePath": "VirtualMachine/ComputeTopology/Memory/SizeInMB", "RequestType": "Update", @@ -2499,7 +2499,7 @@ TEST_F(HyperVHCSAPI_UnitTests, resize_memory_of_compute_system_wait_for_operatio TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_happy_path) { - constexpr auto expected_vm_query = LR"( + constexpr static auto expected_vm_query = LR"( { "PropertyTypes":[] })"; @@ -2548,7 +2548,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_create_operation_fa TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_fail) { - constexpr auto expected_vm_query = LR"( + constexpr static auto expected_vm_query = LR"( { "PropertyTypes":[] })"; @@ -2572,7 +2572,7 @@ TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_fail) TEST_F(HyperVHCSAPI_UnitTests, get_compute_system_properties_wait_for_operation_result_fail) { - constexpr auto expected_vm_query = LR"( + constexpr static auto expected_vm_query = LR"( { "PropertyTypes":[] })";