diff --git a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_ipam_type.h b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_ipam_type.h index 1da0d6e580..8999dd66f1 100644 --- a/src/platform/backends/hyperv_api/hcn/hyperv_hcn_ipam_type.h +++ b/src/platform/backends/hyperv_api/hcn/hyperv_hcn_ipam_type.h @@ -43,6 +43,11 @@ struct HcnIpamType : FormatAsMixin return {"static"}; } + [[nodiscard]] bool operator==(const HcnIpamType& rhs) const + { + return value == rhs.value; + } + private: HcnIpamType(std::string_view v) : value(v) { diff --git a/src/platform/backends/hyperv_api/hcs_virtual_machine.cpp b/src/platform/backends/hyperv_api/hcs_virtual_machine.cpp index f7b8e1503f..1564c8835c 100644 --- a/src/platform/backends/hyperv_api/hcs_virtual_machine.cpp +++ b/src/platform/backends/hyperv_api/hcs_virtual_machine.cpp @@ -425,6 +425,12 @@ void HCSVirtualMachine::set_state(hcs::ComputeSystemState compute_system_state) { mpl::debug(get_name(), "set_state() -> HCS state `{}`", compute_system_state); + if (state == State::unavailable) + { + mpl::debug(get_name(), "set_state() -> Zone is unavailable"); + return; + } + const auto prev_state = state; switch (compute_system_state) { @@ -449,7 +455,7 @@ void HCSVirtualMachine::set_state(hcs::ComputeSystemState compute_system_state) if (state == prev_state) return; - mpl::info(get_name(), "set_state() > State changed from {} to {}", prev_state, state); + mpl::info(get_name(), "set_state() -> State changed from {} to {}", prev_state, state); } void HCSVirtualMachine::start() diff --git a/src/platform/backends/hyperv_api/hcs_virtual_machine_exceptions.h b/src/platform/backends/hyperv_api/hcs_virtual_machine_exceptions.h index 5db1cfd7d8..b76744c0ec 100644 --- a/src/platform/backends/hyperv_api/hcs_virtual_machine_exceptions.h +++ b/src/platform/backends/hyperv_api/hcs_virtual_machine_exceptions.h @@ -72,11 +72,16 @@ struct ResizeDiskException : public FormattedExceptionBase<> using FormattedExceptionBase::FormattedExceptionBase; }; -struct CreateBridgeException : public FormattedExceptionBase<> +struct CreateNetworkException : public FormattedExceptionBase<> { using FormattedExceptionBase::FormattedExceptionBase; }; +struct CreateBridgeException : public CreateNetworkException +{ + using CreateNetworkException::CreateNetworkException; +}; + struct WindowsFeatureNotEnabledException : public FormattedExceptionBase<> { using FormattedExceptionBase::FormattedExceptionBase; diff --git a/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.cpp b/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.cpp index 9d3ee1d75d..de03844fed 100644 --- a/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.cpp +++ b/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.cpp @@ -58,7 +58,6 @@ using hcs::HCS; using virtdisk::VirtDisk; constexpr auto log_category = "HyperV-Virtual-Machine-Factory"; -constexpr auto default_hyperv_switch_guid = "C08CB7B8-9B3C-408E-8E30-5E16A3AEB444"; constexpr auto extra_interface_vswitch_name_fmtstr = "Multipass vSwitch ({})"; /** * Regex pattern to extract the origin network name and GUID from an extra interface @@ -72,7 +71,8 @@ HCSVirtualMachineFactory::HCSVirtualMachineFactory(const Path& data_dir, MP_UTILS.derive_instances_dir(data_dir, HCSVirtualMachineFactory::get_backend_directory_name(), instances_subdir), - az_manager) + az_manager), + az_network_guids{create_az_bridges(az_manager.get_zones())} { } @@ -81,7 +81,7 @@ VirtualMachine::UPtr HCSVirtualMachineFactory::create_virtual_machine( const SSHKeyProvider& key_provider, VMStatusMonitor& monitor) { - return std::make_unique(default_hyperv_switch_guid, + return std::make_unique(az_network_guids.at(desc.zone), desc, monitor, key_provider, @@ -343,4 +343,34 @@ void HCSVirtualMachineFactory::hypervisor_health_check() } } +std::unordered_map HCSVirtualMachineFactory::create_az_bridges( + const AvailabilityZoneManager::Zones& zones) +{ + std::unordered_map az_mapping; + for (const auto& i : zones) + { + const auto& zone = i.get(); + hcn::CreateNetworkParameters network_params{ + .name = fmt::format("Multipass vNetwork ({})", zone.get_name()), + .type = hcn::HcnNetworkType::Ics(), + .flags = hcn::HcnNetworkFlags::enable_dhcp_server, + .guid = utils::make_uuid(network_params.name), + .ipams = {{.type = hcn::HcnIpamType::Static(), + .subnets = {{.ip_address_prefix = {zone.get_subnet().to_cidr()}}}}}}; + + const auto create_network_result = HCN().create_network(network_params); + if (!create_network_result && + static_cast(create_network_result.code) != HCN_E_NETWORK_ALREADY_EXISTS) + { + throw CreateNetworkException{"Could not create network for {}, status: {}", + zone.get_name(), + create_network_result}; + } + + az_mapping.emplace(zone.get_name(), network_params.guid); + } + + return az_mapping; +} + } // namespace multipass::hyperv diff --git a/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.h b/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.h index 95cf663a45..bdfe485d07 100644 --- a/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.h +++ b/src/platform/backends/hyperv_api/hcs_virtual_machine_factory.h @@ -19,6 +19,8 @@ #include +#include + namespace multipass::hyperv { @@ -61,5 +63,10 @@ struct HCSVirtualMachineFactory final : public BaseVirtualMachineFactory */ [[nodiscard]] static std::vector get_adapters(); [[nodiscard]] static std::vector get_hyperv_vswitches(); + + [[nodiscard]] static std::unordered_map create_az_bridges( + const AvailabilityZoneManager::Zones& zones); + + std::unordered_map az_network_guids; }; } // namespace multipass::hyperv diff --git a/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine.cpp b/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine.cpp index 6bb1244fc9..19ee065023 100644 --- a/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine.cpp +++ b/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine.cpp @@ -482,6 +482,33 @@ TEST_F(HyperVHCSVirtualMachine_UnitTests, vm_suspend_success) // --------------------------------------------------------- +TEST_F(HyperVHCSVirtualMachine_UnitTests, vm_set_unavailable) +{ + default_open_success(); + + EXPECT_CALL(mock_hcs, get_compute_system_state(Eq(mock_handle), _)) + .WillOnce(DoAll([](const hcs_handle_t&, + hcs_system_state_t& state) { state = hcs_system_state_t::running; }, + Return(hcs_op_result_t{0, L""}))) + .WillOnce(DoAll([](const hcs_handle_t&, + hcs_system_state_t& state) { state = hcs_system_state_t::stopped; }, + Return(hcs_op_result_t{0, L""}))); + + EXPECT_CALL(mock_hcs, terminate_compute_system(Eq(mock_handle))) + .WillOnce(Return(hcs_op_result_t{0, L""})); + + std::shared_ptr uut{nullptr}; + ASSERT_NO_THROW(uut = construct_vm()); + + EXPECT_EQ(uut->state, multipass::VirtualMachine::State::running); + + uut->set_available(false); + + EXPECT_EQ(uut->state, multipass::VirtualMachine::State::unavailable); +} + +// --------------------------------------------------------- + TEST_F(HyperVHCSVirtualMachine_UnitTests, vm_suspend_failure) { default_open_success(); diff --git a/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine_factory.cpp b/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine_factory.cpp index aed49664da..c006a7ae9f 100644 --- a/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine_factory.cpp +++ b/tests/unit/hyperv_api/test_ut_hyperv_hcs_virtual_machine_factory.cpp @@ -70,6 +70,23 @@ struct HyperVHCSVirtualMachineFactory_UnitTests : public ::testing::Test auto construct_factory() { + EXPECT_CALL(mock_hcn, + create_network(Field(&mhv::hcn::CreateNetworkParameters::name, + Eq("Multipass vNetwork (zone1)")))) + .WillOnce(DoAll( + [&](const mhv::hcn::CreateNetworkParameters& params) { + EXPECT_EQ(params.type, mhv::hcn::HcnNetworkType::Ics()); + EXPECT_EQ(params.guid, + multipass::utils::make_uuid("Multipass vNetwork (zone1)")); + EXPECT_EQ(params.policies.size(), 0); + ASSERT_EQ(params.ipams.size(), 1); + EXPECT_EQ(params.ipams[0].type, mhv::hcn::HcnIpamType::Static()); + ASSERT_EQ(params.ipams[0].subnets.size(), 1); + EXPECT_EQ(params.ipams[0].subnets[0].ip_address_prefix, + az_manager.get_zone("zone1").get_subnet().to_cidr()); + }, + Return(hcs_op_result_t{0, L""}))); + return std::make_shared(dummy_data_dir.path(), az_manager); } }; @@ -161,13 +178,14 @@ TEST_F(HyperVHCSVirtualMachineFactory_UnitTests, prepare_instance_image_failed) .WillOnce(Return(hcs_op_result_t{1, L""})); ASSERT_NO_THROW(uut = construct_factory()); - EXPECT_THROW(uut->prepare_instance_image(img, desc), multipass::hyperv::ImageResizeException); + EXPECT_THROW(uut->prepare_instance_image(img, desc), mhv::ImageResizeException); } TEST_F(HyperVHCSVirtualMachineFactory_UnitTests, create_virtual_machine) { std::shared_ptr uut{nullptr}; multipass::VirtualMachineDescription desc; + desc.zone = "zone1"; multipass::NetworkInterfaceInfo interface1{.id = "aabb", .type = "Ethernet"}, interface2{.id = "bbaa", .type = "Ethernet"}; @@ -201,7 +219,7 @@ TEST_F(HyperVHCSVirtualMachineFactory_UnitTests, create_virtual_machine) EXPECT_CALL(mock_hcn, create_network(_)) // only expect call for bbaa. aabb's vSwitch already exists. .WillOnce(DoAll( - [&](const multipass::hyperv::hcn::CreateNetworkParameters& params) { + [&](const mhv::hcn::CreateNetworkParameters& params) { constexpr auto expected_name = "Multipass vSwitch (bbaa)"; EXPECT_EQ(params.name, expected_name); EXPECT_EQ(params.type, mhv::hcn::HcnNetworkType::Transparent()); @@ -209,12 +227,10 @@ TEST_F(HyperVHCSVirtualMachineFactory_UnitTests, create_virtual_machine) ASSERT_EQ(params.policies.size(), 1); EXPECT_EQ(params.policies[0].type, mhv::hcn::HcnNetworkPolicyType::NetAdapterName()); - ASSERT_TRUE( - std::holds_alternative( - params.policies[0].settings)); - const auto& net_adapter_name = - std::get( - params.policies[0].settings); + ASSERT_TRUE(std::holds_alternative( + params.policies[0].settings)); + const auto& net_adapter_name = std::get( + params.policies[0].settings); EXPECT_EQ(net_adapter_name.net_adapter_name, interface2.id); }, Return(hcs_op_result_t{0, L""})));