Commit b26d6144 authored by Yusuke Sato's avatar Yusuke Sato Committed by Commit Bot

arcvm: Merge platform2's arcvm_launch.cc back into Chromium

See go/arcvm-lifetime-v2 for more context. The key differences
between the original code in arcvm_launch.cc and this are:

* This CL uses DCHECK and variants rather than CHECK.
* This CL uses VLOG and variants rather than LOG(INFO).
* This CL uses base's string/number conversion functions rather
  than std's.
* This CL async calls to Concierge since sync ones are not usable
  here.
* The crossystem wrapper function is reimplemented.
* The unittest (which was removed in 46d42eef) is restored with some
  additional tests for verifying crosvm and Concierge crash handling
  code.

BUG=b:142144019
TEST=ran the unit tests + arc.Boot

Change-Id: Iba3d37aaf1527ffc76165cd93c0e121d1e226308
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1848772Reviewed-by: default avatarHidehiko Abe <hidehiko@chromium.org>
Commit-Queue: Yusuke Sato <yusukes@chromium.org>
Cr-Commit-Position: refs/heads/master@{#704789}
parent 0d36bc20
...@@ -361,6 +361,7 @@ source_set("unit_tests") { ...@@ -361,6 +361,7 @@ source_set("unit_tests") {
"session/arc_data_remover_unittest.cc", "session/arc_data_remover_unittest.cc",
"session/arc_session_impl_unittest.cc", "session/arc_session_impl_unittest.cc",
"session/arc_session_runner_unittest.cc", "session/arc_session_runner_unittest.cc",
"session/arc_vm_client_adapter_unittest.cc",
"timer/arc_timer_bridge_unittest.cc", "timer/arc_timer_bridge_unittest.cc",
"wake_lock/arc_wake_lock_bridge_unittest.cc", "wake_lock/arc_wake_lock_bridge_unittest.cc",
] ]
......
...@@ -4,40 +4,248 @@ ...@@ -4,40 +4,248 @@
#include "components/arc/session/arc_vm_client_adapter.h" #include "components/arc/session/arc_vm_client_adapter.h"
#include <sys/statvfs.h>
#include <time.h>
#include <set>
#include <string> #include <string>
#include <utility> #include <utility>
#include <vector> #include <vector>
#include "base/bind.h" #include "base/bind.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/logging.h" #include "base/logging.h"
#include "base/macros.h" #include "base/macros.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/optional.h" #include "base/optional.h"
#include "base/process/launch.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/system/sys_info.h" #include "base/system/sys_info.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/threading/sequenced_task_runner_handle.h" #include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "chromeos/dbus/concierge_client.h" #include "chromeos/dbus/concierge_client.h"
#include "chromeos/dbus/dbus_thread_manager.h" #include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon/debug_daemon_client.h"
#include "chromeos/dbus/login_manager/arc.pb.h" #include "chromeos/dbus/login_manager/arc.pb.h"
#include "chromeos/dbus/upstart/upstart_client.h" #include "chromeos/dbus/upstart/upstart_client.h"
#include "chromeos/system/statistics_provider.h"
#include "components/arc/arc_features.h"
#include "components/arc/arc_util.h" #include "components/arc/arc_util.h"
namespace arc { namespace arc {
namespace { namespace {
constexpr const char kArcVmServerProxyJobName[] = "arcvm_2dserver_2dproxy"; constexpr const char kArcVmServerProxyJobName[] = "arcvm_2dserver_2dproxy";
constexpr const char kBuiltinPath[] = "/opt/google/vms/android";
constexpr const char kCrosSystemPath[] = "/usr/bin/crossystem";
constexpr const char kDlcPath[] = "/run/imageloader/arcvm-dlc/package/root";
constexpr const char kFstab[] = "fstab";
constexpr const char kHomeDirectory[] = "/home";
constexpr const char kKernel[] = "vmlinux";
constexpr const char kRootFs[] = "system.raw.img";
constexpr const char kVendorImage[] = "vendor.raw.img";
chromeos::ConciergeClient* GetConciergeClient() { chromeos::ConciergeClient* GetConciergeClient() {
return chromeos::DBusThreadManager::Get()->GetConciergeClient(); return chromeos::DBusThreadManager::Get()->GetConciergeClient();
} }
bool IsHostRootfsWritable() {
struct statvfs buf;
if (statvfs("/", &buf) < 0) {
PLOG(ERROR) << "statvfs() failed";
return false;
}
const bool rw = !(buf.f_flag & ST_RDONLY);
VLOG(1) << "Host's rootfs is " << (rw ? "rw" : "ro");
return rw;
}
base::FilePath SelectDlcOrBuiltin(const base::FilePath& file) {
const base::FilePath dlc_path = base::FilePath(kDlcPath).Append(file);
if (base::PathExists(dlc_path)) {
VLOG(1) << "arcvm-dlc will be used for " << file.value();
return dlc_path;
}
return base::FilePath(kBuiltinPath).Append(file);
}
// TODO(pliard): Export host-side /data to the VM, and remove the function.
vm_tools::concierge::CreateDiskImageRequest CreateArcDiskRequest(
const std::string& user_id_hash,
int64_t free_disk_bytes) {
DCHECK(!user_id_hash.empty());
vm_tools::concierge::CreateDiskImageRequest request;
request.set_cryptohome_id(user_id_hash);
request.set_disk_path("arcvm");
// The type of disk image to be created.
request.set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
request.set_storage_location(vm_tools::concierge::STORAGE_CRYPTOHOME_ROOT);
// The logical size of the new disk image, in bytes.
request.set_disk_size(free_disk_bytes / 2);
return request;
}
std::string GetReleaseChannel() {
constexpr const char kUnknown[] = "unknown";
const std::set<std::string> channels = {"beta", "canary", "dev",
"dogfood", "stable", "testimage"};
std::string value;
if (!base::SysInfo::GetLsbReleaseValue("CHROMEOS_RELEASE_TRACK", &value)) {
LOG(ERROR) << "Could not load lsb-release";
return kUnknown;
}
auto pieces = base::SplitString(value, "-", base::KEEP_WHITESPACE,
base::SPLIT_WANT_ALL);
if (pieces.size() != 2 || pieces[1] != "channel") {
LOG(ERROR) << "Misformatted CHROMEOS_RELEASE_TRACK value in lsb-release";
return kUnknown;
}
if (channels.find(pieces[0]) == channels.end()) {
LOG(WARNING) << "Unknown ChromeOS channel: \"" << pieces[0] << "\"";
return kUnknown;
}
return pieces[0];
}
std::string MonotonicTimestamp() {
struct timespec ts;
const int ret = clock_gettime(CLOCK_BOOTTIME, &ts);
DPCHECK(ret == 0);
const int64_t time =
ts.tv_sec * base::Time::kNanosecondsPerSecond + ts.tv_nsec;
return base::NumberToString(time);
}
std::vector<std::string> GenerateKernelCmdline(
int32_t lcd_density,
const base::Optional<bool>& play_store_auto_update,
bool is_dev_mode,
bool is_host_on_vm) {
const std::string release_channel = GetReleaseChannel();
const bool stable_or_beta =
release_channel == "stable" || release_channel == "beta";
std::vector<std::string> result = {
"androidboot.hardware=bertha",
"androidboot.container=1",
// TODO(b/139480143): when ArcNativeBridgeExperiment is enabled, switch
// to ndk_translation.
"androidboot.native_bridge=libhoudini.so",
base::StringPrintf("androidboot.dev_mode=%d", is_dev_mode),
base::StringPrintf("androidboot.disable_runas=%d", !is_dev_mode),
base::StringPrintf("androidboot.vm=%d", is_host_on_vm),
// TODO(yusukes): get this from arc-setup config or equivalent.
"androidboot.debuggable=1",
base::StringPrintf("androidboot.lcd_density=%d", lcd_density),
base::StringPrintf(
"androidboot.arc_file_picker=%d",
base::FeatureList::IsEnabled(kFilePickerExperimentFeature)),
base::StringPrintf(
"androidboot.arc_custom_tabs=%d",
base::FeatureList::IsEnabled(kCustomTabsExperimentFeature) &&
!stable_or_beta),
base::StringPrintf(
"androidboot.arc_print_spooler=%d",
base::FeatureList::IsEnabled(kPrintSpoolerExperimentFeature) &&
!stable_or_beta),
"androidboot.chromeos_channel=" + release_channel,
"androidboot.boottime_offset=" + MonotonicTimestamp(),
// TODO(yusukes): remove this once arcvm supports SELinux.
"androidboot.selinux=permissive",
};
if (play_store_auto_update) {
result.push_back(base::StringPrintf("androidboot.play_store_auto_update=%d",
*play_store_auto_update));
}
// TODO(yusukes): include command line parameters from arcbootcontinue.
return result;
}
vm_tools::concierge::StartArcVmRequest CreateStartArcVmRequest(
const std::string& user_id_hash,
uint32_t cpus,
const std::string& disk_path,
std::vector<std::string> kernel_cmdline) {
vm_tools::concierge::StartArcVmRequest request;
request.set_name(kArcVmName);
request.set_owner_id(user_id_hash);
request.add_params("root=/dev/vda");
if (IsHostRootfsWritable())
request.add_params("rw");
request.add_params("init=/init");
for (auto& entry : kernel_cmdline)
request.add_params(std::move(entry));
vm_tools::concierge::VirtualMachineSpec* vm = request.mutable_vm();
vm->set_kernel(SelectDlcOrBuiltin(base::FilePath(kKernel)).value());
// Add / as /dev/vda.
vm->set_rootfs(SelectDlcOrBuiltin(base::FilePath(kRootFs)).value());
// Add /data as /dev/vdb.
vm_tools::concierge::DiskImage* disk_image = request.add_disks();
disk_image->set_path(disk_path);
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_writable(true);
disk_image->set_do_mount(true);
// Add /vendor as /dev/vdc.
disk_image = request.add_disks();
disk_image->set_path(
SelectDlcOrBuiltin(base::FilePath(kVendorImage)).value());
disk_image->set_image_type(vm_tools::concierge::DISK_IMAGE_AUTO);
disk_image->set_writable(false);
disk_image->set_do_mount(true);
// Add Android fstab.
request.set_fstab(SelectDlcOrBuiltin(base::FilePath(kFstab)).value());
// Add cpus.
request.set_cpus(cpus);
return request;
}
int GetSystemPropertyInt(const std::string& property) {
std::string output;
if (!base::GetAppOutput({kCrosSystemPath, property}, &output))
return -1;
int output_int;
return base::StringToInt(output, &output_int) ? output_int : -1;
}
} // namespace } // namespace
class ArcVmClientAdapter : public ArcClientAdapter, class ArcVmClientAdapter : public ArcClientAdapter,
public chromeos::ConciergeClient::VmObserver, public chromeos::ConciergeClient::VmObserver,
public chromeos::ConciergeClient::Observer { public chromeos::ConciergeClient::Observer {
public: public:
ArcVmClientAdapter() { // Initializing |is_dev_mode_| and |is_host_on_vm_| is not always very fast.
// Try to initialize them in the constructor which usually runs when the
// system is not busy.
ArcVmClientAdapter()
: is_dev_mode_(GetSystemPropertyInt("cros_debug") == 1),
is_host_on_vm_(chromeos::system::StatisticsProvider::GetInstance()
->IsRunningOnVm()) {
auto* client = GetConciergeClient(); auto* client = GetConciergeClient();
client->AddVmObserver(this); client->AddVmObserver(this);
client->AddObserver(this); client->AddObserver(this);
...@@ -86,6 +294,7 @@ class ArcVmClientAdapter : public ArcClientAdapter, ...@@ -86,6 +294,7 @@ class ArcVmClientAdapter : public ArcClientAdapter,
} }
base::SequencedTaskRunnerHandle::Get()->PostTask( base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), true)); FROM_HERE, base::BindOnce(std::move(callback), true));
should_notify_observers_ = true;
} }
void UpgradeArc(const UpgradeArcContainerRequest& request, void UpgradeArc(const UpgradeArcContainerRequest& request,
...@@ -96,27 +305,20 @@ class ArcVmClientAdapter : public ArcClientAdapter, ...@@ -96,27 +305,20 @@ class ArcVmClientAdapter : public ArcClientAdapter,
base::BindOnce(&ArcVmClientAdapter::OnArcVmServerProxyJobStarted, base::BindOnce(&ArcVmClientAdapter::OnArcVmServerProxyJobStarted,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
VLOG(1) << "Starting ARCVM"; // TODO(yusukes): Do this in OnArcVmServerProxyJobStarted().
std::vector<std::string> env{ VLOG(1) << "Starting Concierge service";
{"USER_ID_HASH=" + user_id_hash_}, chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->StartConcierge(
{base::StringPrintf("ARC_LCD_DENSITY=%d", lcd_density_)}, base::BindOnce(&ArcVmClientAdapter::OnConciergeStarted,
{base::StringPrintf("CPUS=%u", cpus_)}, weak_factory_.GetWeakPtr(), std::move(callback)));
};
if (play_store_auto_update_) {
env.push_back(base::StringPrintf("PLAY_STORE_AUTO_UPDATE=%d",
*play_store_auto_update_));
}
chromeos::UpstartClient::Get()->StartJob("arcvm", env, std::move(callback));
} }
void StopArcInstance() override { void StopArcInstance() override {
VLOG(1) << "Stopping arcvm"; VLOG(1) << "Stopping arcvm";
// TODO(yusukes): This method should eventually call ArcInstanceStopped() vm_tools::concierge::StopVmRequest request;
// even when only the (yet nonexistent) 'mini' VM is running. request.set_name(kArcVmName);
std::vector<std::string> env{{"USER_ID_HASH=" + user_id_hash_}}; request.set_owner_id(user_id_hash_);
chromeos::UpstartClient::Get()->StopJob( GetConciergeClient()->StopVm(
"arcvm", env, request, base::BindOnce(&ArcVmClientAdapter::OnStopVmReply,
base::BindOnce(&ArcVmClientAdapter::OnArcVmJobStopped,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
} }
...@@ -136,6 +338,92 @@ class ArcVmClientAdapter : public ArcClientAdapter, ...@@ -136,6 +338,92 @@ class ArcVmClientAdapter : public ArcClientAdapter,
void ConciergeServiceRestarted() override {} void ConciergeServiceRestarted() override {}
private: private:
void OnConciergeStarted(chromeos::VoidDBusMethodCallback callback,
bool success) {
if (!success) {
LOG(ERROR) << "Failed to start Concierge service for arcvm";
std::move(callback).Run(false);
return;
}
VLOG(1) << "Concierge service started for arcvm.";
base::PostTaskAndReplyWithResult(
FROM_HERE, {base::ThreadPool(), base::MayBlock()},
base::BindOnce(&base::SysInfo::AmountOfFreeDiskSpace,
base::FilePath(kHomeDirectory)),
base::BindOnce(&ArcVmClientAdapter::CreateDiskImageAfterSizeCheck,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void CreateDiskImageAfterSizeCheck(chromeos::VoidDBusMethodCallback callback,
int64_t free_disk_bytes) {
VLOG(2) << "Got free disk size: " << free_disk_bytes;
if (user_id_hash_.empty()) {
LOG(ERROR) << "User ID hash is not set";
std::move(callback).Run(false);
return;
}
// TODO(yusukes): Don't start the VM when |free_disk_bytes| is too small.
// TODO(pliard): Export host-side /data to the VM, and remove the call.
GetConciergeClient()->CreateDiskImage(
CreateArcDiskRequest(user_id_hash_, free_disk_bytes),
base::BindOnce(&ArcVmClientAdapter::OnDiskImageCreated,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
// TODO(pliard): Export host-side /data to the VM, and remove the first half
// of the function.
void OnDiskImageCreated(
chromeos::VoidDBusMethodCallback callback,
base::Optional<vm_tools::concierge::CreateDiskImageResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to create disk image. Empty response.";
std::move(callback).Run(false);
return;
}
const vm_tools::concierge::CreateDiskImageResponse& response =
reply.value();
if (response.status() != vm_tools::concierge::DISK_STATUS_EXISTS &&
response.status() != vm_tools::concierge::DISK_STATUS_CREATED) {
LOG(ERROR) << "Failed to create disk image: "
<< response.failure_reason();
std::move(callback).Run(false);
return;
}
VLOG(1) << "Disk image for arcvm ready. status=" << response.status()
<< ", disk=" << response.disk_path();
std::vector<std::string> kernel_cmdline = GenerateKernelCmdline(
lcd_density_, play_store_auto_update_, is_dev_mode_, is_host_on_vm_);
auto start_request = CreateStartArcVmRequest(
user_id_hash_, cpus_, response.disk_path(), std::move(kernel_cmdline));
GetConciergeClient()->StartArcVm(
start_request,
base::BindOnce(&ArcVmClientAdapter::OnStartArcVmReply,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void OnStartArcVmReply(
chromeos::VoidDBusMethodCallback callback,
base::Optional<vm_tools::concierge::StartVmResponse> reply) {
if (!reply.has_value()) {
LOG(ERROR) << "Failed to start arcvm. Empty response.";
std::move(callback).Run(false);
return;
}
const vm_tools::concierge::StartVmResponse& response = reply.value();
if (response.status() != vm_tools::concierge::VM_STATUS_RUNNING) {
LOG(ERROR) << "Failed to start arcvm: status=" << response.status()
<< ", reason=" << response.failure_reason();
std::move(callback).Run(false);
return;
}
VLOG(1) << "arcvm started.";
std::move(callback).Run(true);
}
void OnArcInstanceStopped() { void OnArcInstanceStopped() {
VLOG(1) << "ARCVM stopped. Stopping arcvm-server-proxy"; VLOG(1) << "ARCVM stopped. Stopping arcvm-server-proxy";
...@@ -146,14 +434,28 @@ class ArcVmClientAdapter : public ArcClientAdapter, ...@@ -146,14 +434,28 @@ class ArcVmClientAdapter : public ArcClientAdapter,
base::BindOnce(&ArcVmClientAdapter::OnArcVmServerProxyJobStopped, base::BindOnce(&ArcVmClientAdapter::OnArcVmServerProxyJobStopped,
weak_factory_.GetWeakPtr())); weak_factory_.GetWeakPtr()));
// If this method is called before even mini VM is started (e.g. very early
// vm_concierge crash), or this method is called twice (e.g. crosvm crash
// followed by vm_concierge crash), do nothing.
if (!should_notify_observers_)
return;
should_notify_observers_ = false;
for (auto& observer : observer_list_) for (auto& observer : observer_list_)
observer.ArcInstanceStopped(); observer.ArcInstanceStopped();
} }
// TODO(yusukes): Remove this method when we remove arcvm.conf. void OnStopVmReply(
void OnArcVmJobStopped(bool result) { base::Optional<vm_tools::concierge::StopVmResponse> reply) {
if (!result) // If the reply indicates the D-Bus call is successfully done, do nothing.
LOG(ERROR) << "Failed to stop arcvm."; // Concierge call OnVmStopped() eventually.
if (reply.has_value() && reply.value().success())
return;
// We likely tried to stop mini VM which doesn't exist today. Notify
// observers.
// TODO(yusukes): Remove the fallback once we implement mini VM.
OnArcInstanceStopped();
} }
void OnArcVmServerProxyJobStarted(bool result) { void OnArcVmServerProxyJobStarted(bool result) {
...@@ -164,14 +466,19 @@ class ArcVmClientAdapter : public ArcClientAdapter, ...@@ -164,14 +466,19 @@ class ArcVmClientAdapter : public ArcClientAdapter,
VLOG(1) << "OnArcVmServerProxyJobStopped result=" << result; VLOG(1) << "OnArcVmServerProxyJobStopped result=" << result;
} }
const bool is_dev_mode_;
// True when the *host* is running on a VM.
const bool is_host_on_vm_;
// A hash of the primary profile user ID. // A hash of the primary profile user ID.
std::string user_id_hash_; std::string user_id_hash_;
int32_t lcd_density_; int32_t lcd_density_;
uint32_t cpus_; uint32_t cpus_;
base::Optional<bool> play_store_auto_update_; base::Optional<bool> play_store_auto_update_;
bool should_notify_observers_ = false;
// For callbacks. // For callbacks.
base::WeakPtrFactory<ArcVmClientAdapter> weak_factory_{this}; base::WeakPtrFactory<ArcVmClientAdapter> weak_factory_{this};
......
// Copyright 2019 The Chromium 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 "components/arc/session/arc_vm_client_adapter.h"
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "chromeos/dbus/dbus_thread_manager.h"
#include "chromeos/dbus/debug_daemon/fake_debug_daemon_client.h"
#include "chromeos/dbus/fake_concierge_client.h"
#include "chromeos/dbus/login_manager/arc.pb.h"
#include "chromeos/dbus/upstart/fake_upstart_client.h"
#include "components/arc/arc_util.h"
#include "content/public/test/browser_task_environment.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace arc {
namespace {
constexpr const char kUserIdHash[] = "this_is_a_valid_user_id_hash";
// A debugd client that can fail to start Concierge.
// TODO(yusukes): Merge the feature to FakeDebugDaemonClient.
class TestDebugDaemonClient : public chromeos::FakeDebugDaemonClient {
public:
TestDebugDaemonClient() = default;
~TestDebugDaemonClient() override = default;
void StartConcierge(ConciergeCallback callback) override {
start_concierge_called_ = true;
std::move(callback).Run(start_concierge_result_);
}
bool start_concierge_called() const { return start_concierge_called_; }
void set_start_concierge_result(bool result) {
start_concierge_result_ = result;
}
private:
bool start_concierge_called_ = false;
bool start_concierge_result_ = true;
DISALLOW_COPY_AND_ASSIGN(TestDebugDaemonClient);
};
// A concierge that remembers the parameter passed to StartArcVm.
// TODO(yusukes): Merge the feature to FakeConciergeClient.
class TestConciergeClient : public chromeos::FakeConciergeClient {
public:
TestConciergeClient() = default;
~TestConciergeClient() override = default;
void StartArcVm(
const vm_tools::concierge::StartArcVmRequest& request,
chromeos::DBusMethodCallback<vm_tools::concierge::StartVmResponse>
callback) override {
start_arc_vm_request_ = request;
chromeos::FakeConciergeClient::StartArcVm(request, std::move(callback));
}
const vm_tools::concierge::StartArcVmRequest& start_arc_vm_request() const {
return start_arc_vm_request_;
}
private:
vm_tools::concierge::StartArcVmRequest start_arc_vm_request_;
DISALLOW_COPY_AND_ASSIGN(TestConciergeClient);
};
class ArcVmClientAdapterTest : public testing::Test,
public ArcClientAdapter::Observer {
public:
ArcVmClientAdapterTest() {
// Create and set new fake clients every time to reset clients' status.
chromeos::DBusThreadManager::GetSetterForTesting()->SetDebugDaemonClient(
std::make_unique<TestDebugDaemonClient>());
chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
std::make_unique<TestConciergeClient>());
chromeos::UpstartClient::InitializeFake();
}
~ArcVmClientAdapterTest() override {
chromeos::DBusThreadManager::GetSetterForTesting()->SetConciergeClient(
nullptr);
chromeos::DBusThreadManager::GetSetterForTesting()->SetDebugDaemonClient(
nullptr);
}
void SetUp() override {
run_loop_ = std::make_unique<base::RunLoop>();
adapter_ = CreateArcVmClientAdapter();
arc_instance_stopped_called_ = false;
adapter_->AddObserver(this);
// The fake client returns VM_STATUS_STARTING by default. Change it
// to VM_STATUS_RUNNING which is used by ARCVM.
vm_tools::concierge::StartVmResponse start_vm_response;
start_vm_response.set_status(vm_tools::concierge::VM_STATUS_RUNNING);
GetTestConciergeClient()->set_start_vm_response(start_vm_response);
}
void TearDown() override {
adapter_->RemoveObserver(this);
adapter_.reset();
run_loop_.reset();
}
// ArcClientAdapter::Observer:
void ArcInstanceStopped() override {
arc_instance_stopped_called_ = true;
run_loop()->Quit();
}
void ExpectTrueThenQuit(bool result) {
EXPECT_TRUE(result);
run_loop()->Quit();
}
void ExpectFalseThenQuit(bool result) {
EXPECT_FALSE(result);
run_loop()->Quit();
}
protected:
bool GetStartConciergeCalled() {
return GetTestDebugDaemonClient()->start_concierge_called();
}
void SetStartConciergeResponse(bool response) {
GetTestDebugDaemonClient()->set_start_concierge_result(response);
}
void SetValidUserIdHash() { adapter()->SetUserIdHashForProfile(kUserIdHash); }
void StartMiniArc() {
StartArcMiniContainerRequest req;
adapter()->StartMiniArc(
req, base::BindOnce(&ArcVmClientAdapterTest::ExpectTrueThenQuit,
base::Unretained(this)));
run_loop()->Run();
RecreateRunLoop();
}
void UpgradeArc(bool expect_success) {
UpgradeArcContainerRequest req;
adapter()->UpgradeArc(
req, base::BindOnce(expect_success
? &ArcVmClientAdapterTest::ExpectTrueThenQuit
: &ArcVmClientAdapterTest::ExpectFalseThenQuit,
base::Unretained(this)));
run_loop()->Run();
RecreateRunLoop();
}
void SendVmStoppedSignal() {
vm_tools::concierge::VmStoppedSignal signal;
signal.set_name(kArcVmName);
auto& vm_observers = GetTestConciergeClient()->vm_observer_list();
for (auto& observer : vm_observers)
observer.OnVmStopped(signal);
}
void SendNameOwnerChangedSignal() {
auto& observers = GetTestConciergeClient()->observer_list();
for (auto& observer : observers)
observer.ConciergeServiceStopped();
}
void RecreateRunLoop() { run_loop_ = std::make_unique<base::RunLoop>(); }
base::RunLoop* run_loop() { return run_loop_.get(); }
ArcClientAdapter* adapter() { return adapter_.get(); }
bool arc_instance_stopped_called() const {
return arc_instance_stopped_called_;
}
void reset_arc_instance_stopped_called() {
arc_instance_stopped_called_ = false;
}
TestConciergeClient* GetTestConciergeClient() {
return static_cast<TestConciergeClient*>(
chromeos::DBusThreadManager::Get()->GetConciergeClient());
}
private:
TestDebugDaemonClient* GetTestDebugDaemonClient() {
return static_cast<TestDebugDaemonClient*>(
chromeos::DBusThreadManager::Get()->GetDebugDaemonClient());
}
std::unique_ptr<base::RunLoop> run_loop_;
std::unique_ptr<ArcClientAdapter> adapter_;
bool arc_instance_stopped_called_;
content::BrowserTaskEnvironment browser_task_environment_;
DISALLOW_COPY_AND_ASSIGN(ArcVmClientAdapterTest);
};
// Tests that SetUserIdHashForProfile() doesn't crash.
TEST_F(ArcVmClientAdapterTest, SetUserIdHashForProfile) {
adapter()->SetUserIdHashForProfile("deadbeef");
}
// Tests that StartMiniArc() always succeeds.
TEST_F(ArcVmClientAdapterTest, StartMiniArc) {
StartMiniArc();
// Confirm that no VM is started. ARCVM doesn't support mini ARC yet.
EXPECT_FALSE(GetTestConciergeClient()->start_arc_vm_called());
}
// Tests that StopArcInstance() eventually notifies the observer.
TEST_F(ArcVmClientAdapterTest, StopArcInstance) {
StartMiniArc();
adapter()->StopArcInstance();
run_loop()->RunUntilIdle();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
// The callback for StopVm D-Bus reply does NOT call ArcInstanceStopped when
// the D-Bus call result is successful.
EXPECT_FALSE(arc_instance_stopped_called());
// Instead, vm_concierge explicitly notifies Chrome of the VM termination.
RecreateRunLoop();
SendVmStoppedSignal();
run_loop()->Run();
// ..and that calls ArcInstanceStopped.
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that StopArcInstance() immediately notifies the observer on failure.
TEST_F(ArcVmClientAdapterTest, StopArcInstance_Fail) {
StartMiniArc();
// Inject failure.
vm_tools::concierge::StopVmResponse response;
response.set_success(false);
GetTestConciergeClient()->set_stop_vm_response(response);
adapter()->StopArcInstance();
run_loop()->Run();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
// The callback for StopVm D-Bus reply does call ArcInstanceStopped when
// the D-Bus call result is NOT successful.
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that UpgradeArc() handles StartConcierge() failures properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_StartConciergeFailure) {
SetValidUserIdHash();
StartMiniArc();
// Inject failure to StartConcierge().
SetStartConciergeResponse(false);
UpgradeArc(false);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_FALSE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Try to stop the VM. StopVm will fail in this case because
// no VM is running.
vm_tools::concierge::StopVmResponse response;
response.set_success(false);
GetTestConciergeClient()->set_stop_vm_response(response);
adapter()->StopArcInstance();
run_loop()->Run();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that "no user ID hash" failure is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_NoUserId) {
StartMiniArc();
// Don't call SetValidUserIdHash(). Note that we cannot call StartArcVm()
// without a valid ID.
UpgradeArc(false);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_FALSE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Try to stop the VM. StopVm will fail in this case because
// no VM is running.
vm_tools::concierge::StopVmResponse response;
response.set_success(false);
GetTestConciergeClient()->set_stop_vm_response(response);
adapter()->StopArcInstance();
run_loop()->Run();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that StartArcVm() failure is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_StartArcVmFailure) {
SetValidUserIdHash();
StartMiniArc();
// Inject failure to StartArcVm().
vm_tools::concierge::StartVmResponse start_vm_response;
start_vm_response.set_status(vm_tools::concierge::VM_STATUS_UNKNOWN);
GetTestConciergeClient()->set_start_vm_response(start_vm_response);
UpgradeArc(false);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Try to stop the VM. StopVm will fail in this case because
// no VM is running.
vm_tools::concierge::StopVmResponse response;
response.set_success(false);
GetTestConciergeClient()->set_stop_vm_response(response);
adapter()->StopArcInstance();
run_loop()->Run();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that successful StartArcVm() call is handled properly.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_Success) {
SetValidUserIdHash();
StartMiniArc();
UpgradeArc(true);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Try to stop the VM.
adapter()->StopArcInstance();
run_loop()->RunUntilIdle();
EXPECT_TRUE(GetTestConciergeClient()->stop_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
RecreateRunLoop();
SendVmStoppedSignal();
run_loop()->Run();
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that StartArcVm() is called with valid parameters.
TEST_F(ArcVmClientAdapterTest, UpgradeArc_StartArcVmParams) {
SetValidUserIdHash();
StartMiniArc();
UpgradeArc(true);
ASSERT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
// Verify parameters
const auto& params = GetTestConciergeClient()->start_arc_vm_request();
EXPECT_EQ("arcvm", params.name());
EXPECT_EQ(kUserIdHash, params.owner_id());
EXPECT_LT(0u, params.cpus());
EXPECT_FALSE(params.vm().kernel().empty());
// Make sure system.raw.img is passed.
EXPECT_FALSE(params.vm().rootfs().empty());
// Make sure vendor.raw.img is passed.
EXPECT_LE(1, params.disks_size());
EXPECT_LT(0, params.params_size());
}
// Tests that crosvm crash is handled properly.
TEST_F(ArcVmClientAdapterTest, CrosvmCrash) {
SetValidUserIdHash();
StartMiniArc();
UpgradeArc(true);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Kill crosvm and verify StopArcInstance is called.
SendVmStoppedSignal();
run_loop()->Run();
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests that vm_concierge crash is handled properly.
TEST_F(ArcVmClientAdapterTest, ConciergeCrash) {
SetValidUserIdHash();
StartMiniArc();
UpgradeArc(true);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Kill vm_concierge and verify StopArcInstance is called.
SendNameOwnerChangedSignal();
run_loop()->Run();
EXPECT_TRUE(arc_instance_stopped_called());
}
// Tests the case where crosvm crashes, then vm_concierge crashes too.
TEST_F(ArcVmClientAdapterTest, CrosvmAndConciergeCrashes) {
SetValidUserIdHash();
StartMiniArc();
UpgradeArc(true);
EXPECT_TRUE(GetStartConciergeCalled());
EXPECT_TRUE(GetTestConciergeClient()->start_arc_vm_called());
EXPECT_FALSE(arc_instance_stopped_called());
// Kill crosvm and verify StopArcInstance is called.
SendVmStoppedSignal();
run_loop()->Run();
EXPECT_TRUE(arc_instance_stopped_called());
// Kill vm_concierge and verify StopArcInstance is NOT called since
// the observer has already been called.
RecreateRunLoop();
reset_arc_instance_stopped_called();
SendNameOwnerChangedSignal();
run_loop()->RunUntilIdle();
EXPECT_FALSE(arc_instance_stopped_called());
}
} // namespace
} // namespace arc
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment