• 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/selector/creation_analyzer.h"
18 
19 #include <sys/types.h>
20 
21 #include <algorithm>
22 #include <map>
23 #include <regex>
24 #include <set>
25 #include <string>
26 
27 #include <android-base/parseint.h>
28 #include <android-base/strings.h>
29 
30 #include "common/libs/utils/contains.h"
31 #include "common/libs/utils/flag_parser.h"
32 #include "common/libs/utils/users.h"
33 #include "host/commands/cvd/common_utils.h"
34 #include "host/commands/cvd/selector/instance_database_utils.h"
35 #include "host/commands/cvd/selector/selector_constants.h"
36 #include "host/libs/config/cuttlefish_config.h"
37 
38 namespace cuttlefish {
39 namespace selector {
40 
IsCvdStart(const std::string & cmd)41 static bool IsCvdStart(const std::string& cmd) {
42   if (cmd.empty()) {
43     return false;
44   }
45   return cmd == "start";
46 }
47 
Analyze(const std::string & cmd,const CreationAnalyzerParam & param,const ucred & credential,const InstanceDatabase & instance_database,InstanceLockFileManager & instance_lock_file_manager)48 Result<GroupCreationInfo> CreationAnalyzer::Analyze(
49     const std::string& cmd, const CreationAnalyzerParam& param,
50     const ucred& credential, const InstanceDatabase& instance_database,
51     InstanceLockFileManager& instance_lock_file_manager) {
52   CF_EXPECT(IsCvdStart(cmd),
53             "CreationAnalyzer::Analyze() is for cvd start only.");
54   const auto client_uid = credential.uid;
55   auto selector_options_parser =
56       CF_EXPECT(StartSelectorParser::ConductSelectFlagsParser(
57           client_uid, param.selector_args, param.cmd_args, param.envs));
58   CreationAnalyzer analyzer(param, credential,
59                             std::move(selector_options_parser),
60                             instance_database, instance_lock_file_manager);
61   auto result = CF_EXPECT(analyzer.Analyze());
62   return result;
63 }
64 
CreationAnalyzer(const CreationAnalyzerParam & param,const ucred & credential,StartSelectorParser && selector_options_parser,const InstanceDatabase & instance_database,InstanceLockFileManager & instance_file_lock_manager)65 CreationAnalyzer::CreationAnalyzer(
66     const CreationAnalyzerParam& param, const ucred& credential,
67     StartSelectorParser&& selector_options_parser,
68     const InstanceDatabase& instance_database,
69     InstanceLockFileManager& instance_file_lock_manager)
70     : cmd_args_(param.cmd_args),
71       envs_(param.envs),
72       selector_args_(param.selector_args),
73       credential_(credential),
74       selector_options_parser_{std::move(selector_options_parser)},
75       instance_database_{instance_database},
76       instance_file_lock_manager_{instance_file_lock_manager} {}
77 
ConstructIdLockFileMap(std::vector<InstanceLockFile> && lock_files)78 static std::unordered_map<unsigned, InstanceLockFile> ConstructIdLockFileMap(
79     std::vector<InstanceLockFile>&& lock_files) {
80   std::unordered_map<unsigned, InstanceLockFile> mapping;
81   for (auto& lock_file : lock_files) {
82     const unsigned id = static_cast<unsigned>(lock_file.Instance());
83     mapping.insert({id, std::move(lock_file)});
84   }
85   lock_files.clear();
86   return mapping;
87 }
88 
IsIdAvailable(const InstanceDatabase & instance_database,const unsigned id)89 static Result<void> IsIdAvailable(const InstanceDatabase& instance_database,
90                                   const unsigned id) {
91   auto subset =
92       CF_EXPECT(instance_database.FindInstances(Query{kInstanceIdField, id}));
93   CF_EXPECT(subset.empty());
94   return {};
95 }
96 
97 Result<std::vector<PerInstanceInfo>>
AnalyzeInstanceIdsInternal(const std::vector<unsigned> & requested_instance_ids)98 CreationAnalyzer::AnalyzeInstanceIdsInternal(
99     const std::vector<unsigned>& requested_instance_ids) {
100   CF_EXPECT(!requested_instance_ids.empty(),
101             "Instance IDs were specified, so should be one or more.");
102   for (const auto id : requested_instance_ids) {
103     CF_EXPECT(IsIdAvailable(instance_database_, id),
104               "instance ID #" << id << " is requeested but not available.");
105   }
106 
107   std::vector<std::string> per_instance_names;
108   if (selector_options_parser_.PerInstanceNames()) {
109     per_instance_names = *selector_options_parser_.PerInstanceNames();
110     CF_EXPECT_EQ(per_instance_names.size(), requested_instance_ids.size());
111   } else {
112     for (const auto id : requested_instance_ids) {
113       per_instance_names.push_back(std::to_string(id));
114     }
115   }
116 
117   std::map<unsigned, std::string> id_name_pairs;
118   for (size_t i = 0; i != requested_instance_ids.size(); i++) {
119     id_name_pairs[requested_instance_ids.at(i)] = per_instance_names.at(i);
120   }
121 
122   std::vector<PerInstanceInfo> instance_info;
123   bool must_acquire_file_locks = selector_options_parser_.MustAcquireFileLock();
124   if (!must_acquire_file_locks) {
125     for (const auto& [id, name] : id_name_pairs) {
126       instance_info.emplace_back(id, name);
127     }
128     return instance_info;
129   }
130   auto acquired_all_file_locks =
131       CF_EXPECT(instance_file_lock_manager_.LockAllAvailable());
132   auto id_to_lockfile_map =
133       ConstructIdLockFileMap(std::move(acquired_all_file_locks));
134   for (const auto& [id, instance_name] : id_name_pairs) {
135     CF_EXPECT(Contains(id_to_lockfile_map, id),
136               "Instance ID " << id << " lock file can't be locked.");
137     auto& lock_file = id_to_lockfile_map.at(id);
138     instance_info.emplace_back(id, instance_name, std::move(lock_file));
139   }
140   return instance_info;
141 }
142 
143 /*
144  * Filters out the ids in id_pool that already exist in instance_database
145  */
CollectUnusedIds(const InstanceDatabase & instance_database,std::vector<unsigned> && id_pool)146 static Result<std::vector<unsigned>> CollectUnusedIds(
147     const InstanceDatabase& instance_database,
148     std::vector<unsigned>&& id_pool) {
149   std::vector<unsigned> collected_ids;
150   for (const auto id : id_pool) {
151     if (IsIdAvailable(instance_database, id).ok()) {
152       collected_ids.push_back(id);
153     }
154   }
155   return collected_ids;
156 }
157 
158 struct NameLockFilePair {
159   std::string name;
160   InstanceLockFile lock_file;
161 };
162 Result<std::vector<PerInstanceInfo>>
AnalyzeInstanceIdsInternal()163 CreationAnalyzer::AnalyzeInstanceIdsInternal() {
164   CF_EXPECT(selector_options_parser_.MustAcquireFileLock(),
165             "For now, cvd server always acquire the file locks "
166                 << "when IDs are automatically allocated.");
167 
168   // As this test was done earlier, this line must not fail
169   const auto n_instances = selector_options_parser_.RequestedNumInstances();
170   auto acquired_all_file_locks =
171       CF_EXPECT(instance_file_lock_manager_.LockAllAvailable());
172   auto id_to_lockfile_map =
173       ConstructIdLockFileMap(std::move(acquired_all_file_locks));
174 
175   /* generate n_instances consecutive ids. For backward compatibility,
176    * we prefer n consecutive ids for now.
177    */
178   std::vector<unsigned> id_pool;
179   id_pool.reserve(id_to_lockfile_map.size());
180   for (const auto& [id, _] : id_to_lockfile_map) {
181     id_pool.push_back(id);
182   }
183   auto unused_id_pool =
184       CF_EXPECT(CollectUnusedIds(instance_database_, std::move(id_pool)));
185   auto unique_id_allocator = std::move(IdAllocator::New(unused_id_pool));
186   CF_EXPECT(unique_id_allocator != nullptr,
187             "Memory allocation for UniqueResourceAllocator failed.");
188 
189   // auto-generation means the user did not specify much: e.g. "cvd start"
190   // In this case, the user may expect the instance id to be 1+
191   using ReservationSet = UniqueResourceAllocator<unsigned>::ReservationSet;
192   std::optional<ReservationSet> allocated_ids_opt;
193   if (selector_options_parser_.IsMaybeDefaultGroup()) {
194     allocated_ids_opt = unique_id_allocator->TakeRange(1, 1 + n_instances);
195   }
196   if (!allocated_ids_opt) {
197     allocated_ids_opt =
198         unique_id_allocator->UniqueConsecutiveItems(n_instances);
199   }
200   CF_EXPECT(allocated_ids_opt != std::nullopt, "Unique ID allocation failed.");
201 
202   std::vector<unsigned> allocated_ids;
203   allocated_ids.reserve(allocated_ids_opt->size());
204   for (const auto& reservation : *allocated_ids_opt) {
205     allocated_ids.push_back(reservation.Get());
206   }
207   std::sort(allocated_ids.begin(), allocated_ids.end());
208 
209   const auto per_instance_names_opt =
210       selector_options_parser_.PerInstanceNames();
211   if (per_instance_names_opt) {
212     CF_EXPECT(per_instance_names_opt->size() == allocated_ids.size());
213   }
214   std::vector<PerInstanceInfo> instance_info;
215   for (size_t i = 0; i != allocated_ids.size(); i++) {
216     const auto id = allocated_ids.at(i);
217     std::string name = (per_instance_names_opt ? per_instance_names_opt->at(i)
218                                                : std::to_string(id));
219     instance_info.emplace_back(id, name, std::move(id_to_lockfile_map.at(id)));
220   }
221   return instance_info;
222 }
223 
AnalyzeInstanceIds()224 Result<std::vector<PerInstanceInfo>> CreationAnalyzer::AnalyzeInstanceIds() {
225   auto requested_instance_ids = selector_options_parser_.InstanceIds();
226   return requested_instance_ids
227              ? CF_EXPECT(AnalyzeInstanceIdsInternal(*requested_instance_ids))
228              : CF_EXPECT(AnalyzeInstanceIdsInternal());
229 }
230 
231 /*
232  * 1. Remove --num_instances, --instance_nums, --base_instance_num if any.
233  * 2. If the ids are consecutive and ordered, add:
234  *   --base_instance_num=min --num_instances=ids.size()
235  * 3. If not, --instance_nums=<ids>
236  *
237  */
UpdateInstanceArgs(std::vector<std::string> && args,const std::vector<unsigned> & ids)238 static Result<std::vector<std::string>> UpdateInstanceArgs(
239     std::vector<std::string>&& args, const std::vector<unsigned>& ids) {
240   CF_EXPECT(ids.empty() == false);
241 
242   std::vector<std::string> new_args{std::move(args)};
243   std::string old_instance_nums;
244   std::string old_num_instances;
245   std::string old_base_instance_num;
246 
247   std::vector<Flag> instance_id_flags{
248       GflagsCompatFlag("instance_nums", old_instance_nums),
249       GflagsCompatFlag("num_instances", old_num_instances),
250       GflagsCompatFlag("base_instance_num", old_base_instance_num)};
251   // discard old ones
252   ParseFlags(instance_id_flags, new_args);
253 
254   auto max = *(std::max_element(ids.cbegin(), ids.cend()));
255   auto min = *(std::min_element(ids.cbegin(), ids.cend()));
256 
257   const bool is_consecutive = ((max - min) == (ids.size() - 1));
258   const bool is_sorted = std::is_sorted(ids.begin(), ids.end());
259 
260   if (!is_consecutive || !is_sorted) {
261     std::string flag_value = android::base::Join(ids, ",");
262     new_args.push_back("--instance_nums=" + flag_value);
263     return new_args;
264   }
265 
266   // sorted and consecutive, so let's use old flags
267   // like --num_instances and --base_instance_num
268   new_args.push_back("--num_instances=" + std::to_string(ids.size()));
269   new_args.push_back("--base_instance_num=" + std::to_string(min));
270   return new_args;
271 }
272 
UpdateWebrtcDeviceId(std::vector<std::string> && args,const std::vector<PerInstanceInfo> & per_instance_info)273 Result<std::vector<std::string>> CreationAnalyzer::UpdateWebrtcDeviceId(
274     std::vector<std::string>&& args,
275     const std::vector<PerInstanceInfo>& per_instance_info) {
276   std::vector<std::string> new_args{std::move(args)};
277   std::string flag_value;
278   std::vector<Flag> webrtc_device_id_flag{
279       GflagsCompatFlag("webrtc_device_id", flag_value)};
280   std::vector<std::string> copied_args{new_args};
281   CF_EXPECT(ParseFlags(webrtc_device_id_flag, copied_args));
282 
283   if (!flag_value.empty()) {
284     return new_args;
285   }
286 
287   CF_EXPECT(!group_name_.empty());
288   std::vector<std::string> device_name_list;
289   for (const auto& instance : per_instance_info) {
290     const auto& per_instance_name = instance.per_instance_name_;
291     std::string device_name = group_name_ + "-" + per_instance_name;
292     device_name_list.push_back(device_name);
293   }
294   // take --webrtc_device_id flag away
295   new_args = std::move(copied_args);
296   new_args.push_back("--webrtc_device_id=" +
297                      android::base::Join(device_name_list, ","));
298   return new_args;
299 }
300 
Analyze()301 Result<GroupCreationInfo> CreationAnalyzer::Analyze() {
302   auto instance_info = CF_EXPECT(AnalyzeInstanceIds());
303   std::vector<unsigned> ids;
304   ids.reserve(instance_info.size());
305   for (const auto& instance : instance_info) {
306     ids.push_back(instance.instance_id_);
307   }
308   cmd_args_ = CF_EXPECT(UpdateInstanceArgs(std::move(cmd_args_), ids));
309 
310   group_name_ = CF_EXPECT(AnalyzeGroupName(instance_info));
311   cmd_args_ =
312       CF_EXPECT(UpdateWebrtcDeviceId(std::move(cmd_args_), instance_info));
313 
314   home_ = CF_EXPECT(AnalyzeHome());
315   envs_["HOME"] = home_;
316 
317   CF_EXPECT(Contains(envs_, kAndroidHostOut));
318   std::string android_product_out_path = Contains(envs_, kAndroidProductOut)
319                                              ? envs_.at(kAndroidProductOut)
320                                              : envs_.at(kAndroidHostOut);
321   GroupCreationInfo report = {.home = home_,
322                               .host_artifacts_path = envs_.at(kAndroidHostOut),
323                               .product_out_path = android_product_out_path,
324                               .group_name = group_name_,
325                               .instances = std::move(instance_info),
326                               .args = cmd_args_,
327                               .envs = envs_};
328   return report;
329 }
330 
AnalyzeGroupName(const std::vector<PerInstanceInfo> & per_instance_infos) const331 Result<std::string> CreationAnalyzer::AnalyzeGroupName(
332     const std::vector<PerInstanceInfo>& per_instance_infos) const {
333   if (selector_options_parser_.GroupName()) {
334     return selector_options_parser_.GroupName().value();
335   }
336   // auto-generate group name
337   std::vector<unsigned> ids;
338   ids.reserve(per_instance_infos.size());
339   for (const auto& per_instance_info : per_instance_infos) {
340     ids.push_back(per_instance_info.instance_id_);
341   }
342   std::string base_name = GenDefaultGroupName();
343   if (selector_options_parser_.IsMaybeDefaultGroup()) {
344     /*
345      * this base_name might be already taken. In that case, the user's
346      * request should fail in the InstanceDatabase
347      */
348     auto groups =
349         CF_EXPECT(instance_database_.FindGroups({kGroupNameField, base_name}));
350     CF_EXPECT(groups.empty(), "The default instance group name, \""
351                                   << base_name << "\" has been already taken.");
352     return base_name;
353   }
354 
355   /* We cannot return simply "cvd" as we do not want duplication in the group
356    * name across the instance groups owned by the user. Note that the set of ids
357    * are expected to be unique to the user, so we use the ids. If ever the end
358    * user happened to have already used the generated name, we did our best, and
359    * cvd start will fail with a proper error message.
360    */
361   auto unique_suffix =
362       std::to_string(*std::min_element(ids.begin(), ids.end()));
363   return base_name + "_" + unique_suffix;
364 }
365 
AnalyzeHome() const366 Result<std::string> CreationAnalyzer::AnalyzeHome() const {
367   auto system_wide_home = CF_EXPECT(SystemWideUserHome(credential_.uid));
368   if (Contains(envs_, "HOME") && envs_.at("HOME") != system_wide_home) {
369     return envs_.at("HOME");
370   }
371 
372   if (selector_options_parser_.IsMaybeDefaultGroup()) {
373     auto groups = CF_EXPECT(
374         instance_database_.FindGroups({kHomeField, system_wide_home}));
375     if (groups.empty()) {
376       return system_wide_home;
377     }
378   }
379 
380   CF_EXPECT(!group_name_.empty(),
381             "To auto-generate HOME, the group name is a must.");
382   const auto client_uid = credential_.uid;
383   const auto client_gid = credential_.gid;
384   std::string auto_generated_home =
385       CF_EXPECT(ParentOfAutogeneratedHomes(client_uid, client_gid));
386   auto_generated_home.append("/" + std::to_string(client_uid));
387   auto_generated_home.append("/" + group_name_);
388   CF_EXPECT(EnsureDirectoryExistsAllTheWay(auto_generated_home));
389   return auto_generated_home;
390 }
391 
392 }  // namespace selector
393 }  // namespace cuttlefish
394