blob: fbc7daa64687d012cd00f8db912e05584e2f145d [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/vmm/vmm_controller.h"
#include <lib/sys/cpp/testing/component_context_provider.h>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <src/lib/testing/loop_fixture/real_loop_fixture.h>
namespace vmm {
namespace {
using ::fuchsia::virtualization::GuestConfig;
using ::fuchsia::virtualization::GuestError;
using ::fuchsia::virtualization::GuestLifecycle_Create_Result;
using ::fuchsia::virtualization::GuestLifecycle_Run_Result;
using ::fuchsia::virtualization::GuestLifecycleSyncPtr;
using ::testing::_;
using ::testing::Return;
using ::testing::StrictMock;
class MockVmm : public Vmm {
public:
MOCK_METHOD(fit::result<GuestError>, Initialize,
(GuestConfig, ::sys::ComponentContext*, async_dispatcher_t*), (override));
MOCK_METHOD(fit::result<GuestError>, StartPrimaryVcpu,
(fit::function<void(fit::result<GuestError>)>), (override));
MOCK_METHOD(void, NotifyClientsShutdown, (zx_status_t), (override));
};
class VmmControllerTest : public gtest::RealLoopFixture {
public:
void SetUp() override {
RealLoopFixture::SetUp();
controller_loop_ = std::make_shared<async::Loop>(&kAsyncLoopConfigNoAttachToCurrentThread);
controller_ =
std::make_unique<VmmController>([this]() { controller_loop_->Shutdown(); },
provider_.TakeContext(), controller_loop_->dispatcher());
RunLoopUntilIdle();
}
sys::testing::ComponentContextProvider provider_;
std::shared_ptr<async::Loop> controller_loop_;
std::unique_ptr<VmmController> controller_;
};
TEST_F(VmmControllerTest, GuestLifecycleChannelDisconnected) {
GuestLifecycleSyncPtr lifecycle;
provider_.ConnectToPublicService(lifecycle.NewRequest());
ASSERT_TRUE(lifecycle.is_bound());
ASSERT_EQ(controller_loop_->GetState(), ASYNC_LOOP_RUNNABLE);
lifecycle.Unbind();
RunLoopUntilIdle();
controller_loop_->RunUntilIdle();
// The lifecycle channel being closed (such as if the guest manager goes away) should also
// result in the VMM controller's dispatch loop entering a shutdown state.
ASSERT_FALSE(lifecycle.is_bound());
ASSERT_EQ(controller_loop_->GetState(), ASYNC_LOOP_SHUTDOWN);
}
TEST_F(VmmControllerTest, RecreatingNonRunningGuestDestroysVmm) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
controller_->ProvideVmmForTesting(std::move(vmm));
GuestConfig cfg; // Invalid config.
bool create_callback_called = false;
fuchsia::virtualization::GuestPtr guest;
controller_->Create(std::move(cfg),
[&create_callback_called](GuestLifecycle_Create_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::BAD_CONFIG);
create_callback_called = true;
});
controller_->Bind(guest.NewRequest());
ASSERT_TRUE(create_callback_called);
// The first VMM was destroyed upon creating the second one, and the second one encountered an
// error during initialization, so there's no VMM to start.
bool run_callback_called = false;
controller_->Run([&run_callback_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::NOT_CREATED);
run_callback_called = true;
});
ASSERT_TRUE(run_callback_called);
}
TEST_F(VmmControllerTest, RecreatingRunningGuestRequiresStop) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
EXPECT_CALL(*vmm, StartPrimaryVcpu(_)).WillOnce(Return(fit::ok()));
controller_->ProvideVmmForTesting(std::move(vmm));
bool run_callback_called = false;
controller_->Run(
[&run_callback_called](GuestLifecycle_Run_Result result) { run_callback_called = true; });
// Callback is saved as the guest is running.
ASSERT_FALSE(run_callback_called);
GuestConfig cfg;
bool create_callback_called = false;
fuchsia::virtualization::GuestPtr guest;
controller_->Create(std::move(cfg),
[&create_callback_called](GuestLifecycle_Create_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::ALREADY_RUNNING);
create_callback_called = true;
});
controller_->Bind(guest.NewRequest());
ASSERT_TRUE(create_callback_called);
// Guest is still running (the controller must call Stop before creating a new VMM).
ASSERT_FALSE(run_callback_called);
}
TEST_F(VmmControllerTest, GuestLifecycleCreateNotCalled) {
bool run_callback_called = false;
controller_->Run([&run_callback_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::NOT_CREATED);
run_callback_called = true;
});
ASSERT_TRUE(run_callback_called);
}
TEST_F(VmmControllerTest, GuestLifecycleAlreadyRunning) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
EXPECT_CALL(*vmm, StartPrimaryVcpu(_)).WillOnce(Return(fit::ok()));
controller_->ProvideVmmForTesting(std::move(vmm));
bool run_callback1_called = false;
controller_->Run(
[&run_callback1_called](GuestLifecycle_Run_Result result) { run_callback1_called = true; });
// First callback is saved for when the guest exits.
ASSERT_FALSE(run_callback1_called);
bool run_callback2_called = false;
controller_->Run([&run_callback2_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::ALREADY_RUNNING);
run_callback2_called = true;
});
// Second callback is immediately used to report an error.
ASSERT_TRUE(run_callback2_called);
}
TEST_F(VmmControllerTest, RunFailureDoesntSetCallbackAndDestroysVmm) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
EXPECT_CALL(*vmm, StartPrimaryVcpu(_))
.WillOnce(Return(fit::error(GuestError::VCPU_START_FAILURE)));
controller_->ProvideVmmForTesting(std::move(vmm));
bool run_callback1_called = false;
controller_->Run([&run_callback1_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::VCPU_START_FAILURE);
run_callback1_called = true;
});
// Callback is used to report a VCPU start error.
ASSERT_TRUE(run_callback1_called);
bool run_callback2_called = false;
controller_->Run([&run_callback2_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::NOT_CREATED);
run_callback2_called = true;
});
// The VM was destroyed after failing to start, so calling run again returns a not
// created failure.
ASSERT_TRUE(run_callback2_called);
}
TEST_F(VmmControllerTest, StopWithoutGuestCreation) {
bool stop_callback_called = false;
controller_->Stop([&stop_callback_called]() { stop_callback_called = true; });
// It's fine to call Stop without a created VMM (such as if there was an error starting).
ASSERT_TRUE(stop_callback_called);
}
TEST_F(VmmControllerTest, StopWithoutGuestRunning) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
controller_->ProvideVmmForTesting(std::move(vmm));
bool stop_callback_called = false;
controller_->Stop([&stop_callback_called]() { stop_callback_called = true; });
// It's fine to call Stop without starting a VMM to just destroy it.
ASSERT_TRUE(stop_callback_called);
}
TEST_F(VmmControllerTest, ForcedShutdownReturnsError) {
auto vmm = std::make_unique<StrictMock<MockVmm>>();
EXPECT_CALL(*vmm, StartPrimaryVcpu(_)).WillOnce(Return(fit::ok()));
EXPECT_CALL(*vmm, NotifyClientsShutdown(ZX_ERR_INTERNAL));
controller_->ProvideVmmForTesting(std::move(vmm));
bool run_callback_called = false;
GuestLifecycle_Run_Result run_result;
controller_->Run([&run_callback_called, &run_result](GuestLifecycle_Run_Result result) {
run_result = std::move(result);
run_callback_called = true;
});
// The controller saves the callback for when the guest exits.
ASSERT_FALSE(run_callback_called);
bool stop_callback_called = false;
controller_->Stop([&stop_callback_called]() { stop_callback_called = true; });
// Stop posts a task to the dispatch loop.
controller_loop_->RunUntilIdle();
ASSERT_TRUE(stop_callback_called);
ASSERT_TRUE(run_callback_called);
ASSERT_TRUE(run_result.is_err());
ASSERT_EQ(run_result.err(), GuestError::CONTROLLER_FORCED_HALT);
}
TEST_F(VmmControllerTest, CleanGuestInitiatedShutdownReturnsSuccess) {
fit::function<void(fit::result<GuestError>)> captured_callback;
auto vmm = std::make_unique<StrictMock<MockVmm>>();
EXPECT_CALL(*vmm, StartPrimaryVcpu(_))
.WillOnce([&captured_callback](fit::function<void(fit::result<GuestError>)> stop_callback) {
captured_callback = std::move(stop_callback);
return fit::ok();
});
EXPECT_CALL(*vmm, NotifyClientsShutdown(ZX_OK));
controller_->ProvideVmmForTesting(std::move(vmm));
bool run_callback1_called = false;
GuestLifecycle_Run_Result run_result;
controller_->Run([&run_callback1_called, &run_result](GuestLifecycle_Run_Result result) {
run_result = std::move(result);
run_callback1_called = true;
});
// The controller saves the callback for when the guest exits.
ASSERT_FALSE(run_callback1_called);
// This posts a task to the dispatch loop, so the guest is still running.
captured_callback(fit::ok());
ASSERT_FALSE(run_callback1_called);
controller_loop_->RunUntilIdle();
// The guest has stopped and the VCPU has reported a value.
ASSERT_TRUE(run_callback1_called);
ASSERT_TRUE(run_result.is_response());
bool run_callback2_called = false;
controller_->Run([&run_callback2_called](GuestLifecycle_Run_Result result) {
ASSERT_TRUE(result.is_err());
ASSERT_EQ(result.err(), GuestError::NOT_CREATED);
run_callback2_called = true;
});
// Attempt to run again confirms that the VMM was destroyed.
ASSERT_TRUE(run_callback2_called);
}
} // namespace
} // namespace vmm