blob: a46ad1d95b8f782b8cf88806b0ca74aa853ae232 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "src/virtualization/bin/guest_manager/guest_manager.h"
#include <fuchsia/net/interfaces/cpp/fidl.h>
#include <fuchsia/virtualization/cpp/fidl.h>
#include <lib/async/cpp/task.h>
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <lib/syslog/cpp/macros.h>
#include <future>
#include <src/lib/testing/loop_fixture/real_loop_fixture.h>
#include <test/placeholders/cpp/fidl.h>
namespace {
using ::fuchsia::virtualization::GuestConfig;
using ::fuchsia::virtualization::GuestDescriptor;
using ::fuchsia::virtualization::GuestError;
using ::fuchsia::virtualization::GuestLifecycle;
using ::fuchsia::virtualization::GuestManagerError;
class FakeGuestLifecycle : public GuestLifecycle {
public:
FakeGuestLifecycle(sys::testing::ComponentContextProvider* provider,
async_dispatcher_t* dispatcher)
: dispatcher_(dispatcher) {
FX_CHECK(ZX_OK ==
provider->service_directory_provider()->AddService(bindings_.GetHandler(this)));
}
// |fuchsia::virtualization::GuestManager|
void Create(::fuchsia::virtualization::GuestConfig guest_config,
CreateCallback callback) override {
captured_config_ = std::move(guest_config);
if (create_response_.is_ok()) {
callback(fpromise::ok());
} else {
callback(fpromise::error(create_response_.error_value()));
}
}
// |fuchsia::virtualization::GuestManager|
void Bind(fidl::InterfaceRequest<::fuchsia::virtualization::Guest> guest) override {}
// |fuchsia::virtualization::GuestManager|
void Run(RunCallback callback) override { captured_run_callback_ = std::move(callback); }
// |fuchsia::virtualization::GuestManager|
void Stop(StopCallback callback) override {
async::PostTask(dispatcher_, [this]() {
captured_run_callback_(fpromise::error(GuestError::CONTROLLER_FORCED_HALT));
});
callback();
}
// The guest lifecycle provider never intentionally closes the server end of the channel. This
// simulates what happens when the component terminates unexpectedly (such as a crash).
void SimulateCrash() { bindings_.CloseAll(); }
void set_create_response(fit::result<GuestError> err) { create_response_ = err; }
RunCallback take_run_callback() { return std::move(captured_run_callback_); }
GuestConfig take_guest_config() { return std::move(captured_config_); }
private:
fit::result<GuestError> create_response_ = fit::ok();
RunCallback captured_run_callback_;
GuestConfig captured_config_;
fidl::BindingSet<GuestLifecycle> bindings_;
async_dispatcher_t* dispatcher_ = nullptr; // Unowned.
};
class FakeNetInterfaces : public ::fuchsia::net::interfaces::State,
fuchsia::net::interfaces::Watcher {
public:
explicit FakeNetInterfaces(sys::testing::ComponentContextProvider* provider) {
FX_CHECK(ZX_OK ==
provider->service_directory_provider()->AddService(state_bindings_.GetHandler(this)));
}
// |fuchsia::net::interfaces::State|
void GetWatcher(::fuchsia::net::interfaces::WatcherOptions options,
::fidl::InterfaceRequest<fuchsia::net::interfaces::Watcher> watcher) override {
watcher_binding_.Bind(std::move(watcher));
}
// |fuchsia::net::interfaces::Watcher|
void Watch(WatchCallback callback) override {
::fuchsia::net::interfaces::Event event;
if (events_.empty()) {
event.set_idle({});
} else {
event = std::move(events_.front());
events_.pop();
}
callback(std::move(event));
}
void AddExistingInterface(::fuchsia::hardware::network::DeviceClass device_class,
bool online = true) {
::fuchsia::net::interfaces::DeviceClass device;
device.set_device(device_class);
::fuchsia::net::interfaces::Properties properties;
properties.set_device_class(std::move(device));
properties.set_online(online);
::fuchsia::net::interfaces::Event event;
event.set_existing(std::move(properties));
events_.push(std::move(event));
}
private:
std::queue<::fuchsia::net::interfaces::Event> events_;
::fidl::BindingSet<::fuchsia::net::interfaces::State> state_bindings_;
::fidl::Binding<fuchsia::net::interfaces::Watcher> watcher_binding_{this};
};
class GuestManagerTest : public gtest::RealLoopFixture {
public:
void SetUp() override {
RealLoopFixture::SetUp();
fake_net_interfaces_ = std::make_unique<FakeNetInterfaces>(&provider_);
fake_guest_lifecycle_ = std::make_unique<FakeGuestLifecycle>(&provider_, dispatcher());
}
sys::testing::ComponentContextProvider provider_;
std::unique_ptr<FakeNetInterfaces> fake_net_interfaces_;
std::unique_ptr<FakeGuestLifecycle> fake_guest_lifecycle_;
};
TEST_F(GuestManagerTest, LaunchFailInvalidPath) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "invalid_path.cfg");
bool launch_callback_called = false;
manager.Launch({}, {}, [&launch_callback_called](auto res) {
ASSERT_TRUE(res.is_err());
ASSERT_EQ(GuestManagerError::BAD_CONFIG, res.err());
launch_callback_called = true;
});
ASSERT_TRUE(launch_callback_called);
}
TEST_F(GuestManagerTest, LaunchFailInvalidConfig) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/",
"data/configs/bad_schema_invalid_field.cfg");
bool launch_callback_called = false;
manager.Launch({}, {}, [&launch_callback_called](auto res) {
ASSERT_TRUE(res.is_err());
ASSERT_EQ(GuestManagerError::BAD_CONFIG, res.err());
launch_callback_called = true;
});
ASSERT_TRUE(launch_callback_called);
}
TEST_F(GuestManagerTest, ForceShutdownNonRunningGuest) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
bool get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::NOT_STARTED);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
bool shutdown_callback_called = false;
manager.ForceShutdown([&shutdown_callback_called]() { shutdown_callback_called = true; });
RunLoopUntilIdle();
ASSERT_TRUE(shutdown_callback_called);
// Shutting down a non-running guest does nothing, including changing state from NOT_STARTED
// (for example to STOPPING or STOPPED).
get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::NOT_STARTED);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
}
TEST_F(GuestManagerTest, ForceShutdown) {
bool launch_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
bool get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::RUNNING);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
bool shutdown_callback_called = false;
manager.ForceShutdown([&shutdown_callback_called]() { shutdown_callback_called = true; });
RunLoopUntilIdle();
ASSERT_TRUE(shutdown_callback_called);
get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::STOPPED);
ASSERT_EQ(info.stop_error(), GuestError::CONTROLLER_FORCED_HALT);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
}
TEST_F(GuestManagerTest, VmmComponentCrash) {
bool launch_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
bool get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::RUNNING);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
// The VMM controller closing the lifecycle channel means that it went away unexpectedly.
fake_guest_lifecycle_->SimulateCrash();
RunLoopUntilIdle();
get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(),
fuchsia::virtualization::GuestStatus::VMM_UNEXPECTED_TERMINATION);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
}
TEST_F(GuestManagerTest, FailedToCreateAndInitializeVmmWithRestart) {
// Inject a failure into Launch.
fake_guest_lifecycle_->set_create_response(fit::error(GuestError::GUEST_INITIALIZATION_FAILURE));
bool launch_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
{
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_TRUE(res.is_err());
ASSERT_EQ(res.err(), GuestManagerError::START_FAILURE);
launch_callback_called = true;
});
}
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// Second Launch succeeds.
fake_guest_lifecycle_->set_create_response(fit::ok());
launch_callback_called = false;
{
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
}
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
}
TEST_F(GuestManagerTest, GuestInitiatedCleanShutdown) {
bool launch_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
bool get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::RUNNING);
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
// VMM controller only calls the run callback when the guest has terminated.
fake_guest_lifecycle_->take_run_callback()(fpromise::ok());
RunLoopUntilIdle();
get_callback_called = false;
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::STOPPED);
ASSERT_FALSE(info.has_stop_error()); // Clean shutdown.
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
}
TEST_F(GuestManagerTest, LaunchAndApplyUserGuestConfig) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fidl::InterfaceHandle<fuchsia::io::File> file;
fidl::InterfaceRequest request = file.NewRequest();
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_cmdline_add()->emplace_back("extra_cmd_line_arg=0");
user_guest_config.mutable_block_devices()->push_back({
.id = "lessthan20charid",
.mode = fuchsia::virtualization::BlockMode::READ_ONLY,
.format = fuchsia::virtualization::BlockFormat::WithFile(std::move(file)),
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
const GuestConfig config = fake_guest_lifecycle_->take_guest_config();
const fuchsia::virtualization::BlockSpec& spec0 = config.block_devices()[0];
ASSERT_EQ("data", spec0.id);
ASSERT_TRUE(spec0.format.is_file()) << spec0.format.Which();
const fuchsia::virtualization::BlockSpec& spec1 = config.block_devices()[1];
ASSERT_EQ("lessthan20charid", spec1.id);
ASSERT_TRUE(spec1.format.is_file()) << spec1.format.Which();
ASSERT_EQ(2u, config.block_devices().size());
ASSERT_EQ("test cmdline extra_cmd_line_arg=0", config.cmdline());
ASSERT_EQ(fuchsia::virtualization::KernelType::ZIRCON, config.kernel_type());
ASSERT_TRUE(config.kernel());
ASSERT_TRUE(config.ramdisk());
ASSERT_EQ(4u, config.cpus());
}
TEST_F(GuestManagerTest, DoubleLaunchFail) {
bool launch_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_TRUE(res.is_err());
ASSERT_EQ(GuestManagerError::ALREADY_RUNNING, res.err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
}
TEST_F(GuestManagerTest, LaunchAndGetInfo) {
bool get_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
manager.GetInfo([&get_callback_called](auto info) {
ASSERT_EQ(info.guest_status(), fuchsia::virtualization::GuestStatus::NOT_STARTED);
ASSERT_FALSE(info.has_uptime());
ASSERT_FALSE(info.has_guest_descriptor());
ASSERT_FALSE(info.has_stop_error());
get_callback_called = true;
});
ASSERT_TRUE(get_callback_called);
bool launch_callback_called = false;
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.set_default_net(true);
fuchsia::virtualization::GuestPtr guest;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
fuchsia::virtualization::GuestConfig finalized_config =
fake_guest_lifecycle_->take_guest_config();
std::optional<fuchsia::virtualization::GuestInfo> info;
auto handle = std::async(std::launch::async, [&manager, &info] {
manager.GetInfo([&info](auto guest_info) { info = std::move(guest_info); });
});
RunLoopUntil([&info]() { return info.has_value(); });
ASSERT_EQ(info->guest_status(), fuchsia::virtualization::GuestStatus::RUNNING);
ASSERT_GT(info->uptime(), 0);
ASSERT_FALSE(info->has_stop_error());
ASSERT_TRUE(info->has_detected_problems());
ASSERT_EQ(info->detected_problems().size(), 1u);
ASSERT_EQ(info->detected_problems()[0], GuestManager::GuestNetworkStateToStringExplanation(
GuestNetworkState::NO_HOST_NETWORKING));
const GuestDescriptor& guest_descriptor = info->guest_descriptor();
ASSERT_EQ(guest_descriptor.guest_memory(), finalized_config.guest_memory());
ASSERT_EQ(guest_descriptor.num_cpus(), finalized_config.cpus());
ASSERT_EQ(guest_descriptor.wayland(), finalized_config.has_wayland_device());
ASSERT_EQ(guest_descriptor.magma(), finalized_config.has_magma_device());
ASSERT_EQ(guest_descriptor.has_networks() && !guest_descriptor.networks().empty(),
finalized_config.has_default_net() && finalized_config.default_net());
ASSERT_EQ(guest_descriptor.balloon(),
finalized_config.has_virtio_balloon() && finalized_config.virtio_balloon());
ASSERT_EQ(guest_descriptor.console(),
finalized_config.has_virtio_console() && finalized_config.virtio_console());
ASSERT_EQ(guest_descriptor.gpu(),
finalized_config.has_virtio_gpu() && finalized_config.virtio_gpu());
ASSERT_EQ(guest_descriptor.rng(),
finalized_config.has_virtio_rng() && finalized_config.virtio_rng());
ASSERT_EQ(guest_descriptor.vsock(),
finalized_config.has_virtio_vsock() && finalized_config.virtio_vsock());
ASSERT_EQ(guest_descriptor.sound(),
finalized_config.has_virtio_sound() && finalized_config.virtio_sound());
}
TEST_F(GuestManagerTest, Connect) {
bool connect_callback_called = false;
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestPtr guest;
manager.Connect(guest.NewRequest(), [&connect_callback_called](auto res) {
ASSERT_TRUE(res.is_err());
ASSERT_EQ(GuestManagerError::NOT_RUNNING, res.err());
connect_callback_called = true;
});
ASSERT_TRUE(connect_callback_called);
guest.Unbind();
bool launch_callback_called = false;
fuchsia::virtualization::GuestConfig user_guest_config;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
guest.Unbind();
connect_callback_called = false;
manager.Connect(guest.NewRequest(), [&connect_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
connect_callback_called = true;
});
ASSERT_TRUE(connect_callback_called);
}
TEST_F(GuestManagerTest, DuplicateListenersProvidedByUserGuestConfig) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
// Two listeners with the same port.
const uint32_t host_port = 12345;
user_guest_config.mutable_vsock_listeners()->push_back(
{host_port, fidl::InterfaceHandle<fuchsia::virtualization::HostVsockAcceptor>()});
user_guest_config.mutable_vsock_listeners()->push_back(
{host_port, fidl::InterfaceHandle<fuchsia::virtualization::HostVsockAcceptor>()});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
fuchsia::virtualization::GuestManager_Launch_Result result;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&result, &launch_callback_called](auto res) {
result = std::move(res);
launch_callback_called = true;
});
ASSERT_TRUE(launch_callback_called);
ASSERT_EQ(result.err(), GuestManagerError::BAD_CONFIG);
}
TEST_F(GuestManagerTest, UserProvidedInitialListeners) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fidl::InterfaceHandle<fuchsia::virtualization::HostVsockAcceptor> acceptor1, acceptor2;
// Give the handles valid channels (although the endpoint will go unused).
auto request1 = acceptor1.NewRequest();
auto request2 = acceptor2.NewRequest();
user_guest_config.mutable_vsock_listeners()->push_back({123, std::move(acceptor1)});
user_guest_config.mutable_vsock_listeners()->push_back({456, std::move(acceptor2)});
bool launch_callback_called = false;
fuchsia::virtualization::GuestPtr guest;
fuchsia::virtualization::GuestManager_Launch_Result result;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_TRUE(res.is_response());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// Initial Listeners are passed to the VMM via the guest config.
fuchsia::virtualization::GuestConfig finalized_config =
fake_guest_lifecycle_->take_guest_config();
ASSERT_TRUE(finalized_config.has_vsock_listeners());
ASSERT_EQ(finalized_config.vsock_listeners().size(), 2ul);
}
TEST_F(GuestManagerTest, GuestProbablyHasNetworking) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// There's a virtual interface, a bridge, and host networking. Everything looks good!
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::ETHERNET);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::VIRTUAL);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::BRIDGE);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::OK);
}
TEST_F(GuestManagerTest, NoNetworkDevices) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
const GuestConfig config = fake_guest_lifecycle_->take_guest_config();
ASSERT_FALSE((config.has_net_devices() && !config.net_devices().empty()));
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::NO_NETWORK_DEVICE);
}
TEST_F(GuestManagerTest, BridgingRequiredHostOnWifi) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// There's no bridge, and the host is on WiFi with no ethernet connection. This will result
// in no internet connection.
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::WLAN);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::VIRTUAL);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::ATTEMPTED_TO_BRIDGE_WITH_WLAN);
}
TEST_F(GuestManagerTest, BridgingRequiredHostOnWifiAndEthernet) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// There's no bridge, but the host has both a WiFi and ethernet connection. The host should
// be able to bridge the virtual connection to the ethernet connection, so the lack of a bridge
// may be transient.
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::WLAN);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::ETHERNET);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::VIRTUAL);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::NO_BRIDGE_CREATED);
}
TEST_F(GuestManagerTest, BridgingRequiredHostOnEthernet) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// There's no bridge, but the host has a working ethernet connection. This may be transient.
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::ETHERNET);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::VIRTUAL);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::NO_BRIDGE_CREATED);
}
TEST_F(GuestManagerTest, NotEnoughVirtualInterfaces) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x01, 0x02, 0x03, 0x04, 0x05, 0x06}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// Config has two guest interfaces, but there's only one present.
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::ETHERNET);
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::VIRTUAL);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::MISSING_VIRTUAL_INTERFACES);
}
TEST_F(GuestManagerTest, NoHostNetworking) {
GuestManager manager(dispatcher(), provider_.context(), "/pkg/", "data/configs/valid_guest.cfg");
fuchsia::virtualization::GuestConfig user_guest_config;
user_guest_config.mutable_net_devices()->push_back({
.mac_address = {{0x02, 0x1a, 0x11, 0x00, 0x01, 0x00}},
.enable_bridge = true,
});
fuchsia::virtualization::GuestPtr guest;
bool launch_callback_called = false;
manager.Launch(std::move(user_guest_config), guest.NewRequest(),
[&launch_callback_called](auto res) {
ASSERT_FALSE(res.is_err());
launch_callback_called = true;
});
RunLoopUntilIdle();
ASSERT_TRUE(launch_callback_called);
// Only active interfaces are used.
fake_net_interfaces_->AddExistingInterface(::fuchsia::hardware::network::DeviceClass::ETHERNET,
/*online=*/false);
std::optional<GuestNetworkState> result;
auto handle = std::async(std::launch::async, [&manager, &result] {
// Blocking, so run on a non-dispatch loop thread.
result = manager.QueryGuestNetworkState();
});
RunLoopUntil([&result]() { return result.has_value(); });
ASSERT_EQ(result.value(), GuestNetworkState::NO_HOST_NETWORKING);
}
} // namespace