Skip to content

Commit 9c2e449

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 9c2e449

10 files changed

Lines changed: 164 additions & 59 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
@@ -149,6 +149,8 @@ cf_cc_library(
149149
"//cuttlefish/host/commands/cvd/cli/selector:num_instances_parser",
150150
"//cuttlefish/host/commands/cvd/cli/selector:selector_common_parser",
151151
"//cuttlefish/host/commands/cvd/cli/selector:selector_constants",
152+
"//cuttlefish/host/commands/cvd/cli/selector",
153+
"//cuttlefish/host/commands/cvd/cli:utils",
152154
"//cuttlefish/host/commands/cvd/fetch:fetch_cvd",
153155
"//cuttlefish/host/commands/cvd/instances",
154156
"//cuttlefish/host/commands/cvd/instances:cvd_persistent_data",

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

Lines changed: 106 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,24 @@
2527
#include <cstring>
2628
#include <limits>
2729
#include <memory>
30+
#include <optional>
31+
#include <ranges>
2832
#include <sstream>
2933
#include <string>
3034
#include <utility>
3135
#include <vector>
3236

33-
#include <fmt/core.h>
34-
#include <fmt/format.h>
3537
#include "absl/log/log.h"
3638
#include "absl/strings/match.h"
3739
#include "absl/strings/str_join.h"
40+
#include "absl/strings/str_split.h"
3841

3942
#include "cuttlefish/common/libs/utils/contains.h"
4043
#include "cuttlefish/common/libs/utils/environment.h"
4144
#include "cuttlefish/common/libs/utils/files.h"
45+
#include "cuttlefish/common/libs/utils/users.h"
4246
#include "cuttlefish/flag_parser/flag.h"
4347
#include "cuttlefish/flag_parser/gflags_compat.h"
44-
#include "cuttlefish/common/libs/utils/users.h"
4548
#include "cuttlefish/host/commands/cvd/cli/command_request.h"
4649
#include "cuttlefish/host/commands/cvd/cli/commands/command_handler.h"
4750
#include "cuttlefish/host/commands/cvd/cli/commands/host_tool_target.h"
@@ -50,9 +53,11 @@
5053
#include "cuttlefish/host/commands/cvd/cli/help_format.h"
5154
#include "cuttlefish/host/commands/cvd/cli/selector/creation_analyzer.h"
5255
#include "cuttlefish/host/commands/cvd/cli/selector/num_instances_parser.h"
56+
#include "cuttlefish/host/commands/cvd/cli/selector/selector.h"
5357
#include "cuttlefish/host/commands/cvd/cli/selector/selector_common_parser.h"
5458
#include "cuttlefish/host/commands/cvd/cli/selector/selector_constants.h"
5559
#include "cuttlefish/host/commands/cvd/cli/types.h"
60+
#include "cuttlefish/host/commands/cvd/cli/utils.h"
5661
#include "cuttlefish/host/commands/cvd/instances/cvd_persistent_data.pb.h"
5762
#include "cuttlefish/host/commands/cvd/instances/instance_manager.h"
5863
#include "cuttlefish/host/commands/cvd/instances/local_instance_group.h"
@@ -70,8 +75,6 @@ using selector::GroupCreationInfo;
7075
constexpr char kSummaryHelpText[] =
7176
"Create a Cuttlefish instance group";
7277

73-
74-
7578
std::string DefaultHostPath(const cvd_common::Envs& envs) {
7679
for (const auto& key : {kAndroidHostOut, kAndroidSoongHostOut, "HOME"}) {
7780
auto it = envs.find(key);
@@ -118,6 +121,18 @@ Result<CommandRequest> CreateStartCommand(const LocalInstanceGroup& group,
118121
.Build());
119122
}
120123

124+
Result<void> StartGroup(const LocalInstanceGroup& group,
125+
const std::vector<std::string>& subcmd_args,
126+
const cvd_common::Envs& envs,
127+
InstanceManager& instance_manager) {
128+
const CommandRequest start_cmd =
129+
CF_EXPECT(CreateStartCommand(group, subcmd_args, envs));
130+
std::unique_ptr<CvdCommandHandler> start_handler =
131+
NewCvdStartCommandHandler(instance_manager);
132+
CF_EXPECT(start_handler->Handle(start_cmd));
133+
return {};
134+
}
135+
121136
Result<cvd_common::Envs> GetEnvs(const CommandRequest& request) {
122137
cvd_common::Envs envs = request.Env();
123138
if (auto it = envs.find("HOME"); it != envs.end() && it->second.empty()) {
@@ -236,12 +251,73 @@ std::vector<Flag> BuildSelectorFlagsForCreateHelp(
236251
};
237252
}
238253

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

241285
CvdCreateCommandHandler::CvdCreateCommandHandler(
242286
InstanceManager& instance_manager)
243287
: instance_manager_(instance_manager) {}
244288

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

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

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

284364
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));
365+
CF_EXPECT(StartGroup(*group, subcmd_args, envs, instance_manager_));
290366

291367
if (CF_EXPECT(IsDefaultGroup(request))) {
292368
// For backward compatibility, we add extra symlink in system wide home
293369
// when HOME is NOT overridden and selector flags are NOT given.
294-
auto symlink_res = CreateSymlinks(group);
370+
auto symlink_res = CreateSymlinks(*group);
295371
if (!symlink_res.ok()) {
296372
LOG(ERROR) << "Failed to create symlinks for default group: "
297373
<< symlink_res.error();
@@ -303,7 +379,6 @@ Result<void> CvdCreateCommandHandler::Handle(const CommandRequest& request) {
303379
}
304380

305381
Result<LocalInstanceGroup> CvdCreateCommandHandler::CreateGroup(
306-
InstanceManager& instance_manager,
307382
const std::vector<std::string>& subcmd_args, const cvd_common::Envs& envs,
308383
const CommandRequest& request) {
309384
GroupCreationInfo creation_info = CF_EXPECT(AnalyzeCreation({
@@ -313,15 +388,20 @@ Result<LocalInstanceGroup> CvdCreateCommandHandler::CreateGroup(
313388
.instance_ids = num_instances_parser_.InstanceIds(),
314389
}));
315390

316-
auto groups = CF_EXPECT(instance_manager.FindGroups(
391+
auto groups = CF_EXPECT(instance_manager_.FindGroups(
317392
{.group_name = creation_info.group_creation_params.group_name}));
318393
CF_EXPECTF(groups.empty(), "Group named '{}' already exists",
319394
creation_info.group_creation_params.group_name);
320-
return instance_manager.CreateInstanceGroup(
395+
396+
LocalInstanceGroup group = CF_EXPECT(instance_manager_.CreateInstanceGroup(
321397
std::move(creation_info.group_creation_params),
322-
std::move(creation_info.group_directories));
323-
}
398+
std::move(creation_info.group_directories)));
324399

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

326406
std::string CvdCreateCommandHandler::SummaryHelp() const {
327407
return kSummaryHelpText;
@@ -404,6 +484,7 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
404484
own_flags_.host_path = DefaultHostPath(env);
405485
own_flags_.product_path = DefaultProductPath(env);
406486
own_flags_.start = true;
487+
own_flags_.reuse = false;
407488
std::vector<Flag> flags = num_instances_parser_.Flags(selector_options);
408489
flags.emplace_back(
409490
GflagsCompatFlag("host_path", own_flags_.host_path)
@@ -417,6 +498,11 @@ std::vector<Flag> CvdCreateCommandHandler::FlagModeFlags(
417498
"$ANDROID_PRODUCT_OUT, $HOME or the current directory."));
418499
flags.emplace_back(GflagsCompatFlag("start", own_flags_.start)
419500
.Help("Whether to start the instance group."));
501+
flags.emplace_back(
502+
GflagsCompatFlag("reuse", own_flags_.reuse)
503+
.Help("Whether to attempt reusing an existing group. Will fail if "
504+
"there are multiple matching groups, or the chosen group has a "
505+
"host tools / guest image mismatch."));
420506
return flags;
421507
}
422508

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: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ cf_cc_library(
1313
"//cuttlefish/common/libs/utils:files",
1414
"//cuttlefish/common/libs/utils:users",
1515
"//cuttlefish/host/commands/cvd/cli/selector:selector_common_parser",
16+
"//cuttlefish/host/commands/cvd/cli:utils",
1617
"//cuttlefish/host/commands/cvd/instances",
18+
"//cuttlefish/host/commands/cvd/instances/lock",
1719
"//cuttlefish/host/commands/cvd/instances:cvd_persistent_data",
1820
"//cuttlefish/host/commands/cvd/instances:instance_manager",
19-
"//cuttlefish/host/commands/cvd/instances/lock",
2021
"//cuttlefish/host/commands/cvd/utils:common",
2122
"//cuttlefish/result",
2223
"//libbase",

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");

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
namespace cuttlefish {
2828
namespace selector {
2929

30+
InstanceDatabase::Filter BuildFilterFromSelectors(
31+
const SelectorOptions& selectors);
32+
3033
// Selects a single group based on the request's selector options. Asks the user
3134
// to manually choose a single group if multiple groups match the selector
3235
// options and stdin is a terminal.

0 commit comments

Comments
 (0)