Skip to content

Commit 21afa94

Browse files
committed
cvd: Add --reuse_if_possible flag to cvd create
Adds `--reuse_if_possible` flag to `cvd create`. If this flag is provided, `cvd create` will check if there is an existing instance group with matching host and product paths. If a matching group is found and it is not in an active state (preparing, starting, running, stopping), it will delegate to `cvd start` for that group and skip the rest of the creation logic. Exposes `ExpandProductPaths` in `cvd` utils to share it between `cvd create` and `CreationAnalyzer` for consistent product path expansion. Assisted-by: Jetski:Gemini-Next Bug: b/519304405 TAG=agy CONV=34ddc7b8-a695-4937-be99-b796087dd987
1 parent b0fe189 commit 21afa94

5 files changed

Lines changed: 125 additions & 24 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/commands/create.cpp

Lines changed: 96 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include <cstring>
2626
#include <limits>
2727
#include <memory>
28+
#include <optional>
2829
#include <sstream>
2930
#include <string>
3031
#include <utility>
@@ -35,6 +36,7 @@
3536
#include "absl/log/log.h"
3637
#include "absl/strings/match.h"
3738
#include "absl/strings/str_join.h"
39+
#include "absl/strings/str_split.h"
3840

3941
#include "cuttlefish/common/libs/utils/contains.h"
4042
#include "cuttlefish/common/libs/utils/environment.h"
@@ -118,6 +120,18 @@ Result<CommandRequest> CreateStartCommand(const LocalInstanceGroup& group,
118120
.Build());
119121
}
120122

123+
Result<void> StartGroup(const LocalInstanceGroup& group,
124+
const std::vector<std::string>& subcmd_args,
125+
const cvd_common::Envs& envs,
126+
InstanceManager& instance_manager) {
127+
const CommandRequest start_cmd =
128+
CF_EXPECT(CreateStartCommand(group, subcmd_args, envs));
129+
std::unique_ptr<CvdCommandHandler> start_handler =
130+
NewCvdStartCommandHandler(instance_manager);
131+
CF_EXPECT(start_handler->Handle(start_cmd));
132+
return {};
133+
}
134+
121135
Result<cvd_common::Envs> GetEnvs(const CommandRequest& request) {
122136
cvd_common::Envs envs = request.Env();
123137
if (auto it = envs.find("HOME"); it != envs.end() && it->second.empty()) {
@@ -236,6 +250,47 @@ std::vector<Flag> BuildSelectorFlagsForCreateHelp(
236250
};
237251
}
238252

253+
bool IsActiveState(cvd::InstanceState state) {
254+
return state == cvd::INSTANCE_STATE_PREPARING ||
255+
state == cvd::INSTANCE_STATE_STARTING ||
256+
state == cvd::INSTANCE_STATE_RUNNING ||
257+
state == cvd::INSTANCE_STATE_STOPPING;
258+
}
259+
260+
bool IsGroupReuseable(const LocalInstanceGroup& group) {
261+
for (const LocalInstance& instance : group.Instances()) {
262+
if (IsActiveState(instance.State())) {
263+
return false;
264+
}
265+
}
266+
return true;
267+
}
268+
269+
Result<bool> MatchPaths(const LocalInstanceGroup& group, const std::string& target_host,
270+
const std::vector<std::string>& target_products) {
271+
const std::string real_group_host = CF_EXPECT(RealPath(group.HostArtifactsPath()));
272+
const std::string real_target_host = CF_EXPECT(RealPath(target_host));
273+
274+
if (real_group_host != real_target_host) {
275+
return false;
276+
}
277+
278+
const std::vector<std::string> group_products =
279+
absl::StrSplit(group.ProductOutPath(), ',');
280+
281+
std::vector<std::string> real_group_products;
282+
for (const auto& path : group_products) {
283+
real_group_products.push_back(CF_EXPECT(RealPath(path)));
284+
}
285+
286+
std::vector<std::string> real_target_products;
287+
for (const auto& path : target_products) {
288+
real_target_products.push_back(CF_EXPECT(RealPath(path)));
289+
}
290+
291+
return real_group_products == real_target_products;
292+
}
293+
239294
} // namespace
240295

241296
CvdCreateCommandHandler::CvdCreateCommandHandler(
@@ -275,18 +330,49 @@ Result<void> CvdCreateCommandHandler::Handle(const CommandRequest& request) {
275330
// CreationAnalyzer needs these to be set in the environment
276331
envs[kAndroidHostOut] = AbsolutePath(own_flags_.host_path);
277332
envs[kAndroidProductOut] = AbsolutePath(own_flags_.product_path);
278-
auto group =
333+
334+
if (own_flags_.reuse_if_possible) {
335+
const std::string target_host = envs[kAndroidHostOut];
336+
const std::string target_product = envs[kAndroidProductOut];
337+
const std::vector<std::string> target_products =
338+
ExpandProductPaths(target_product, num_instances_parser_.NumInstances());
339+
340+
const std::vector<LocalInstanceGroup> groups =
341+
CF_EXPECT(instance_manager_.FindGroups({}));
342+
343+
std::optional<LocalInstanceGroup> matched_group;
344+
for (const LocalInstanceGroup& g : groups) {
345+
const auto match_res = MatchPaths(g, target_host, target_products);
346+
if (!match_res.ok()) {
347+
continue;
348+
}
349+
if (*match_res) {
350+
matched_group = g;
351+
break;
352+
}
353+
}
354+
if (matched_group) {
355+
if (IsGroupReuseable(*matched_group)) {
356+
LOG(INFO) << "Reusing existing instance group: "
357+
<< matched_group->GroupName();
358+
CF_EXPECT(StartGroup(*matched_group, subcmd_args, envs, instance_manager_));
359+
return {};
360+
} else {
361+
LOG(WARNING) << "Found matching instance group '"
362+
<< matched_group->GroupName()
363+
<< "' but it is in active state. Proceding with creation of a new group.";
364+
}
365+
}
366+
}
367+
368+
LocalInstanceGroup group =
279369
CF_EXPECT(CreateGroup(instance_manager_, subcmd_args, envs, request));
280370

281371
group.SetAllStates(cvd::INSTANCE_STATE_STOPPED);
282372
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
283373

284374
if (own_flags_.start) {
285-
CommandRequest start_cmd =
286-
CF_EXPECT(CreateStartCommand(group, subcmd_args, envs));
287-
std::unique_ptr<CvdCommandHandler> start_handler =
288-
NewCvdStartCommandHandler(instance_manager_);
289-
CF_EXPECT(start_handler->Handle(start_cmd));
375+
CF_EXPECT(StartGroup(group, subcmd_args, envs, instance_manager_));
290376

291377
if (CF_EXPECT(IsDefaultGroup(request))) {
292378
// For backward compatibility, we add extra symlink in system wide home
@@ -404,6 +490,7 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
404490
own_flags_.host_path = DefaultHostPath(env);
405491
own_flags_.product_path = DefaultProductPath(env);
406492
own_flags_.start = true;
493+
own_flags_.reuse_if_possible = false;
407494
std::vector<Flag> flags = num_instances_parser_.Flags(selector_options);
408495
flags.emplace_back(
409496
GflagsCompatFlag("host_path", own_flags_.host_path)
@@ -417,6 +504,9 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
417504
"$ANDROID_PRODUCT_OUT, $HOME or the current directory."));
418505
flags.emplace_back(GflagsCompatFlag("start", own_flags_.start)
419506
.Help("Whether to start the instance group."));
507+
flags.emplace_back(
508+
GflagsCompatFlag("reuse_if_possible", own_flags_.reuse_if_possible)
509+
.Help("Reuse an existing instance group if paths match and it is stopped."));
420510
return flags;
421511
}
422512

base/cvd/cuttlefish/host/commands/cvd/cli/commands/create.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ class CvdCreateCommandHandler : public CvdCommandHandler {
4444
std::string product_path;
4545
bool start;
4646
std::string config_file;
47+
bool reuse_if_possible;
4748
};
4849

4950
std::vector<Flag> ConfigFileModeFlags();

base/cvd/cuttlefish/host/commands/cvd/cli/selector/creation_analyzer.cpp

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -82,31 +82,23 @@ Result<GroupCreationInfo> AnalyzeCreation(const CreationAnalyzerParam& params) {
8282
.home = CF_EXPECT(HomeFromEnvironment(params.envs)),
8383
.host_artifacts_path = CF_EXPECT(AndroidHostPath(params.envs)),
8484
};
85-
group_directories.product_out_paths.reserve(params.num_instances);
86-
auto it = params.envs.find(kAndroidProductOut);
85+
std::string product_out_val;
86+
const auto it = params.envs.find(kAndroidProductOut);
8787
if (it != params.envs.end()) {
88-
std::vector<std::string_view> env_product_out =
89-
absl::StrSplit(it->second, ',');
88+
product_out_val = it->second;
89+
const std::vector<std::string_view> env_product_out =
90+
absl::StrSplit(product_out_val, ',');
9091
if (env_product_out.size() > params.num_instances) {
9192
LOG(WARNING) << env_product_out.size()
9293
<< " product paths provided, but only "
9394
<< params.num_instances << " are going to be created";
94-
env_product_out.resize(params.num_instances);
95-
}
96-
for (auto& env_path : env_product_out) {
97-
group_directories.product_out_paths.emplace_back(env_path);
9895
}
9996
} else {
100-
group_directories.product_out_paths.emplace_back(
101-
group_directories.host_artifacts_path);
102-
}
103-
while (group_directories.product_out_paths.size() < params.num_instances) {
104-
// Use the first product path when more instances are required than product
105-
// paths provided. This supports creating multiple identical instances from
106-
// a single set of images.
107-
group_directories.product_out_paths.emplace_back(
108-
group_directories.product_out_paths[0]);
97+
product_out_val = group_directories.host_artifacts_path.value_or("");
10998
}
99+
const std::vector<std::string> expanded =
100+
ExpandProductPaths(product_out_val, params.num_instances);
101+
group_directories.product_out_paths.assign(expanded.begin(), expanded.end());
110102

111103
return GroupCreationInfo{
112104
.group_creation_params = group_params,

base/cvd/cuttlefish/host/commands/cvd/utils/common.cpp

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,9 @@
2222
#include <unordered_map>
2323

2424
#include <android-base/file.h>
25-
#include "absl/strings/strip.h"
2625
#include "absl/strings/str_format.h"
26+
#include "absl/strings/str_split.h"
27+
#include "absl/strings/strip.h"
2728

2829
#include "cuttlefish/common/libs/utils/files.h"
2930
#include "cuttlefish/host/libs/config/config_utils.h"
@@ -146,4 +147,18 @@ std::string AssemblyDirFromHome(const std::string& group_home_dir) {
146147
return to_ret;
147148
}
148149

150+
std::vector<std::string> ExpandProductPaths(const std::string& product_path,
151+
size_t num_instances) {
152+
const std::vector<std::string_view> split = absl::StrSplit(product_path, ',');
153+
std::vector<std::string> expanded;
154+
expanded.reserve(num_instances);
155+
for (size_t i = 0; i < std::min(split.size(), num_instances); ++i) {
156+
expanded.push_back(std::string(split[i]));
157+
}
158+
while (expanded.size() < num_instances) {
159+
expanded.push_back(expanded[0]);
160+
}
161+
return expanded;
162+
}
163+
149164
} // namespace cuttlefish

base/cvd/cuttlefish/host/commands/cvd/utils/common.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,7 @@ std::string AssemblyDirFromHome(const std::string& group_home_dir);
5656
// variables followed by the current directory.
5757
Result<std::string> AndroidHostPath(const cvd_common::Envs& env);
5858

59+
std::vector<std::string> ExpandProductPaths(const std::string& product_path,
60+
size_t num_instances);
61+
5962
} // namespace cuttlefish

0 commit comments

Comments
 (0)