/* * 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 #include #include #include #include #include #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 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 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 InstanceManager::Serialize(const uid_t uid) { std::lock_guard lock(instance_db_mutex_); const auto& db = GetInstanceDB(uid); return db.Serialize(); } Result 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::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::SelectGroup( const cvd_common::Args& selector_args, const cvd_common::Envs& envs, const uid_t uid) { return SelectGroup(selector_args, {}, envs, uid); } Result 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::SelectInstance( const cvd_common::Args& selector_args, const cvd_common::Envs& envs, const uid_t uid) { return SelectInstance(selector_args, {}, envs, uid); } Result 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 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 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 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 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 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::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 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 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 InstanceManager::CvdFleet( const uid_t uid, const SharedFD& out, const SharedFD& err, const std::vector& 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 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 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> InstanceManager::TryAcquireLock( int instance_num) { std::lock_guard lock(instance_db_mutex_); return CF_EXPECT(lock_manager_.TryAcquireLock(instance_num)); } Result> InstanceManager::FindGroups(const uid_t uid, const Query& query) const { return CF_EXPECT(FindGroups(uid, Queries{query})); } Result> 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 output; for (const auto& group_ref : groups) { output.push_back(group_ref.Get()); } return output; } Result> InstanceManager::FindInstances(const uid_t uid, const Query& query) const { return CF_EXPECT(FindInstances(uid, Queries{query})); } Result> 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 output; for (const auto& instance : instances) { output.push_back(instance.Get().GetCopy()); } return output; } Result InstanceManager::FindGroup( const uid_t uid, const Query& query) const { return CF_EXPECT(FindGroup(uid, Queries{query})); } Result 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