blob: e81d107581734ef4e1050f7dc3ddd1590efc26f2 [file] [log] [blame]
//
// 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); }