// // 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 #include #include #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= --zone= [] [] 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&) { auto http_client = FLAGS_use_sso_client ? std::unique_ptr(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&) { auto http_client = FLAGS_use_sso_client ? std::unique_ptr(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& args) { if (args.empty()) { std::cerr << "Missing host name." << std::endl; return -1; } auto http_client = FLAGS_use_sso_client ? std::unique_ptr(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& 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&) { if (FLAGS_build_id == "") { std::cerr << "Missing --build_id flag."; return -1; } auto http_client = FLAGS_use_sso_client ? std::unique_ptr(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&) { auto http_client = FLAGS_use_sso_client ? std::unique_ptr(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&)>>> 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 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); }