blob: a71ccf717f911e79b4273f6e56e9c87cfb2418c8 [file] [log] [blame]
// Copyright 2018 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.
#ifndef SRC_VIRTUALIZATION_TESTS_LIB_ENCLOSED_GUEST_H_
#define SRC_VIRTUALIZATION_TESTS_LIB_ENCLOSED_GUEST_H_
#include <fuchsia/virtualization/cpp/fidl.h>
#include <lib/async-loop/cpp/loop.h>
#include <lib/async/cpp/executor.h>
#include <lib/fit/result.h>
#include <lib/sys/component/cpp/testing/realm_builder.h>
#include <memory>
#include <gtest/gtest.h>
#include "lib/async/dispatcher.h"
#include "src/ui/testing/ui_test_manager/ui_test_manager.h"
#include "src/virtualization/lib/vsh/command_runner.h"
#include "src/virtualization/tests/lib/fake_memory_pressure_provider.h"
#include "src/virtualization/tests/lib/fake_netstack.h"
#include "src/virtualization/tests/lib/guest_console.h"
#include "src/virtualization/tests/lib/socket_logger.h"
enum class GuestKernel {
ZIRCON,
LINUX,
};
struct GuestLaunchInfo {
std::string url;
std::string interface_name;
fuchsia::virtualization::GuestConfig config;
};
// EnclosedGuest is a base class that defines an guest environment and instance
// encapsulated in an EnclosingEnvironment. A derived class must define the
// |LaunchInfo| to send to the guest environment controller, as well as methods
// for waiting for the guest to be ready and running test utilities. Most tests
// will derive from either ZirconEnclosedGuest or DebianEnclosedGuest below and
// override LaunchInfo only. EnclosedGuest is designed to be used with
// GuestTest.
class EnclosedGuest {
protected:
using RunLoopUntilFunc = fit::function<bool(fit::function<bool()>, zx::duration)>;
public:
EnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: dispatcher_(dispatcher),
run_loop_until_(std::move(run_loop_until)),
fake_memory_pressure_provider_(dispatcher) {}
virtual ~EnclosedGuest();
// Start the guest. `Start` is the preferred way to start the guest. If the realm
// needs to be customized, `Start` can be replaced by a call to `InstallInRealm`
// followed by a call to `LaunchInRealm`. This should follow a pattern like:
//
// ```
// GuestLaunchInfo guest_launch_info;
// realm_builder = RealmBuilder::Create();
// InstallInRealm(realm_builder, guest_launch_info);
// ...
// ... // customize realm_builder
// ...
// RealmRoot realm_root(realm_builder.Build(dispatcher));
// LaunchInRealm(realm_root, guest_launch_info, deadline);
// ```
//
// Abort with ZX_ERR_TIMED_OUT if we reach `deadline` before the guest has started.
zx_status_t Start(zx::time deadline);
virtual void InstallInRealm(component_testing::Realm& realm, GuestLaunchInfo& guest_launch_info);
zx_status_t LaunchInRealm(std::unique_ptr<sys::ServiceDirectory> services,
GuestLaunchInfo& guest_launch_info, zx::time deadline);
// Provides guest specific launch info, called by Start.
virtual zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) = 0;
// Attempt to gracefully stop the guest.
//
// Abort with ZX_ERR_TIMED_OUT if we reach `deadline` first.
zx_status_t Stop(zx::time deadline);
// Force stop the guest, and restart it. This can be used to ensure that devices are cleanly
// brought down and up without needing to destroy the VMM component.
//
// Aborts with ZX_ERR_TIMED_OUT if we reach `deadline` first.
zx_status_t ForceRestart(GuestLaunchInfo& guest_launch_info, zx::time deadline);
// Execute |command| on the guest serial and wait for the |result|.
virtual zx_status_t Execute(const std::vector<std::string>& argv,
const std::unordered_map<std::string, std::string>& env,
zx::time deadline, std::string* result = nullptr,
int32_t* return_code = nullptr);
// Run a test util named |util| with |argv| in the guest and wait for the
// |result|.
zx_status_t RunUtil(const std::string& util, const std::vector<std::string>& argv,
zx::time deadline, std::string* result = nullptr);
bool RunLoopUntil(fit::function<bool()> condition, zx::time deadline);
// Return a shell command for a test utility named |util| with the given
// |argv| in the guest. The result may be passed directly to |Execute|
// to actually run the command.
virtual std::vector<std::string> GetTestUtilCommand(const std::string& util,
const std::vector<std::string>& argv) = 0;
virtual GuestKernel GetGuestKernel() = 0;
fit::result<::fuchsia::virtualization::GuestError> ConnectToBalloon(
::fidl::InterfaceRequest<::fuchsia::virtualization::BalloonController> controller);
fit::result<::fuchsia::virtualization::GuestError> GetHostVsockEndpoint(
::fidl::InterfaceRequest<::fuchsia::virtualization::HostVsockEndpoint> endpoint);
fit::result<::fuchsia::virtualization::GuestError> ConnectToMem(
::fidl::InterfaceRequest<::fuchsia::virtualization::MemController> controller);
template <typename Interface>
fidl::InterfacePtr<Interface> ConnectToService(
const std::string& interface_name = Interface::Name_) const {
return realm_services_->Connect<Interface>(interface_name);
}
uint32_t GetGuestCid() const { return guest_cid_; }
FakeNetstack* GetNetstack() { return &fake_netstack_; }
FakeMemoryPressureProvider* GetMemoryPressureProvider() {
return &fake_memory_pressure_provider_;
}
std::optional<GuestConsole>& GetConsole() { return console_; }
// Waits for a view to be created and presented using the GraphicalPresenter protocol.
//
// This is appropriate for tests that interact with UI stack in some way and need to interact with
// the test UI stack.
struct DisplayInfo {
uint32_t width;
uint32_t height;
};
DisplayInfo WaitForDisplay();
protected:
// Waits until the guest is ready to run test utilities, called by Start.
virtual zx_status_t WaitForSystemReady(zx::time deadline) = 0;
// Waits for the guest to perform a graceful shutdown.
virtual zx_status_t ShutdownAndWait(zx::time deadline) = 0;
virtual std::string ShellPrompt() = 0;
// Invoked after the guest |Realm| has been created but before the guest
// has been launched.
//
// Any vsock ports that are listened on here are guaranteed to be ready to
// accept connections before the guest attempts to connect to them.
virtual zx_status_t SetupVsockServices(zx::time deadline, GuestLaunchInfo& guest_launch_info) {
return ZX_OK;
}
fuchsia::virtualization::GuestPtr guest_;
fuchsia::virtualization::HostVsockEndpointPtr vsock_;
private:
// Launch the guest into an already prepared realm.
zx_status_t LaunchInternal(GuestLaunchInfo& guest_launch_info, zx::time deadline);
std::unique_ptr<sys::ServiceDirectory> StartWithRealmBuilder(zx::time deadline,
GuestLaunchInfo& guest_launch_info);
std::unique_ptr<sys::ServiceDirectory> StartWithUITestManager(zx::time deadline,
GuestLaunchInfo& guest_launch_info);
async_dispatcher_t* const dispatcher_;
RunLoopUntilFunc run_loop_until_;
FakeNetstack fake_netstack_;
FakeMemoryPressureProvider fake_memory_pressure_provider_;
fuchsia::virtualization::GuestManagerSyncPtr guest_manager_;
std::optional<SocketLogger> serial_logger_;
std::optional<GuestConsole> console_;
uint32_t guest_cid_;
// Only one of |ui_test_manager_| and |realm_root_| will be non-null, depending on if graphics
// APIs are used.
std::optional<ui_testing::UITestManager> ui_test_manager_;
std::optional<component_testing::RealmRoot> realm_root_;
// The exposed services directory for the test realm.
std::unique_ptr<sys::ServiceDirectory> realm_services_;
std::optional<zx_status_t> guest_error_;
};
class ZirconEnclosedGuest : public EnclosedGuest {
public:
ZirconEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: ZirconEnclosedGuest(dispatcher, std::move(run_loop_until), /* enable_gpu */ false) {}
std::vector<std::string> GetTestUtilCommand(const std::string& util,
const std::vector<std::string>& argv) override;
GuestKernel GetGuestKernel() override { return GuestKernel::ZIRCON; }
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override;
protected:
ZirconEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until,
bool enable_gpu)
: EnclosedGuest(dispatcher, std::move(run_loop_until)), enable_gpu_(enable_gpu) {}
zx_status_t WaitForSystemReady(zx::time deadline) override;
zx_status_t ShutdownAndWait(zx::time deadline) override;
std::string ShellPrompt() override { return "$ "; }
private:
const bool enable_gpu_;
};
class ZirconGpuEnclosedGuest : public ZirconEnclosedGuest {
public:
ZirconGpuEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: ZirconEnclosedGuest(dispatcher, std::move(run_loop_until), /* enable_gpu */ true) {}
};
class DebianEnclosedGuest : public EnclosedGuest {
public:
DebianEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: DebianEnclosedGuest(dispatcher, std::move(run_loop_until), /* enable_gpu */ false) {}
std::vector<std::string> GetTestUtilCommand(const std::string& util,
const std::vector<std::string>& argv) override;
GuestKernel GetGuestKernel() override { return GuestKernel::LINUX; }
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override;
protected:
DebianEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until,
bool enable_gpu)
: EnclosedGuest(dispatcher, std::move(run_loop_until)), enable_gpu_(enable_gpu) {}
zx_status_t WaitForSystemReady(zx::time deadline) override;
zx_status_t ShutdownAndWait(zx::time deadline) override;
std::string ShellPrompt() override { return "$ "; }
private:
const bool enable_gpu_;
};
class DebianGpuEnclosedGuest : public DebianEnclosedGuest {
public:
DebianGpuEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: DebianEnclosedGuest(dispatcher, std::move(run_loop_until), /* enable_gpu */ true) {}
};
class TerminaEnclosedGuest : public EnclosedGuest {
public:
TerminaEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: TerminaEnclosedGuest(dispatcher, std::move(run_loop_until),
fuchsia::virtualization::ContainerStatus::STARTING_VM) {}
GuestKernel GetGuestKernel() override { return GuestKernel::LINUX; }
std::vector<std::string> GetTestUtilCommand(const std::string& util,
const std::vector<std::string>& argv) override;
zx_status_t Execute(const std::vector<std::string>& command,
const std::unordered_map<std::string, std::string>& env, zx::time deadline,
std::string* result, int32_t* return_code) override;
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override;
void InstallInRealm(component_testing::Realm& realm, GuestLaunchInfo& guest_launch_info) override;
protected:
TerminaEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until,
fuchsia::virtualization::ContainerStatus target_status)
: EnclosedGuest(dispatcher, std::move(run_loop_until)),
target_status_(target_status),
executor_(dispatcher) {}
zx_status_t WaitForSystemReady(zx::time deadline) override;
zx_status_t ShutdownAndWait(zx::time deadline) override;
std::string ShellPrompt() override { return "$ "; }
private:
const fuchsia::virtualization::ContainerStatus target_status_;
std::unique_ptr<vsh::BlockingCommandRunner> command_runner_;
async::Executor executor_;
::fuchsia::virtualization::LinuxManagerPtr linux_manager_;
};
class TerminaContainerEnclosedGuest : public TerminaEnclosedGuest {
public:
TerminaContainerEnclosedGuest(async_dispatcher_t* dispatcher, RunLoopUntilFunc run_loop_until)
: TerminaEnclosedGuest(dispatcher, std::move(run_loop_until),
fuchsia::virtualization::ContainerStatus::READY) {}
zx_status_t BuildLaunchInfo(GuestLaunchInfo* launch_info) override;
void InstallInRealm(component_testing::Realm& realm, GuestLaunchInfo& guest_launch_info) override;
zx_status_t WaitForSystemReady(zx::time deadline) override;
zx_status_t Execute(const std::vector<std::string>& argv,
const std::unordered_map<std::string, std::string>& env, zx::time deadline,
std::string* result, int32_t* return_code) override;
};
using AllGuestTypes = ::testing::Types<ZirconEnclosedGuest, DebianEnclosedGuest>;
class GuestTestNameGenerator {
public:
template <typename T>
static std::string GetName(int idx) {
// Use is_base_of because some tests will use sub-classes. By default gtest will just use
// idx to string, so we just suffix the actual enclosed guest type.
if (std::is_base_of<ZirconEnclosedGuest, T>())
return std::to_string(idx) + "_ZirconGuest";
if (std::is_base_of<DebianEnclosedGuest, T>())
return std::to_string(idx) + "_DebianGuest";
if (std::is_base_of<TerminaContainerEnclosedGuest, T>())
return std::to_string(idx) + "_TerminaContainerGuest";
if (std::is_base_of<TerminaEnclosedGuest, T>())
return std::to_string(idx) + "_TerminaGuest";
}
};
#endif // SRC_VIRTUALIZATION_TESTS_LIB_ENCLOSED_GUEST_H_