• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "host/commands/cvd/instance_manager.h"
18 
19 #include <signal.h>
20 
21 #include <map>
22 #include <mutex>
23 #include <sstream>
24 
25 #include <android-base/file.h>
26 #include <fruit/fruit.h>
27 
28 #include "common/libs/fs/shared_buf.h"
29 #include "common/libs/fs/shared_fd.h"
30 #include "common/libs/utils/contains.h"
31 #include "common/libs/utils/files.h"
32 #include "common/libs/utils/flag_parser.h"
33 #include "common/libs/utils/result.h"
34 #include "common/libs/utils/subprocess.h"
35 #include "cvd_server.pb.h"
36 #include "host/commands/cvd/common_utils.h"
37 #include "host/commands/cvd/selector/instance_database_utils.h"
38 #include "host/commands/cvd/selector/selector_constants.h"
39 #include "host/commands/cvd/server_constants.h"
40 #include "host/libs/config/cuttlefish_config.h"
41 #include "host/libs/config/known_paths.h"
42 
43 namespace cuttlefish {
44 namespace {
45 
46 // Returns true only if command terminated normally, and returns 0
RunCommand(Command && command)47 Result<void> RunCommand(Command&& command) {
48   auto subprocess = std::move(command.Start());
49   siginfo_t infop{};
50   // This blocks until the process exits, but doesn't reap it.
51   auto result = subprocess.Wait(&infop, WEXITED);
52   CF_EXPECT(result != -1, "Lost track of subprocess pid");
53   CF_EXPECT(infop.si_code == CLD_EXITED && infop.si_status == 0);
54   return {};
55 }
56 
57 }  // namespace
58 
GetCuttlefishConfigPath(const std::string & home)59 Result<std::string> InstanceManager::GetCuttlefishConfigPath(
60     const std::string& home) {
61   return selector::GetCuttlefishConfigPath(home);
62 }
63 
InstanceManager(InstanceLockFileManager & lock_manager,HostToolTargetManager & host_tool_target_manager)64 InstanceManager::InstanceManager(
65     InstanceLockFileManager& lock_manager,
66     HostToolTargetManager& host_tool_target_manager)
67     : lock_manager_(lock_manager),
68       host_tool_target_manager_(host_tool_target_manager) {}
69 
GetInstanceDB(const uid_t uid)70 selector::InstanceDatabase& InstanceManager::GetInstanceDB(const uid_t uid) {
71   if (!Contains(instance_dbs_, uid)) {
72     instance_dbs_.try_emplace(uid);
73   }
74   return instance_dbs_[uid];
75 }
76 
Serialize(const uid_t uid)77 Result<Json::Value> InstanceManager::Serialize(const uid_t uid) {
78   std::lock_guard lock(instance_db_mutex_);
79   const auto& db = GetInstanceDB(uid);
80   return db.Serialize();
81 }
82 
LoadFromJson(const uid_t uid,const Json::Value & db_json)83 Result<void> InstanceManager::LoadFromJson(const uid_t uid,
84                                            const Json::Value& db_json) {
85   std::lock_guard lock(instance_db_mutex_);
86   CF_EXPECT(!Contains(instance_dbs_, uid));
87   auto& db = GetInstanceDB(uid);
88   CF_EXPECT(db.LoadFromJson(db_json));
89   return {};
90 }
91 
Analyze(const std::string & sub_cmd,const CreationAnalyzerParam & param,const ucred & credential)92 Result<InstanceManager::GroupCreationInfo> InstanceManager::Analyze(
93     const std::string& sub_cmd, const CreationAnalyzerParam& param,
94     const ucred& credential) {
95   const uid_t uid = credential.uid;
96   std::unique_lock lock(instance_db_mutex_);
97   auto& instance_db = GetInstanceDB(uid);
98   lock.unlock();
99 
100   auto group_creation_info = CF_EXPECT(CreationAnalyzer::Analyze(
101       sub_cmd, param, credential, instance_db, lock_manager_));
102   return {group_creation_info};
103 }
104 
SelectGroup(const cvd_common::Args & selector_args,const cvd_common::Envs & envs,const uid_t uid)105 Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
106     const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
107     const uid_t uid) {
108   return SelectGroup(selector_args, {}, envs, uid);
109 }
110 
SelectGroup(const cvd_common::Args & selector_args,const Queries & extra_queries,const cvd_common::Envs & envs,const uid_t uid)111 Result<InstanceManager::LocalInstanceGroup> InstanceManager::SelectGroup(
112     const cvd_common::Args& selector_args, const Queries& extra_queries,
113     const cvd_common::Envs& envs, const uid_t uid) {
114   std::unique_lock lock(instance_db_mutex_);
115   auto& instance_db = GetInstanceDB(uid);
116   auto group_selector = CF_EXPECT(
117       GroupSelector::GetSelector(selector_args, extra_queries, envs, uid));
118   auto group = CF_EXPECT(group_selector.FindGroup(instance_db));
119   return group;
120 }
121 
SelectInstance(const cvd_common::Args & selector_args,const cvd_common::Envs & envs,const uid_t uid)122 Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
123     const cvd_common::Args& selector_args, const cvd_common::Envs& envs,
124     const uid_t uid) {
125   return SelectInstance(selector_args, {}, envs, uid);
126 }
127 
SelectInstance(const cvd_common::Args & selector_args,const Queries & extra_queries,const cvd_common::Envs & envs,const uid_t uid)128 Result<InstanceManager::LocalInstance::Copy> InstanceManager::SelectInstance(
129     const cvd_common::Args& selector_args, const Queries& extra_queries,
130     const cvd_common::Envs& envs, const uid_t uid) {
131   std::unique_lock lock(instance_db_mutex_);
132   auto& instance_db = GetInstanceDB(uid);
133   auto instance_selector = CF_EXPECT(
134       InstanceSelector::GetSelector(selector_args, extra_queries, envs, uid));
135   auto instance_copy = CF_EXPECT(instance_selector.FindInstance(instance_db));
136   return instance_copy;
137 }
138 
HasInstanceGroups(const uid_t uid)139 bool InstanceManager::HasInstanceGroups(const uid_t uid) {
140   std::lock_guard lock(instance_db_mutex_);
141   auto& instance_db = GetInstanceDB(uid);
142   return !instance_db.IsEmpty();
143 }
144 
SetInstanceGroup(const uid_t uid,const selector::GroupCreationInfo & group_info)145 Result<void> InstanceManager::SetInstanceGroup(
146     const uid_t uid, const selector::GroupCreationInfo& group_info) {
147   std::lock_guard assemblies_lock(instance_db_mutex_);
148   auto& instance_db = GetInstanceDB(uid);
149 
150   const auto group_name = group_info.group_name;
151   const auto home_dir = group_info.home;
152   const auto host_artifacts_path = group_info.host_artifacts_path;
153   const auto product_out_path = group_info.product_out_path;
154   const auto& per_instance_info = group_info.instances;
155 
156   auto new_group = CF_EXPECT(
157       instance_db.AddInstanceGroup({.group_name = group_name,
158                                     .home_dir = home_dir,
159                                     .host_artifacts_path = host_artifacts_path,
160                                     .product_out_path = product_out_path}));
161 
162   using InstanceInfo = selector::InstanceDatabase::InstanceInfo;
163   std::vector<InstanceInfo> instances_info;
164   for (const auto& instance : per_instance_info) {
165     InstanceInfo info{.name = instance.per_instance_name_,
166                       .id = instance.instance_id_};
167     instances_info.push_back(info);
168   }
169   auto result = instance_db.AddInstances(group_name, instances_info);
170   if (!result.ok()) {
171     /*
172      * The way InstanceManager uses the database is that it adds an empty
173      * group, gets an handle, and add instances to it. Thus, failing to adding
174      * an instance to the group does not always mean that the instance group
175      * addition fails. It is up to the caller. In this case, however, failing
176      * to add an instance to a new group means failing to create an instance
177      * group itself. Thus, we should remove the new instance group from the
178      * database.
179      *
180      */
181     instance_db.RemoveInstanceGroup(new_group.Get());
182     return CF_ERR(result.error().Trace());
183   }
184   return {};
185 }
186 
SetBuildId(const uid_t uid,const std::string & group_name,const std::string & build_id)187 Result<void> InstanceManager::SetBuildId(const uid_t uid,
188                                          const std::string& group_name,
189                                          const std::string& build_id) {
190   std::lock_guard assemblies_lock(instance_db_mutex_);
191   auto& instance_db = GetInstanceDB(uid);
192   CF_EXPECT(instance_db.SetBuildId(group_name, build_id));
193   return {};
194 }
195 
RemoveInstanceGroup(const uid_t uid,const std::string & dir)196 void InstanceManager::RemoveInstanceGroup(const uid_t uid,
197                                           const std::string& dir) {
198   std::lock_guard assemblies_lock(instance_db_mutex_);
199   auto& instance_db = GetInstanceDB(uid);
200   auto result = instance_db.FindGroup({selector::kHomeField, dir});
201   if (!result.ok()) return;
202   auto group = *result;
203   instance_db.RemoveInstanceGroup(group);
204 }
205 
206 template <typename... Args>
GetCommand(const std::string & prog_path,Args &&...args)207 static Command GetCommand(const std::string& prog_path, Args&&... args) {
208   Command command(prog_path);
209   (command.AddParameter(args), ...);
210   return command;
211 }
212 
213 struct ExecCommandResult {
214   std::string stdout_buf;
215   std::string stderr_buf;
216 };
217 
ExecCommand(Command && command)218 static Result<ExecCommandResult> ExecCommand(Command&& command) {
219   ExecCommandResult command_result;
220   CF_EXPECT_EQ(RunWithManagedStdio(std::move(command), /* stdin */ nullptr,
221                                    std::addressof(command_result.stdout_buf),
222                                    std::addressof(command_result.stderr_buf)),
223                0);
224   return command_result;
225 }
226 
227 Result<InstanceManager::StatusCommandOutput>
IssueStatusCommand(const selector::LocalInstanceGroup & group,const SharedFD & err)228 InstanceManager::IssueStatusCommand(const selector::LocalInstanceGroup& group,
229                                     const SharedFD& err) {
230   std::string not_supported_version_msg = " does not comply with cvd fleet.\n";
231   const auto host_android_out = group.HostArtifactsPath();
232   auto status_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
233       .artifacts_path = host_android_out,
234       .op = "status",
235   }));
236   const auto prog_path = host_android_out + "/bin/" + status_bin;
237   Command with_args = GetCommand(prog_path, "--all_instances", "--print");
238   with_args.SetEnvironment({ConcatToString("HOME=", group.HomeDir())});
239   auto command_result = ExecCommand(std::move(with_args));
240   if (command_result.ok()) {
241     StatusCommandOutput output;
242     if (command_result->stdout_buf.empty()) {
243       WriteAll(err, ConcatToString(group.GroupName(), "-*",
244                                    not_supported_version_msg));
245       Json::Reader().parse("{}", output.stdout_json);
246       return output;
247     }
248     output.stdout_json = CF_EXPECT(ParseJson(command_result->stdout_buf));
249     return output;
250   }
251   StatusCommandOutput output;
252   int index = 0;
253   for (const auto& instance_ref : CF_EXPECT(group.FindAllInstances())) {
254     const auto id = instance_ref.Get().InstanceId();
255     Command without_args = GetCommand(prog_path);
256     std::vector<std::string> new_envs{
257         ConcatToString("HOME=", group.HomeDir()),
258         ConcatToString(kCuttlefishInstanceEnvVarName, "=", std::to_string(id))};
259     without_args.SetEnvironment(new_envs);
260     auto second_command_result =
261         CF_EXPECT(ExecCommand(std::move(without_args)));
262     if (second_command_result.stdout_buf.empty()) {
263       WriteAll(err,
264                instance_ref.Get().DeviceName() + not_supported_version_msg);
265       second_command_result.stdout_buf.append("{}");
266     }
267     output.stdout_json[index] =
268         CF_EXPECT(ParseJson(second_command_result.stdout_buf));
269   }
270   return output;
271 }
272 
CvdFleetImpl(const uid_t uid,const SharedFD & out,const SharedFD & err)273 Result<cvd::Status> InstanceManager::CvdFleetImpl(const uid_t uid,
274                                                   const SharedFD& out,
275                                                   const SharedFD& err) {
276   std::lock_guard assemblies_lock(instance_db_mutex_);
277   auto& instance_db = GetInstanceDB(uid);
278   const char _GroupDeviceInfoStart[] = "[\n";
279   const char _GroupDeviceInfoSeparate[] = ",\n";
280   const char _GroupDeviceInfoEnd[] = "]\n";
281   WriteAll(out, _GroupDeviceInfoStart);
282   auto&& instance_groups = instance_db.InstanceGroups();
283 
284   for (const auto& group : instance_groups) {
285     CF_EXPECT(group != nullptr);
286     auto result = IssueStatusCommand(*group, err);
287     if (!result.ok()) {
288       WriteAll(err, "      (unknown instance status error)");
289     } else {
290       const auto [stderr_msg, stdout_json] = *result;
291       WriteAll(err, stderr_msg);
292       // TODO(kwstephenkim): build a data structure that also includes
293       // selector-related information, etc.
294       WriteAll(out, stdout_json.toStyledString());
295     }
296     // move on
297     if (group == *instance_groups.crbegin()) {
298       continue;
299     }
300     WriteAll(out, _GroupDeviceInfoSeparate);
301   }
302   WriteAll(out, _GroupDeviceInfoEnd);
303   cvd::Status status;
304   status.set_code(cvd::Status::OK);
305   return status;
306 }
307 
CvdFleet(const uid_t uid,const SharedFD & out,const SharedFD & err,const std::vector<std::string> & fleet_cmd_args)308 Result<cvd::Status> InstanceManager::CvdFleet(
309     const uid_t uid, const SharedFD& out, const SharedFD& err,
310     const std::vector<std::string>& fleet_cmd_args) {
311   bool is_help = false;
312   for (const auto& arg : fleet_cmd_args) {
313     if (arg == "--help" || arg == "-help") {
314       is_help = true;
315       break;
316     }
317   }
318   CF_EXPECT(!is_help,
319             "cvd fleet --help should be handled by fleet handler itself.");
320   const auto status = CF_EXPECT(CvdFleetImpl(uid, out, err));
321   return status;
322 }
323 
StopBin(const std::string & host_android_out)324 Result<std::string> InstanceManager::StopBin(
325     const std::string& host_android_out) {
326   const auto stop_bin = CF_EXPECT(host_tool_target_manager_.ExecBaseName({
327       .artifacts_path = host_android_out,
328       .op = "stop",
329   }));
330   return stop_bin;
331 }
332 
IssueStopCommand(const SharedFD & out,const SharedFD & err,const std::string & config_file_path,const selector::LocalInstanceGroup & group)333 Result<void> InstanceManager::IssueStopCommand(
334     const SharedFD& out, const SharedFD& err,
335     const std::string& config_file_path,
336     const selector::LocalInstanceGroup& group) {
337   const auto stop_bin = CF_EXPECT(StopBin(group.HostArtifactsPath()));
338   Command command(group.HostArtifactsPath() + "/bin/" + stop_bin);
339   command.AddParameter("--clear_instance_dirs");
340   command.RedirectStdIO(Subprocess::StdIOChannel::kStdOut, out);
341   command.RedirectStdIO(Subprocess::StdIOChannel::kStdErr, err);
342   command.AddEnvironmentVariable(kCuttlefishConfigEnvVarName, config_file_path);
343   auto wait_result = RunCommand(std::move(command));
344   /**
345    * --clear_instance_dirs may not be available for old branches. This causes
346    * the stop_cvd to terminates with a non-zero exit code due to the parsing
347    * error. Then, we will try to re-run it without the flag.
348    */
349   if (!wait_result.ok()) {
350     std::stringstream error_msg;
351     error_msg << stop_bin << " was executed internally, and failed. It might "
352               << "be failing to parse the new --clear_instance_dirs. Will try "
353               << "without the flag.\n";
354     WriteAll(err, error_msg.str());
355     Command no_clear_instance_dir_command(group.HostArtifactsPath() + "/bin/" +
356                                           stop_bin);
357     no_clear_instance_dir_command.RedirectStdIO(
358         Subprocess::StdIOChannel::kStdOut, out);
359     no_clear_instance_dir_command.RedirectStdIO(
360         Subprocess::StdIOChannel::kStdErr, err);
361     no_clear_instance_dir_command.AddEnvironmentVariable(
362         kCuttlefishConfigEnvVarName, config_file_path);
363     wait_result = RunCommand(std::move(no_clear_instance_dir_command));
364   }
365 
366   if (!wait_result.ok()) {
367     WriteAll(err,
368              "Warning: error stopping instances for dir \"" + group.HomeDir() +
369                  "\".\nThis can happen if instances are already stopped.\n");
370   }
371   for (const auto& instance : group.Instances()) {
372     auto lock = lock_manager_.TryAcquireLock(instance->InstanceId());
373     if (lock.ok() && (*lock)) {
374       (*lock)->Status(InUseState::kNotInUse);
375       continue;
376     }
377     WriteAll(err, "InstanceLockFileManager failed to acquire lock");
378   }
379   return {};
380 }
381 
CvdClear(const SharedFD & out,const SharedFD & err)382 cvd::Status InstanceManager::CvdClear(const SharedFD& out,
383                                       const SharedFD& err) {
384   std::lock_guard lock(instance_db_mutex_);
385   cvd::Status status;
386   const std::string config_json_name = cpp_basename(GetGlobalConfigFileLink());
387   for (auto& [uid, instance_db] : instance_dbs_) {
388     auto&& instance_groups = instance_db.InstanceGroups();
389     for (const auto& group : instance_groups) {
390       auto config_path = group->GetCuttlefishConfigPath();
391       if (config_path.ok()) {
392         auto stop_result = IssueStopCommand(out, err, *config_path, *group);
393         if (!stop_result.ok()) {
394           LOG(ERROR) << stop_result.error().Message();
395         }
396       }
397       RemoveFile(group->HomeDir() + "/cuttlefish_runtime");
398       RemoveFile(group->HomeDir() + config_json_name);
399     }
400     instance_db.Clear();
401   }
402   // TODO(kwstephenkim): we need a better mechanism to make sure that
403   // we clear all run_cvd processes.
404   instance_dbs_.clear();
405   WriteAll(err, "Stopped all known instances\n");
406   status.set_code(cvd::Status::OK);
407   return status;
408 }
409 
TryAcquireLock(int instance_num)410 Result<std::optional<InstanceLockFile>> InstanceManager::TryAcquireLock(
411     int instance_num) {
412   std::lock_guard lock(instance_db_mutex_);
413   return CF_EXPECT(lock_manager_.TryAcquireLock(instance_num));
414 }
415 
416 Result<std::vector<InstanceManager::LocalInstanceGroup>>
FindGroups(const uid_t uid,const Query & query) const417 InstanceManager::FindGroups(const uid_t uid, const Query& query) const {
418   return CF_EXPECT(FindGroups(uid, Queries{query}));
419 }
420 
421 Result<std::vector<InstanceManager::LocalInstanceGroup>>
FindGroups(const uid_t uid,const Queries & queries) const422 InstanceManager::FindGroups(const uid_t uid, const Queries& queries) const {
423   std::lock_guard lock(instance_db_mutex_);
424   if (!Contains(instance_dbs_, uid)) {
425     return {};
426   }
427   const auto& db = instance_dbs_.at(uid);
428   auto groups = CF_EXPECT(db.FindGroups(queries));
429   // create a copy as we are escaping the critical section
430   std::vector<LocalInstanceGroup> output;
431   for (const auto& group_ref : groups) {
432     output.push_back(group_ref.Get());
433   }
434   return output;
435 }
436 
437 Result<std::vector<InstanceManager::LocalInstance::Copy>>
FindInstances(const uid_t uid,const Query & query) const438 InstanceManager::FindInstances(const uid_t uid, const Query& query) const {
439   return CF_EXPECT(FindInstances(uid, Queries{query}));
440 }
441 
442 Result<std::vector<InstanceManager::LocalInstance::Copy>>
FindInstances(const uid_t uid,const Queries & queries) const443 InstanceManager::FindInstances(const uid_t uid, const Queries& queries) const {
444   std::lock_guard lock(instance_db_mutex_);
445   if (!Contains(instance_dbs_, uid)) {
446     return {};
447   }
448   const auto& db = instance_dbs_.at(uid);
449   auto instances = CF_EXPECT(db.FindInstances(queries));
450   // create a copy as we are escaping the critical section
451   std::vector<LocalInstance::Copy> output;
452   for (const auto& instance : instances) {
453     output.push_back(instance.Get().GetCopy());
454   }
455   return output;
456 }
457 
FindGroup(const uid_t uid,const Query & query) const458 Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
459     const uid_t uid, const Query& query) const {
460   return CF_EXPECT(FindGroup(uid, Queries{query}));
461 }
462 
FindGroup(const uid_t uid,const Queries & queries) const463 Result<InstanceManager::LocalInstanceGroup> InstanceManager::FindGroup(
464     const uid_t uid, const Queries& queries) const {
465   std::lock_guard lock(instance_db_mutex_);
466   CF_EXPECT(Contains(instance_dbs_, uid));
467   const auto& db = instance_dbs_.at(uid);
468   auto output = CF_EXPECT(db.FindGroups(queries));
469   CF_EXPECT_EQ(output.size(), 1);
470   return *(output.begin());
471 }
472 
473 }  // namespace cuttlefish
474