blob: aff1c389a4e4ad21c56df36b56d4cd03b571796c [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "patchpanel/vm_concierge_client.h"
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <utility>
#include <base/functional/bind.h>
#include <base/logging.h>
#include <base/memory/ref_counted.h>
#include <base/run_loop.h>
#include <base/task/single_thread_task_runner.h>
#include <base/types/expected.h>
#include <base/task/sequenced_task_runner.h>
#include <base/test/task_environment.h>
#include <dbus/bus.h>
#include <dbus/message.h>
#include <dbus/mock_bus.h>
#include <dbus/mock_object_proxy.h>
#include <dbus/object_path.h>
#include <dbus/object_proxy.h>
#include <dbus/vm_concierge/dbus-constants.h>
#include <gtest/gtest.h>
#include <vm_concierge/concierge_service.pb.h>
using ::testing::_;
using ::testing::Invoke;
using ::testing::Return;
namespace patchpanel {
namespace {
constexpr uint64_t kMockVmCid = 0xdeadbeef;
constexpr char kMockVmName[] = "MockVm";
constexpr char kMockVmOwner[] = "MockUser";
class CallbackMonitor {
public:
CallbackMonitor() = default;
MOCK_METHOD(void, OnAttachCallback, (std::optional<uint32_t> bus_num), ());
MOCK_METHOD(void, OnDetachCallback, (bool), ());
VmConciergeClient::AttachTapCallback CreateAttachTestCallback();
VmConciergeClient::DetachTapCallback CreateDetachTestCallback();
};
VmConciergeClient::AttachTapCallback
CallbackMonitor::CreateAttachTestCallback() {
return base::BindOnce(&CallbackMonitor::OnAttachCallback,
base::Unretained(this));
}
VmConciergeClient::DetachTapCallback
CallbackMonitor::CreateDetachTestCallback() {
return base::BindOnce(&CallbackMonitor::OnDetachCallback,
base::Unretained(this));
}
class VmConciergeClientTest : public ::testing::Test {
public:
static constexpr uint8_t kSlotNum = 1;
static constexpr char kMockFailReason[] = "mock fail reason";
VmConciergeClientTest() = default;
void SetUp() override {
dbus::Bus::Options options;
options.bus_type = dbus::Bus::SYSTEM;
mock_bus_ = new dbus::MockBus(options);
concierge_proxy_ = new dbus::MockObjectProxy(
mock_bus_.get(), vm_tools::concierge::kVmConciergeServiceName,
dbus::ObjectPath(vm_tools::concierge::kVmConciergeServicePath));
// Sets an expectation that VmConciergeClient will get ObjectProxy.
EXPECT_CALL(
*mock_bus_.get(),
GetObjectProxy(
vm_tools::concierge::kVmConciergeServiceName,
dbus::ObjectPath(vm_tools::concierge::kVmConciergeServicePath)))
.WillOnce(Return(concierge_proxy_.get()));
// Sets an expectation that mock proxy will register to listen to
// kVmStartedSignal and kVmStoppingSignal from concierge_proxy_.
EXPECT_CALL(*concierge_proxy_.get(),
DoConnectToSignal(vm_tools::concierge::kVmConciergeInterface,
vm_tools::concierge::kVmStartedSignal, _, _))
.WillRepeatedly(Invoke(this, &VmConciergeClientTest::ConnectToSignal));
EXPECT_CALL(*concierge_proxy_.get(),
DoConnectToSignal(vm_tools::concierge::kVmConciergeInterface,
vm_tools::concierge::kVmStoppingSignal, _, _))
.WillRepeatedly(Invoke(this, &VmConciergeClientTest::ConnectToSignal));
EXPECT_CALL(*mock_bus_, GetDBusTaskRunner())
.WillRepeatedly(
Return(base::SingleThreadTaskRunner::GetCurrentDefault().get()));
client_ = std::make_unique<VmConciergeClient>(mock_bus_);
}
void ResolveAttachAsSuccess(testing::Unused,
testing::Unused,
dbus::ObjectProxy::ResponseCallback* callback) {
vm_tools::concierge::AttachNetDeviceResponse attach_response;
attach_response.set_success(true);
attach_response.set_guest_bus(kSlotNum);
std::unique_ptr<dbus::Response> dbus_response =
dbus::Response::CreateEmpty();
dbus::MessageWriter writer(dbus_response.get());
writer.AppendProtoAsArrayOfBytes(attach_response);
std::move(*callback).Run(dbus_response.get());
}
void ResolveDetachAsFail(testing::Unused,
testing::Unused,
dbus::ObjectProxy::ResponseCallback* callback) {
vm_tools::concierge::DetachNetDeviceResponse detach_response;
detach_response.set_success(false);
detach_response.set_failure_reason(kMockFailReason);
std::unique_ptr<dbus::Response> dbus_response =
dbus::Response::CreateEmpty();
dbus::MessageWriter writer(dbus_response.get());
writer.AppendProtoAsArrayOfBytes(detach_response);
std::move(*callback).Run(dbus_response.get());
}
protected:
void SendVmStartedSignal() {
const auto it =
signal_callbacks_.find(vm_tools::concierge::kVmStartedSignal);
ASSERT_TRUE(it != signal_callbacks_.end())
<< "Client didn't register for kVmStartedSignal";
dbus::Signal dbus_signal(vm_tools::concierge::kVmConciergeInterface,
vm_tools::concierge::kVmStartedSignal);
dbus::MessageWriter writer(&dbus_signal);
vm_tools::concierge::VmStartedSignal vm_started_signal;
vm_tools::concierge::VmInfo* vm_info = new vm_tools::concierge::VmInfo();
vm_info->set_cid(kMockVmCid);
vm_started_signal.set_allocated_vm_info(vm_info);
vm_started_signal.set_name(kMockVmName);
vm_started_signal.set_owner_id(kMockVmOwner);
writer.AppendProtoAsArrayOfBytes(vm_started_signal);
it->second.Run(&dbus_signal);
}
void SendVmStoppingSignal() {
const auto it =
signal_callbacks_.find(vm_tools::concierge::kVmStoppingSignal);
ASSERT_TRUE(it != signal_callbacks_.end())
<< "Client didn't register for kVmStoppingSignal";
dbus::Signal dbus_signal(vm_tools::concierge::kVmConciergeInterface,
vm_tools::concierge::kVmStoppingSignal);
dbus::MessageWriter writer(&dbus_signal);
vm_tools::concierge::VmStoppingSignal vm_stopping_signal;
vm_stopping_signal.set_cid(kMockVmCid);
vm_stopping_signal.set_name(kMockVmName);
vm_stopping_signal.set_owner_id(kMockVmOwner);
writer.AppendProtoAsArrayOfBytes(vm_stopping_signal);
it->second.Run(&dbus_signal);
}
void ConnectToSignal(
const std::string& interface_name,
const std::string& signal_name,
dbus::ObjectProxy::SignalCallback signal_callback,
dbus::ObjectProxy::OnConnectedCallback* on_connected_callback);
base::test::TaskEnvironment task_environment_;
scoped_refptr<dbus::MockBus> mock_bus_;
scoped_refptr<dbus::MockObjectProxy> concierge_proxy_;
std::unique_ptr<VmConciergeClient> client_;
CallbackMonitor monitor_;
private:
std::map<std::string, dbus::ObjectProxy::SignalCallback> signal_callbacks_;
};
void VmConciergeClientTest::ConnectToSignal(
const std::string& interface_name,
const std::string& signal_name,
dbus::ObjectProxy::SignalCallback signal_callback,
dbus::ObjectProxy::OnConnectedCallback* on_connected_callback) {
EXPECT_EQ(interface_name, vm_tools::concierge::kVmConciergeInterface);
signal_callbacks_[signal_name] = std::move(signal_callback);
task_environment_.GetMainThreadTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(std::move(*on_connected_callback), interface_name,
signal_name, true /* success */));
}
} // namespace
// Tests that attempt to use client with unknown VM cid is rejected.
TEST_F(VmConciergeClientTest, RejectUnregisteredVm) {
SendVmStartedSignal();
base::RunLoop().RunUntilIdle();
// AttachTapDevice fails since kMockVmCid is not registered with client.
EXPECT_FALSE(client_->AttachTapDevice(kMockVmCid, "test-tap",
monitor_.CreateAttachTestCallback()));
}
// Basic test that VmConciergeClient can send request and process response after
// VmStartedSignal is emitted.
TEST_F(VmConciergeClientTest, DetachAfterVmStartedSignal) {
client_->RegisterVm(kMockVmCid);
EXPECT_CALL(*concierge_proxy_.get(), DoCallMethod)
.WillOnce(Invoke(this, &VmConciergeClientTest::ResolveDetachAsFail));
SendVmStartedSignal();
EXPECT_CALL(monitor_, OnDetachCallback(false)).Times(1);
EXPECT_TRUE(client_->DetachTapDevice(kMockVmCid, kSlotNum,
monitor_.CreateDetachTestCallback()));
base::RunLoop().RunUntilIdle();
}
// Tests that VmConciergeClient defers attach requests until it receives
// VmStartedSignal.
TEST_F(VmConciergeClientTest, AttachBeforeVmStartedSignal) {
client_->RegisterVm(kMockVmCid);
EXPECT_CALL(*concierge_proxy_.get(), DoCallMethod)
.WillOnce(Invoke(this, &VmConciergeClientTest::ResolveAttachAsSuccess));
// expect attach requests are accepted since the VM is registered.
EXPECT_TRUE(client_->AttachTapDevice(kMockVmCid, "test-tap",
monitor_.CreateAttachTestCallback()));
// expect callback not invoked since VmStartedSignal is not emitted.
EXPECT_CALL(monitor_, OnAttachCallback).Times(0);
base::RunLoop().RunUntilIdle();
// expect callback after signal emitted.
EXPECT_CALL(monitor_, OnAttachCallback({kSlotNum})).Times(1);
SendVmStartedSignal();
base::RunLoop().RunUntilIdle();
}
// Tests that VmConciergeClient rejects requests after a VM is shutting down.
TEST_F(VmConciergeClientTest, RejectAfterVmStoppingSignal) {
client_->RegisterVm(kMockVmCid);
SendVmStartedSignal();
SendVmStoppingSignal();
base::RunLoop().RunUntilIdle();
// AttachTapDevice fails early because Stopping signal is received.
EXPECT_FALSE(client_->AttachTapDevice(kMockVmCid, "test-tap",
monitor_.CreateAttachTestCallback()));
}
} // namespace patchpanel