unplugged-system/device/google/cuttlefish/host/commands/cvd/instance_manager.cpp

474 lines
17 KiB
C++
Raw Permalink Normal View History

/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "host/commands/cvd/instance_manager.h"
#include <signal.h>
#include <map>
#include <mutex>
#include <sstream>
#include <android-base/file.h>
#include <fruit/fruit.h>
#include "common/libs/fs/shared_buf.h"
#include "common/libs/fs/shared_fd.h"
#include "common/libs/utils/contains.h"
#include "common/libs/utils/files.h"
#include "common/libs/utils/flag_parser.h"
#include "common/libs/utils/result.h"
#include "common/libs/utils/subprocess.h"
#include "cvd_server.pb.h"
#include "host/commands/cvd/common_utils.h"
#include "host/commands/cvd/selector/instance_database_utils.h"
#include "host/commands/cvd/selector/selector_constants.h"
#include "host/commands/cvd/server_constants.h"
#include "host/libs/config/cuttlefish_config.h"
#include "host/libs/config/known_paths.h"
namespace cuttlefish {
namespace {
// Returns true only if command terminated normally, and returns 0
Result<void> RunCommand(Command&& command) {
auto subprocess = std::move(command.Start());
siginfo_t infop{};
// This blocks until the process exits, but doesn't reap it.
auto result = subprocess.Wait(&infop, WEXITED);
CF_EXPECT(result != -1, "Lost track of subprocess pid");
CF_EXPECT(infop.si_code == CLD_EXITED && infop.si_status == 0);
return {};
}
} // namespace
Result<std::string> InstanceManager::GetCuttlefishConfigPath(
const std::string& home) {
return selector::GetCuttlefishConfigPath(home);
}
InstanceManager::InstanceManager(
InstanceLockFileManager& lock_manager,
HostToolTargetManager& host_tool_target_manager)
: lock_manager_(lock_manager),
host_tool_target_manager_(host_tool_target_manager) {}
selector::InstanceDatabase& InstanceManager::GetInstanceDB(const uid_t uid) {
if (!Contains(instance_dbs_, uid)) {
instance_dbs_.try_emplace(uid);
}
return instance_dbs_[uid];
}
Result<Json::Value> InstanceManager::Serialize(const uid_t uid) {
std::lock_guard lock(instance_db_mutex_);
const auto& db = GetInstanceDB(uid);
return db.Serialize();
}
Result<void> InstanceManager::LoadFromJson(const uid_t uid,
const Json::Value& db_json) {
std::lock_guard lock(instance_db_mutex_);
CF_EXPECT(!Contains(instance_dbs_, uid));
auto& db = GetInstanceDB(uid);
CF_EXPECT(db.LoadFromJson(db_json));
return {};
}
Result<InstanceManager::GroupCreationInfo> InstanceManager::Analyze(
const std::string& sub_cmd, const CreationAnalyzerParam& param,
const ucred& credential) {
const uid_t uid = credential.uid;
std::unique_lock lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
lock.unlock();
auto group_creation_info = CF_EXPECT(CreationAnalyzer::Analyze(
sub_cmd, param, credential, instance_db, lock_manager_));
return {group_creation_info};
}
Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
const uid_t uid) {
return SelectGroup(selector_args, {}, envs, uid);
}
Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
const cvd_common::Args& selector_args, const Queries& extra_queries,
const cvd_common::Envs& envs, const uid_t uid) {
std::unique_lock lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
auto group_selector = CF_EXPECT(
GroupSelector::GetSelector(selector_args, extra_queries, envs, uid));
auto group = CF_EXPECT(group_selector.FindGroup(instance_db));
return group;
}
Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
const uid_t uid) {
return SelectInstance(selector_args, {}, envs, uid);
}
Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
const cvd_common::Args& selector_args, const Queries& extra_queries,
const cvd_common::Envs& envs, const uid_t uid) {
std::unique_lock lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
auto instance_selector = CF_EXPECT(
InstanceSelector::GetSelector(selector_args, extra_queries, envs, uid));
auto instance_copy = CF_EXPECT(instance_selector.FindInstance(instance_db));
return instance_copy;
}
bool InstanceManager::HasInstanceGroups(const uid_t uid) {
std::lock_guard lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
return !instance_db.IsEmpty();
}
Result<void> InstanceManager::SetInstanceGroup(
const uid_t uid, const selector::GroupCreationInfo& group_info) {
std::lock_guard assemblies_lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
const auto group_name = group_info.group_name;
const auto home_dir = group_info.home;
const auto host_artifacts_path = group_info.host_artifacts_path;
const auto product_out_path = group_info.product_out_path;
const auto& per_instance_info = group_info.instances;
auto new_group = CF_EXPECT(
instance_db.AddInstanceGroup({.group_name = group_name,
.home_dir = home_dir,
.host_artifacts_path = host_artifacts_path,
.product_out_path = product_out_path}));
using InstanceInfo = selector::InstanceDatabase::InstanceInfo;
std::vector<InstanceInfo> instances_info;
for (const auto& instance : per_instance_info) {
InstanceInfo info{.name = instance.per_instance_name_,
.id = instance.instance_id_};
instances_info.push_back(info);
}
auto result = instance_db.AddInstances(group_name, instances_info);
if (!result.ok()) {
/*
* The way InstanceManager uses the database is that it adds an empty
* group, gets an handle, and add instances to it. Thus, failing to adding
* an instance to the group does not always mean that the instance group
* addition fails. It is up to the caller. In this case, however, failing
* to add an instance to a new group means failing to create an instance
* group itself. Thus, we should remove the new instance group from the
* database.
*
*/
instance_db.RemoveInstanceGroup(new_group.Get());
return CF_ERR(result.error().Trace());
}
return {};
}
Result<void> InstanceManager::SetBuildId(const uid_t uid,
const std::string& group_name,
const std::string& build_id) {
std::lock_guard assemblies_lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
CF_EXPECT(instance_db.SetBuildId(group_name, build_id));
return {};
}
void InstanceManager::RemoveInstanceGroup(const uid_t uid,
const std::string& dir) {
std::lock_guard assemblies_lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
auto result = instance_db.FindGroup({selector::kHomeField, dir});
if (!result.ok()) return;
auto group = *result;
instance_db.RemoveInstanceGroup(group);
}
template <typename... Args>
static Command GetCommand(const std::string& prog_path, Args&&... args) {
Command command(prog_path);
(command.AddParameter(args), ...);
return command;
}
struct ExecCommandResult {
std::string stdout_buf;
std::string stderr_buf;
};
static Result<ExecCommandResult> ExecCommand(Command&& command) {
ExecCommandResult command_result;
CF_EXPECT_EQ(RunWithManagedStdio(std::move(command), /* stdin */ nullptr,
std::addressof(command_result.stdout_buf),
std::addressof(command_result.stderr_buf)),
0);
return command_result;
}
Result<InstanceManager::StatusCommandOutput>
InstanceManager::IssueStatusCommand(const selector::LocalInstanceGroup& group,
const SharedFD& err) {
std::string not_supported_version_msg = " does not comply with cvd fleet.\n";
const auto host_android_out = group.HostArtifactsPath();
auto status_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
.artifacts_path = host_android_out,
.op = "status",
}));
const auto prog_path = host_android_out + "/bin/" + status_bin;
Command with_args = GetCommand(prog_path, "--all_instances", "--print");
with_args.SetEnvironment({ConcatToString("HOME=", group.HomeDir())});
auto command_result = ExecCommand(std::move(with_args));
if (command_result.ok()) {
StatusCommandOutput output;
if (command_result->stdout_buf.empty()) {
WriteAll(err, ConcatToString(group.GroupName(), "-*",
not_supported_version_msg));
Json::Reader().parse("{}", output.stdout_json);
return output;
}
output.stdout_json = CF_EXPECT(ParseJson(command_result->stdout_buf));
return output;
}
StatusCommandOutput output;
int index = 0;
for (const auto& instance_ref : CF_EXPECT(group.FindAllInstances())) {
const auto id = instance_ref.Get().InstanceId();
Command without_args = GetCommand(prog_path);
std::vector<std::string> new_envs{
ConcatToString("HOME=", group.HomeDir()),
ConcatToString(kCuttlefishInstanceEnvVarName, "=", std::to_string(id))};
without_args.SetEnvironment(new_envs);
auto second_command_result =
CF_EXPECT(ExecCommand(std::move(without_args)));
if (second_command_result.stdout_buf.empty()) {
WriteAll(err,
instance_ref.Get().DeviceName() + not_supported_version_msg);
second_command_result.stdout_buf.append("{}");
}
output.stdout_json[index] =
CF_EXPECT(ParseJson(second_command_result.stdout_buf));
}
return output;
}
Result<cvd::Status> InstanceManager::CvdFleetImpl(const uid_t uid,
const SharedFD& out,
const SharedFD& err) {
std::lock_guard assemblies_lock(instance_db_mutex_);
auto& instance_db = GetInstanceDB(uid);
const char _GroupDeviceInfoStart[] = "[\n";
const char _GroupDeviceInfoSeparate[] = ",\n";
const char _GroupDeviceInfoEnd[] = "]\n";
WriteAll(out, _GroupDeviceInfoStart);
auto&& instance_groups = instance_db.InstanceGroups();
for (const auto& group : instance_groups) {
CF_EXPECT(group != nullptr);
auto result = IssueStatusCommand(*group, err);
if (!result.ok()) {
WriteAll(err, " (unknown instance status error)");
} else {
const auto [stderr_msg, stdout_json] = *result;
WriteAll(err, stderr_msg);
// TODO(kwstephenkim): build a data structure that also includes
// selector-related information, etc.
WriteAll(out, stdout_json.toStyledString());
}
// move on
if (group == *instance_groups.crbegin()) {
continue;
}
WriteAll(out, _GroupDeviceInfoSeparate);
}
WriteAll(out, _GroupDeviceInfoEnd);
cvd::Status status;
status.set_code(cvd::Status::OK);
return status;
}
Result<cvd::Status> InstanceManager::CvdFleet(
const uid_t uid, const SharedFD& out, const SharedFD& err,
const std::vector<std::string>& fleet_cmd_args) {
bool is_help = false;
for (const auto& arg : fleet_cmd_args) {
if (arg == "--help" || arg == "-help") {
is_help = true;
break;
}
}
CF_EXPECT(!is_help,
"cvd fleet --help should be handled by fleet handler itself.");
const auto status = CF_EXPECT(CvdFleetImpl(uid, out, err));
return status;
}
Result<std::string> InstanceManager::StopBin(
const std::string& host_android_out) {
const auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
.artifacts_path = host_android_out,
.op = "stop",
}));
return stop_bin;
}
Result<void> InstanceManager::IssueStopCommand(
const SharedFD& out, const SharedFD& err,
const std::string& config_file_path,
const selector::LocalInstanceGroup& group) {
const auto stop_bin = CF_EXPECT(StopBin(group.HostArtifactsPath()));
Command command(group.HostArtifactsPath() + "/bin/" + stop_bin);
command.AddParameter("--clear_instance_dirs");
command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err);
command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, config_file_path);
auto wait_result = RunCommand(std::move(command));
/**
* --clear_instance_dirs may not be available for old branches. This causes
* the stop_cvd to terminates with a non-zero exit code due to the parsing
* error. Then, we will try to re-run it without the flag.
*/
if (!wait_result.ok()) {
std::stringstream error_msg;
error_msg << stop_bin << " was executed internally, and failed. It might "
<< "be failing to parse the new --clear_instance_dirs. Will try "
<< "without the flag.\n";
WriteAll(err, error_msg.str());
Command no_clear_instance_dir_command(group.HostArtifactsPath() + "/bin/" +
stop_bin);
no_clear_instance_dir_command.RedirectStdIO(
Subprocess::StdIOChannel::kStdOut, out);
no_clear_instance_dir_command.RedirectStdIO(
Subprocess::StdIOChannel::kStdErr, err);
no_clear_instance_dir_command.AddEnvironmentVariable(
kCuttlefishConfigEnvVarName, config_file_path);
wait_result = RunCommand(std::move(no_clear_instance_dir_command));
}
if (!wait_result.ok()) {
WriteAll(err,
"Warning: error stopping instances for dir \"" + group.HomeDir() +
"\".\nThis can happen if instances are already stopped.\n");
}
for (const auto& instance : group.Instances()) {
auto lock = lock_manager_.TryAcquireLock(instance->InstanceId());
if (lock.ok() && (*lock)) {
(*lock)->Status(InUseState::kNotInUse);
continue;
}
WriteAll(err, "InstanceLockFileManager failed to acquire lock");
}
return {};
}
cvd::Status InstanceManager::CvdClear(const SharedFD& out,
const SharedFD& err) {
std::lock_guard lock(instance_db_mutex_);
cvd::Status status;
const std::string config_json_name = cpp_basename(GetGlobalConfigFileLink());
for (auto& [uid, instance_db] : instance_dbs_) {
auto&& instance_groups = instance_db.InstanceGroups();
for (const auto& group : instance_groups) {
auto config_path = group->GetCuttlefishConfigPath();
if (config_path.ok()) {
auto stop_result = IssueStopCommand(out, err, *config_path, *group);
if (!stop_result.ok()) {
LOG(ERROR) << stop_result.error().Message();
}
}
RemoveFile(group->HomeDir() + "/cuttlefish_runtime");
RemoveFile(group->HomeDir() + config_json_name);
}
instance_db.Clear();
}
// TODO(kwstephenkim): we need a better mechanism to make sure that
// we clear all run_cvd processes.
instance_dbs_.clear();
WriteAll(err, "Stopped all known instances\n");
status.set_code(cvd::Status::OK);
return status;
}
Result<std::optional<InstanceLockFile>> InstanceManager::TryAcquireLock(
int instance_num) {
std::lock_guard lock(instance_db_mutex_);
return CF_EXPECT(lock_manager_.TryAcquireLock(instance_num));
}
Result<std::vector<InstanceManager::LocalInstanceGroup>>
InstanceManager::FindGroups(const uid_t uid, const Query& query) const {
return CF_EXPECT(FindGroups(uid, Queries{query}));
}
Result<std::vector<InstanceManager::LocalInstanceGroup>>
InstanceManager::FindGroups(const uid_t uid, const Queries& queries) const {
std::lock_guard lock(instance_db_mutex_);
if (!Contains(instance_dbs_, uid)) {
return {};
}
const auto& db = instance_dbs_.at(uid);
auto groups = CF_EXPECT(db.FindGroups(queries));
// create a copy as we are escaping the critical section
std::vector<LocalInstanceGroup> output;
for (const auto& group_ref : groups) {
output.push_back(group_ref.Get());
}
return output;
}
Result<std::vector<InstanceManager::LocalInstance::Copy>>
InstanceManager::FindInstances(const uid_t uid, const Query& query) const {
return CF_EXPECT(FindInstances(uid, Queries{query}));
}
Result<std::vector<InstanceManager::LocalInstance::Copy>>
InstanceManager::FindInstances(const uid_t uid, const Queries& queries) const {
std::lock_guard lock(instance_db_mutex_);
if (!Contains(instance_dbs_, uid)) {
return {};
}
const auto& db = instance_dbs_.at(uid);
auto instances = CF_EXPECT(db.FindInstances(queries));
// create a copy as we are escaping the critical section
std::vector<LocalInstance::Copy> output;
for (const auto& instance : instances) {
output.push_back(instance.Get().GetCopy());
}
return output;
}
Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
const uid_t uid, const Query& query) const {
return CF_EXPECT(FindGroup(uid, Queries{query}));
}
Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
const uid_t uid, const Queries& queries) const {
std::lock_guard lock(instance_db_mutex_);
CF_EXPECT(Contains(instance_dbs_, uid));
const auto& db = instance_dbs_.at(uid);
auto output = CF_EXPECT(db.FindGroups(queries));
CF_EXPECT_EQ(output.size(), 1);
return *(output.begin());
}
} // namespace cuttlefish