350 lines
10 KiB
C++
350 lines
10 KiB
C++
//
|
|
// Copyright (C) 2022 The Android Open Source Project
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
#include <iostream>
|
|
|
|
#include <android-base/logging.h>
|
|
#include <gflags/gflags.h>
|
|
|
|
#include "host/commands/remote/actions.h"
|
|
#include "host/commands/remote/output.h"
|
|
#include "host/commands/remote/remote.h"
|
|
#include "host/libs/web/http_client/sso_client.h"
|
|
|
|
const std::string kUsage = R"(
|
|
NAME
|
|
cvdremote - manage Cuttlefish Virtual Devices (CVDs) in the cloud.
|
|
|
|
SYNOPSIS
|
|
cvdremote --service_url=<url> --zone=<zone> [<resource>] <command> [<args>]
|
|
|
|
RESOURCES
|
|
cvd (default)
|
|
Cuttlefish Virtual Devices.
|
|
|
|
host
|
|
Host machines where CVDs live.
|
|
|
|
COMMANDS
|
|
create
|
|
Create a resource.
|
|
|
|
list
|
|
List the resources.
|
|
)";
|
|
|
|
// General flags.
|
|
DEFINE_string(service_url, "", "Cloud orchestration service url.");
|
|
DEFINE_string(zone, "us-central1-b", "Cloud zone.");
|
|
DEFINE_bool(verbose, false,
|
|
"Indicates whether to print a verbose output or not.");
|
|
DEFINE_bool(use_sso_client, false,
|
|
"Communicates with cloud orchestration using sso_client_binary");
|
|
|
|
// Flags specifics to host resource commands.
|
|
DEFINE_string(machine_type, "zones/us-central1-b/machineTypes/n1-standard-4",
|
|
"Full or partial URL of the machine type resource.");
|
|
DEFINE_string(min_cpu_platform, "Intel Haswell",
|
|
"Specifies a minimum CPU platform for the VM instance.");
|
|
|
|
// Flags specifics to cvd resource commands.
|
|
DEFINE_string(host, "", "If empty, cvds from all hosts will be printed out.");
|
|
DEFINE_string(build_id, "", "Android build identifier.");
|
|
DEFINE_string(target, "aosp_cf_x86_64_phone-userdebug",
|
|
"Android build target.");
|
|
|
|
namespace cuttlefish {
|
|
namespace {
|
|
|
|
//
|
|
// Create host.
|
|
//
|
|
int CommandCreateHostMain(const std::vector<std::string>&) {
|
|
auto http_client =
|
|
FLAGS_use_sso_client
|
|
? std::unique_ptr<HttpClient>(new http_client::SsoClient())
|
|
: HttpClient::CurlClient();
|
|
CloudOrchestratorApi api(FLAGS_service_url, FLAGS_zone, *http_client);
|
|
GCPInstance gcp;
|
|
gcp.machine_type = FLAGS_machine_type.c_str();
|
|
gcp.min_cpu_platform = FLAGS_min_cpu_platform.c_str();
|
|
CreateHostInstanceRequest request;
|
|
request.gcp = &gcp;
|
|
auto action = CreateHostAction(api, request);
|
|
auto result = action->Execute();
|
|
if (!result.ok()) {
|
|
std::cerr << result.error().Message();
|
|
return -1;
|
|
}
|
|
std::cout << *result << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// List hosts.
|
|
//
|
|
int CommandListHostsMain(const std::vector<std::string>&) {
|
|
auto http_client =
|
|
FLAGS_use_sso_client
|
|
? std::unique_ptr<HttpClient>(new http_client::SsoClient())
|
|
: HttpClient::CurlClient();
|
|
CloudOrchestratorApi api(FLAGS_service_url, FLAGS_zone, *http_client);
|
|
auto hosts = api.ListHosts();
|
|
if (!hosts.ok()) {
|
|
std::cerr << hosts.error().Message();
|
|
LOG(DEBUG) << hosts.error().Trace();
|
|
return -1;
|
|
}
|
|
if ((*hosts).empty()) {
|
|
std::cerr << "~ No hosts found ~" << std::endl;
|
|
return 0;
|
|
}
|
|
for (const std::string& host : *hosts) {
|
|
std::cout << host << std::endl;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
//
|
|
// Delete host.
|
|
//
|
|
int CommandDeleteHostMain(const std::vector<std::string>& args) {
|
|
if (args.empty()) {
|
|
std::cerr << "Missing host name." << std::endl;
|
|
return -1;
|
|
}
|
|
auto http_client =
|
|
FLAGS_use_sso_client
|
|
? std::unique_ptr<HttpClient>(new http_client::SsoClient())
|
|
: HttpClient::CurlClient();
|
|
CloudOrchestratorApi api(FLAGS_service_url, FLAGS_zone, *http_client);
|
|
auto action = DeleteHostsAction(api, args);
|
|
auto action_result = action->Execute();
|
|
if (!action_result.ok()) {
|
|
std::cerr << action_result.error().Message();
|
|
return -1;
|
|
}
|
|
bool any_del_had_error = false;
|
|
for (auto& del_instance_result : *action_result) {
|
|
if (!del_instance_result.ok()) {
|
|
std::cerr << del_instance_result.error().Message() << std::endl
|
|
<< std::endl;
|
|
any_del_had_error = true;
|
|
}
|
|
}
|
|
if (any_del_had_error) {
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void PrintCVDs(const std::string& host, const std::vector<std::string>& cvds) {
|
|
for (const std::string& cvd : cvds) {
|
|
CVDOutput o{
|
|
service_url : FLAGS_service_url,
|
|
zone : FLAGS_zone,
|
|
host : host,
|
|
verbose : FLAGS_verbose,
|
|
name : cvd
|
|
};
|
|
std::cout << o.ToString() << std::endl;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Create cvd.
|
|
//
|
|
int CommandCreateCVDMain(const std::vector<std::string>&) {
|
|
if (FLAGS_build_id == "") {
|
|
std::cerr << "Missing --build_id flag.";
|
|
return -1;
|
|
}
|
|
auto http_client =
|
|
FLAGS_use_sso_client
|
|
? std::unique_ptr<HttpClient>(new http_client::SsoClient())
|
|
: HttpClient::CurlClient();
|
|
auto retrying_http_client = HttpClient::ServerErrorRetryClient(
|
|
*http_client, 5 /* retry_attempts */,
|
|
std::chrono::milliseconds(5000) /* retry_delay */);
|
|
CloudOrchestratorApi api(FLAGS_service_url, FLAGS_zone,
|
|
*retrying_http_client);
|
|
std::string host = FLAGS_host;
|
|
if (host == "") {
|
|
GCPInstance gcp;
|
|
gcp.machine_type = FLAGS_machine_type.c_str();
|
|
gcp.min_cpu_platform = FLAGS_min_cpu_platform.c_str();
|
|
CreateHostInstanceRequest request;
|
|
request.gcp = &gcp;
|
|
auto action = CreateHostAction(api, request);
|
|
auto result = action->Execute();
|
|
if (!result.ok()) {
|
|
std::cerr << result.error().Message();
|
|
return -1;
|
|
}
|
|
host = *result;
|
|
}
|
|
CreateCVDRequest request{
|
|
build_info : BuildInfo{
|
|
build_id : FLAGS_build_id,
|
|
target : FLAGS_target,
|
|
},
|
|
};
|
|
auto action = CreateCVDAction(api, request, host);
|
|
auto result = action->Execute();
|
|
if (!result.ok()) {
|
|
std::cerr << result.error().Message();
|
|
return -1;
|
|
}
|
|
std::cout << *result << std::endl;
|
|
return 0;
|
|
}
|
|
|
|
// List cvds.
|
|
//
|
|
// Non-verbose output:
|
|
// Format: "[INSTANCE_NAME] ([HOST_IDENTIFIER])"
|
|
// Example:
|
|
// cvd-1 (cf-ec559de7-6621-4ace-a8be-0f480a6f9498)
|
|
// cvd-2 (cf-ec559de7-6621-4ace-a8be-0f480a6f9498)
|
|
// cvd-3 (cf-ec559de7-6621-4ace-a8be-0f480a6f9498)
|
|
// cvd-1 (cf-e4b0b61d-21c4-497e-8045-bd48c37e487e)
|
|
// cvd-1 (cf-b3aa26b2-1312-4241-989f-b80f92d6d9ae)
|
|
//
|
|
// Verbose output:
|
|
// Format:
|
|
// ```
|
|
// [INSTANCE_NAME] ([HOST_IDENTIFIER])
|
|
// [KEY_1]: [VALUE_1]
|
|
// [KEY_2]: [VALUE_3]
|
|
// ...
|
|
// [KEY_N]: [VALUE_N]
|
|
//
|
|
// ```
|
|
// Example:
|
|
// [1] cvd-1 (cf-ec559de7-6621-4ace-a8be-0f480a6f9498)
|
|
// create time: 2018-10-25T06:32:08.182-07:00
|
|
// display: 1080x1920 (240)
|
|
// webrtcstream_url: https://foo.com/.../client.html
|
|
//
|
|
// [1] cvd-2 (cf-ec559de7-6621-4ace-a8be-0f480a6f9498)
|
|
// create time: 2018-10-25T06:32:08.182-07:00
|
|
// display: 1080x1920 (240)
|
|
// webrtcstream_url: https://foo.com/.../client.html
|
|
|
|
int CommandListCVDsMain(const std::vector<std::string>&) {
|
|
auto http_client =
|
|
FLAGS_use_sso_client
|
|
? std::unique_ptr<HttpClient>(new http_client::SsoClient())
|
|
: HttpClient::CurlClient();
|
|
CloudOrchestratorApi api(FLAGS_service_url, FLAGS_zone, *http_client);
|
|
// TODO(b/248087309): Implements list cvds with multiple hosts asynchronously.
|
|
if (FLAGS_host == "") {
|
|
auto hosts = api.ListHosts();
|
|
if (!hosts.ok()) {
|
|
std::cerr << hosts.error().Message();
|
|
LOG(DEBUG) << hosts.error().Trace();
|
|
return -1;
|
|
}
|
|
if ((*hosts).empty()) {
|
|
std::cerr << "~ No cvds found ~" << std::endl;
|
|
return 0;
|
|
}
|
|
for (const std::string& host : *hosts) {
|
|
auto cvd_streams = api.ListCVDWebRTCStreams(host);
|
|
if (!cvd_streams.ok()) {
|
|
continue;
|
|
}
|
|
PrintCVDs(host, *cvd_streams);
|
|
}
|
|
} else {
|
|
auto cvd_streams = api.ListCVDWebRTCStreams(FLAGS_host);
|
|
if (!cvd_streams.ok()) {
|
|
std::cerr << cvd_streams.error().Message();
|
|
LOG(DEBUG) << cvd_streams.error().Trace();
|
|
return -1;
|
|
}
|
|
PrintCVDs(FLAGS_host, *cvd_streams);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
constexpr char kResourceHost[] = "host";
|
|
constexpr char kResourceCVD[] = "cvd";
|
|
|
|
constexpr char kCommandList[] = "list";
|
|
constexpr char kCommandCreate[] = "create";
|
|
constexpr char kCommandDelete[] = "delete";
|
|
|
|
std::map<
|
|
std::string,
|
|
std::map<std::string, std::function<int(const std::vector<std::string>&)>>>
|
|
commands_map = {
|
|
{kResourceHost,
|
|
{
|
|
{kCommandCreate, CommandCreateHostMain},
|
|
{kCommandList, CommandListHostsMain},
|
|
{kCommandDelete, CommandDeleteHostMain},
|
|
}},
|
|
{kResourceCVD,
|
|
{
|
|
{kCommandCreate, CommandCreateCVDMain},
|
|
{kCommandList, CommandListCVDsMain},
|
|
}},
|
|
};
|
|
|
|
int Main(int argc, char** argv) {
|
|
::gflags::SetUsageMessage(kUsage);
|
|
::gflags::ParseCommandLineFlags(&argc, &argv, true);
|
|
::android::base::InitLogging(argv, android::base::StderrLogger);
|
|
if (FLAGS_service_url == "") {
|
|
std::cerr << "Missing service_url flag";
|
|
return -1;
|
|
}
|
|
std::vector<std::string> args;
|
|
for (int i = 1; i < argc; i++) {
|
|
args.push_back(argv[i]);
|
|
}
|
|
if (args.empty()) {
|
|
std::cerr << "Missing command";
|
|
return -1;
|
|
}
|
|
if (args.size() == 1) {
|
|
args.insert(args.begin(), kResourceCVD);
|
|
}
|
|
std::string resource = args[0];
|
|
args.erase(args.begin());
|
|
if (commands_map.find(resource) == commands_map.end()) {
|
|
std::cerr << "Invalid resource"
|
|
<< " \"" << resource << "\".";
|
|
return -1;
|
|
}
|
|
std::string command = args[0];
|
|
args.erase(args.begin());
|
|
const auto& res_commands_map = commands_map[resource];
|
|
if (res_commands_map.find(command) == res_commands_map.end()) {
|
|
std::cerr << "Invalid command"
|
|
<< " \"" << command << "\" "
|
|
<< "for"
|
|
<< " \"" << resource << "\" resource.";
|
|
return -1;
|
|
}
|
|
return commands_map[resource][command](args);
|
|
}
|
|
|
|
} // namespace
|
|
} // namespace cuttlefish
|
|
|
|
int main(int argc, char** argv) { return cuttlefish::Main(argc, argv); }
|