Skip to content

Commit 920a0f5

Browse files
committed
cvd: Add --reuse flag to cvd create
Adds `--reuse` 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 920a0f5

10 files changed

Lines changed: 162 additions & 58 deletions

File tree

base/cvd/cuttlefish/host/commands/cvd/cli/BUILD.bazel

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ cf_cc_library(
118118
"//cuttlefish/host/commands/cvd/cli:command_request",
119119
"//cuttlefish/host/commands/cvd/cli:help_format",
120120
"//cuttlefish/host/commands/cvd/cli:types",
121+
"//cuttlefish/host/commands/cvd/cli:utils",
121122
"//cuttlefish/host/commands/cvd/cli/commands:bugreport",
122123
"//cuttlefish/host/commands/cvd/cli/commands:cache",
123124
"//cuttlefish/host/commands/cvd/cli/commands:clear",
@@ -145,6 +146,7 @@ cf_cc_library(
145146
"//cuttlefish/host/commands/cvd/cli/commands/monitor:command_handler",
146147
"//cuttlefish/host/commands/cvd/cli/parser:load_config_cc_proto",
147148
"//cuttlefish/host/commands/cvd/cli/parser:load_configs_parser",
149+
"//cuttlefish/host/commands/cvd/cli/selector",
148150
"//cuttlefish/host/commands/cvd/cli/selector:creation_analyzer",
149151
"//cuttlefish/host/commands/cvd/cli/selector:num_instances_parser",
150152
"//cuttlefish/host/commands/cvd/cli/selector:selector_common_parser",

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

Lines changed: 105 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
#include "cuttlefish/host/commands/cvd/cli/commands/create.h"
1818

1919
#include <errno.h>
20+
#include <fmt/core.h>
21+
#include <fmt/format.h>
2022
#include <stddef.h>
2123

2224
#include <algorithm>
@@ -25,23 +27,23 @@
2527
#include <cstring>
2628
#include <limits>
2729
#include <memory>
30+
#include <optional>
2831
#include <sstream>
2932
#include <string>
3033
#include <utility>
3134
#include <vector>
3235

33-
#include <fmt/core.h>
34-
#include <fmt/format.h>
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"
4143
#include "cuttlefish/common/libs/utils/files.h"
44+
#include "cuttlefish/common/libs/utils/users.h"
4245
#include "cuttlefish/flag_parser/flag.h"
4346
#include "cuttlefish/flag_parser/gflags_compat.h"
44-
#include "cuttlefish/common/libs/utils/users.h"
4547
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
4648
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
4749
#include "cuttlefish/host/commands/cvd/cli/commands/host_tool_target.h"
@@ -50,9 +52,11 @@
5052
#include "cuttlefish/host/commands/cvd/cli/help_format.h"
5153
#include "cuttlefish/host/commands/cvd/cli/selector/creation_analyzer.h"
5254
#include "cuttlefish/host/commands/cvd/cli/selector/num_instances_parser.h"
55+
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
5356
#include "cuttlefish/host/commands/cvd/cli/selector/selector_common_parser.h"
5457
#include "cuttlefish/host/commands/cvd/cli/selector/selector_constants.h"
5558
#include "cuttlefish/host/commands/cvd/cli/types.h"
59+
#include "cuttlefish/host/commands/cvd/cli/utils.h"
5660
#include "cuttlefish/host/commands/cvd/instances/cvd_persistent_data.pb.h"
5761
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
5862
#include "cuttlefish/host/commands/cvd/instances/local_instance_group.h"
@@ -70,8 +74,6 @@ using selector::GroupCreationInfo;
7074
constexpr char kSummaryHelpText[] =
7175
"Create a Cuttlefish instance group";
7276

73-
74-
7577
std::string DefaultHostPath(const cvd_common::Envs& envs) {
7678
for (const auto& key : {kAndroidHostOut, kAndroidSoongHostOut, "HOME"}) {
7779
auto it = envs.find(key);
@@ -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,12 +250,73 @@ std::vector<Flag> BuildSelectorFlagsForCreateHelp(
236250
};
237251
}
238252

253+
bool IsDeviceRunning(const LocalInstanceGroup& group) {
254+
return std::ranges::any_of(group.Instances(), &LocalInstance::IsActive);
255+
}
256+
257+
Result<bool> MatchPaths(const LocalInstanceGroup& group, const std::string& target_host,
258+
const std::vector<std::string>& target_products) {
259+
const std::string real_group_host = CF_EXPECT(RealPath(group.HostArtifactsPath()));
260+
const std::string real_target_host = CF_EXPECT(RealPath(target_host));
261+
262+
if (real_group_host != real_target_host) {
263+
return false;
264+
}
265+
266+
const std::vector<std::string> group_products =
267+
absl::StrSplit(group.ProductOutPath(), ',');
268+
269+
std::vector<std::string> real_group_products;
270+
for (const auto& path : group_products) {
271+
real_group_products.push_back(CF_EXPECT(RealPath(path)));
272+
}
273+
274+
std::vector<std::string> real_target_products;
275+
for (const auto& path : target_products) {
276+
real_target_products.push_back(CF_EXPECT(RealPath(path)));
277+
}
278+
279+
return real_group_products == real_target_products;
280+
}
281+
239282
} // namespace
240283

241284
CvdCreateCommandHandler::CvdCreateCommandHandler(
242285
InstanceManager& instance_manager)
243286
: instance_manager_(instance_manager) {}
244287

288+
Result<std::optional<LocalInstanceGroup>>
289+
CvdCreateCommandHandler::TryReuseGroup(
290+
const selector::SelectorOptions& selectors, const cvd_common::Envs& envs) {
291+
InstanceDatabase::Filter filter =
292+
selector::BuildFilterFromSelectors(selectors);
293+
294+
const std::vector<LocalInstanceGroup> groups =
295+
CF_EXPECT(instance_manager_.FindGroups(filter));
296+
if (groups.empty()) {
297+
return std::nullopt;
298+
} else if (groups.size() == 1) {
299+
auto target_host_it = envs.find(kAndroidHostOut);
300+
CF_EXPECT(target_host_it != envs.end());
301+
302+
auto target_product_it = envs.find(kAndroidProductOut);
303+
CF_EXPECT(target_product_it != envs.end());
304+
305+
const std::vector<std::string> target_products = ExpandProductPaths(
306+
target_product_it->second, num_instances_parser_.NumInstances());
307+
bool match = CF_EXPECT(
308+
MatchPaths(groups[0], target_host_it->second, target_products));
309+
CF_EXPECT(std::move(match),
310+
"Trying to change the host paths on an existing group");
311+
CF_EXPECT(!IsDeviceRunning(groups[0]),
312+
"Group is already running, try `cvd stop`");
313+
return groups[0];
314+
} else {
315+
return CF_ERRF("Unclear which of {} groups to match, try `cvd clear`",
316+
groups.size());
317+
}
318+
}
319+
245320
Result<void> CvdCreateCommandHandler::Handle(const CommandRequest& request) {
246321
std::vector<std::string> subcmd_args = request.SubcommandArguments();
247322

@@ -275,23 +350,23 @@ Result<void> CvdCreateCommandHandler::Handle(const CommandRequest& request) {
275350
// CreationAnalyzer needs these to be set in the environment
276351
envs[kAndroidHostOut] = AbsolutePath(own_flags_.host_path);
277352
envs[kAndroidProductOut] = AbsolutePath(own_flags_.product_path);
278-
auto group =
279-
CF_EXPECT(CreateGroup(instance_manager_, subcmd_args, envs, request));
280353

281-
group.SetAllStates(cvd::INSTANCE_STATE_STOPPED);
282-
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
354+
std::optional<LocalInstanceGroup> group;
355+
if (own_flags_.reuse) {
356+
group = CF_EXPECT(TryReuseGroup(request.Selectors(), envs));
357+
}
358+
if (!group.has_value()) {
359+
group = CF_EXPECT(CreateGroup(subcmd_args, envs, request));
360+
}
361+
CF_EXPECT(group.has_value());
283362

284363
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));
364+
CF_EXPECT(StartGroup(*group, subcmd_args, envs, instance_manager_));
290365

291366
if (CF_EXPECT(IsDefaultGroup(request))) {
292367
// For backward compatibility, we add extra symlink in system wide home
293368
// when HOME is NOT overridden and selector flags are NOT given.
294-
auto symlink_res = CreateSymlinks(group);
369+
auto symlink_res = CreateSymlinks(*group);
295370
if (!symlink_res.ok()) {
296371
LOG(ERROR) << "Failed to create symlinks for default group: "
297372
<< symlink_res.error();
@@ -303,7 +378,6 @@ Result<void> CvdCreateCommandHandler::Handle(const CommandRequest& request) {
303378
}
304379

305380
Result<LocalInstanceGroup> CvdCreateCommandHandler::CreateGroup(
306-
InstanceManager& instance_manager,
307381
const std::vector<std::string>& subcmd_args, const cvd_common::Envs& envs,
308382
const CommandRequest& request) {
309383
GroupCreationInfo creation_info = CF_EXPECT(AnalyzeCreation({
@@ -313,15 +387,20 @@ Result<LocalInstanceGroup> CvdCreateCommandHandler::CreateGroup(
313387
.instance_ids = num_instances_parser_.InstanceIds(),
314388
}));
315389

316-
auto groups = CF_EXPECT(instance_manager.FindGroups(
390+
auto groups = CF_EXPECT(instance_manager_.FindGroups(
317391
{.group_name = creation_info.group_creation_params.group_name}));
318392
CF_EXPECTF(groups.empty(), "Group named '{}' already exists",
319393
creation_info.group_creation_params.group_name);
320-
return instance_manager.CreateInstanceGroup(
394+
395+
LocalInstanceGroup group = CF_EXPECT(instance_manager_.CreateInstanceGroup(
321396
std::move(creation_info.group_creation_params),
322-
std::move(creation_info.group_directories));
323-
}
397+
std::move(creation_info.group_directories)));
324398

399+
group.SetAllStates(cvd::INSTANCE_STATE_STOPPED);
400+
CF_EXPECT(instance_manager_.UpdateInstanceGroup(group));
401+
402+
return group;
403+
}
325404

326405
std::string CvdCreateCommandHandler::SummaryHelp() const {
327406
return kSummaryHelpText;
@@ -404,6 +483,7 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
404483
own_flags_.host_path = DefaultHostPath(env);
405484
own_flags_.product_path = DefaultProductPath(env);
406485
own_flags_.start = true;
486+
own_flags_.reuse = false;
407487
std::vector<Flag> flags = num_instances_parser_.Flags(selector_options);
408488
flags.emplace_back(
409489
GflagsCompatFlag("host_path", own_flags_.host_path)
@@ -417,6 +497,11 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
417497
"$ANDROID_PRODUCT_OUT, $HOME or the current directory."));
418498
flags.emplace_back(GflagsCompatFlag("start", own_flags_.start)
419499
.Help("Whether to start the instance group."));
500+
flags.emplace_back(
501+
GflagsCompatFlag("reuse", own_flags_.reuse)
502+
.Help("Whether to attempt reusing an existing group. Will fail if "
503+
"there are multiple matching groups, or the chosen group has a "
504+
"host tools / guest image mismatch."));
420505
return flags;
421506
}
422507

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#pragma once
1818

1919
#include <memory>
20+
#include <optional>
2021
#include <string>
2122
#include <vector>
2223

@@ -44,13 +45,15 @@ class CvdCreateCommandHandler : public CvdCommandHandler {
4445
std::string product_path;
4546
bool start;
4647
std::string config_file;
48+
bool reuse;
4749
};
4850

4951
std::vector<Flag> ConfigFileModeFlags();
5052
std::vector<Flag> FlagModeFlags(const cvd_common::Envs& env,
5153
const selector::SelectorOptions&);
54+
Result<std::optional<LocalInstanceGroup>> TryReuseGroup(
55+
const selector::SelectorOptions& selectors, const cvd_common::Envs& envs);
5256
Result<LocalInstanceGroup> CreateGroup(
53-
InstanceManager& instance_manager,
5457
const std::vector<std::string>& subcmd_args, const cvd_common::Envs& envs,
5558
const CommandRequest& request);
5659

base/cvd/cuttlefish/host/commands/cvd/cli/selector/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ cf_cc_library(
1212
"//cuttlefish/common/libs/utils:contains",
1313
"//cuttlefish/common/libs/utils:files",
1414
"//cuttlefish/common/libs/utils:users",
15+
"//cuttlefish/host/commands/cvd/cli:utils",
1516
"//cuttlefish/host/commands/cvd/cli/selector:selector_common_parser",
1617
"//cuttlefish/host/commands/cvd/instances",
1718
"//cuttlefish/host/commands/cvd/instances:cvd_persistent_data",

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

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
#include "cuttlefish/common/libs/utils/files.h"
3131
#include "cuttlefish/common/libs/utils/users.h"
32+
#include "cuttlefish/host/commands/cvd/cli/utils.h"
3233
#include "cuttlefish/host/commands/cvd/utils/common.h"
3334

3435
namespace cuttlefish {
@@ -82,31 +83,23 @@ Result<GroupCreationInfo> AnalyzeCreation(const CreationAnalyzerParam& params) {
8283
.home = CF_EXPECT(HomeFromEnvironment(params.envs)),
8384
.host_artifacts_path = CF_EXPECT(AndroidHostPath(params.envs)),
8485
};
85-
group_directories.product_out_paths.reserve(params.num_instances);
86-
auto it = params.envs.find(kAndroidProductOut);
86+
std::string product_out_val;
87+
const auto it = params.envs.find(kAndroidProductOut);
8788
if (it != params.envs.end()) {
88-
std::vector<std::string_view> env_product_out =
89-
absl::StrSplit(it->second, ',');
89+
product_out_val = it->second;
90+
const std::vector<std::string_view> env_product_out =
91+
absl::StrSplit(product_out_val, ',');
9092
if (env_product_out.size() > params.num_instances) {
9193
LOG(WARNING) << env_product_out.size()
9294
<< " product paths provided, but only "
9395
<< 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);
9896
}
9997
} 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]);
98+
product_out_val = group_directories.host_artifacts_path.value_or("");
10999
}
100+
const std::vector<std::string> expanded =
101+
ExpandProductPaths(product_out_val, params.num_instances);
102+
group_directories.product_out_paths.assign(expanded.begin(), expanded.end());
110103

111104
return GroupCreationInfo{
112105
.group_creation_params = group_params,

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

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,20 +42,6 @@ enum class DisplayBehavior {
4242
LabelInstance,
4343
};
4444

45-
Result<InstanceDatabase::Filter> BuildFilterFromSelectors(
46-
const SelectorOptions& selectors) {
47-
InstanceDatabase::Filter filter;
48-
filter.group_name = selectors.group_name;
49-
if (selectors.instance_names) {
50-
const std::vector<std::string> per_instance_names =
51-
selectors.instance_names.value();
52-
for (const auto& per_instance_name : per_instance_names) {
53-
filter.instance_names.insert(per_instance_name);
54-
}
55-
}
56-
return filter;
57-
}
58-
5945
std::string GroupDisplay(const std::vector<LocalInstanceGroup>& groups,
6046
const DisplayBehavior behavior) {
6147
std::stringstream result;
@@ -150,10 +136,24 @@ Result<std::pair<LocalInstance, LocalInstanceGroup>> PromptUserForInstance(
150136

151137
} // namespace
152138

139+
InstanceDatabase::Filter BuildFilterFromSelectors(
140+
const SelectorOptions& selectors) {
141+
InstanceDatabase::Filter filter;
142+
filter.group_name = selectors.group_name;
143+
if (selectors.instance_names) {
144+
const std::vector<std::string> per_instance_names =
145+
selectors.instance_names.value();
146+
for (const auto& per_instance_name : per_instance_names) {
147+
filter.instance_names.insert(per_instance_name);
148+
}
149+
}
150+
return filter;
151+
}
152+
153153
Result<LocalInstanceGroup> SelectGroup(const InstanceManager& instance_manager,
154154
const CommandRequest& request) {
155155
const InstanceDatabase::Filter filter =
156-
CF_EXPECT(BuildFilterFromSelectors(request.Selectors()));
156+
BuildFilterFromSelectors(request.Selectors());
157157
std::vector<LocalInstanceGroup> groups =
158158
CF_EXPECT(instance_manager.FindGroups(filter));
159159
CF_EXPECT(!groups.empty(), "No instance groups available");
@@ -169,7 +169,7 @@ Result<LocalInstanceGroup> SelectGroup(const InstanceManager& instance_manager,
169169
Result<std::pair<LocalInstance, LocalInstanceGroup>> SelectInstance(
170170
const InstanceManager& instance_manager, const CommandRequest& request) {
171171
const InstanceDatabase::Filter filter =
172-
CF_EXPECT(BuildFilterFromSelectors(request.Selectors()));
172+
BuildFilterFromSelectors(request.Selectors());
173173
std::vector<std::pair<LocalInstanceGroup, std::vector<LocalInstance>>>
174174
found_instances = CF_EXPECT(instance_manager.FindInstances(filter));
175175
CF_EXPECT(!found_instances.empty(), "No instances available");

0 commit comments

Comments
 (0)