blob: ac14580e1a57e00bf2da2763c5892dd3a2bfdb29 [file] [log] [blame] [edit]
/*
* 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 <filesystem>
#include <unordered_map>
#include <android-base/logging.h>
#include <android-base/strings.h>
#include <gflags/gflags.h>
#include <grpcpp/ext/proto_server_reflection_plugin.h>
#include <grpcpp/grpcpp.h>
#include <test/cpp/util/grpc_tool.h>
#include <test/cpp/util/test_config.h>
#include "common/libs/utils/contains.h"
#include "common/libs/utils/result.h"
#include "host/libs/config/cuttlefish_config.h"
using grpc::InsecureChannelCredentials;
namespace cuttlefish {
namespace {
constexpr char kCvdEnvHelpMessage[] =
"cvd env: cuttlefish environment controller\n"
"Basic usage: cvd [selector options] env [sub_command] [args] [options]\n"
"Sub commands:\n"
" ls: list services and methods for given arguments\n"
" Usage: cvd [selector options] env ls [service] [method] [-l]\n"
" service(optional) : gRPC service name\n"
" method(optional) : method name for given service\n"
" -l(optional) : Use a long listing format\n"
" type: get detailed information for given request/reply type\n"
" Usage: cvd [selector options] env type [service] [method] [type]\n"
" service : gRPC service name\n"
" method : method name in given service\n"
" type : Protocol buffer type name in given method\n"
" call: request a rpc with given method\n"
" Usage: cvd [selector options] env call [service] [method] [request]\n"
" service : gRPC service name\n"
" method : method name in given service\n"
" request : Protobuffer with text format\n\n"
"* \"cvd [selector_options] env\" can be replaced with:\n"
" \"cvd_internal_env [internal device name]\"\n";
bool PrintStream(std::stringstream* ss, const grpc::string& output) {
(*ss) << output;
return true;
}
class InsecureCliCredentials final : public grpc::testing::CliCredentials {
public:
std::shared_ptr<grpc::ChannelCredentials> GetChannelCredentials()
const override {
return InsecureChannelCredentials();
}
const grpc::string GetCredentialUsage() const override { return ""; }
};
std::vector<char*> ConvertToCharVec(const std::vector<std::string>& str_vec) {
std::vector<char*> char_vec;
char_vec.reserve(str_vec.size());
for (const auto& str : str_vec) {
char_vec.push_back(const_cast<char*>(str.c_str()));
}
return char_vec;
}
void RunGrpcCommand(const std::vector<std::string>& arguments,
std::stringstream& output_stream) {
int grpc_cli_argc = arguments.size();
auto new_arguments = ConvertToCharVec(arguments);
char** grpc_cli_argv = new_arguments.data();
grpc::testing::InitTest(&grpc_cli_argc, &grpc_cli_argv, true);
grpc::testing::GrpcToolMainLib(
grpc_cli_argc, (const char**)grpc_cli_argv, InsecureCliCredentials(),
std::bind(PrintStream, &output_stream, std::placeholders::_1));
}
std::string RunGrpcCommand(const std::vector<std::string>& arguments) {
std::stringstream output_stream;
RunGrpcCommand(arguments, output_stream);
return output_stream.str();
}
std::vector<std::string> GetServiceList(const std::string& server_address) {
std::vector<std::string> service_list;
std::stringstream output_stream;
std::vector<std::string> arguments{"grpc_cli", "ls", server_address};
RunGrpcCommand(arguments, output_stream);
std::string service_name;
while (std::getline(output_stream, service_name)) {
if (service_name.compare("grpc.reflection.v1alpha.ServerReflection") == 0) {
continue;
}
service_list.emplace_back(service_name);
}
return service_list;
}
Result<std::string> GetServerAddress(
const std::vector<std::string>& server_address_list,
const std::string& service_name) {
std::vector<std::string> candidates;
for (const auto& server_address : server_address_list) {
for (auto& full_service_name : GetServiceList(server_address)) {
if (android::base::EndsWith(full_service_name, service_name)) {
candidates.emplace_back(server_address);
break;
}
}
}
CF_EXPECT(candidates.size() > 0, service_name + " is not found.");
CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous.");
return candidates[0];
}
Result<std::string> GetFullServiceName(const std::string& server_address,
const std::string& service_name) {
std::vector<std::string> candidates;
for (auto& full_service_name : GetServiceList(server_address)) {
if (android::base::EndsWith(full_service_name, service_name)) {
candidates.emplace_back(full_service_name);
}
}
CF_EXPECT(candidates.size() > 0, service_name + " is not found.");
CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous.");
return candidates[0];
}
Result<std::string> GetFullMethodName(const std::string& server_address,
const std::string& service_name,
const std::string& method_name) {
const auto& full_service_name =
CF_EXPECT(GetFullServiceName(server_address, service_name));
return full_service_name + "/" + method_name;
}
Result<std::string> GetFullTypeName(const std::string& server_address,
const std::string& service_name,
const std::string& method_name,
const std::string& type_name) {
// Run grpc_cli ls -l for given method to extract full type name.
// Example output:
// rpc OpenwrtIpaddr(google.protobuf.Empty) returns
// (openwrtcontrolserver.OpenwrtIpaddrReply) {}
const auto& full_method_name =
CF_EXPECT(GetFullMethodName(server_address, service_name, method_name));
std::vector<std::string> grpc_arguments{"grpc_cli", "ls", "-l",
server_address, full_method_name};
auto grpc_result = RunGrpcCommand(grpc_arguments);
std::vector<std::string> candidates;
for (auto& full_type_name : android::base::Split(grpc_result, "()")) {
if (android::base::EndsWith(full_type_name, type_name)) {
candidates.emplace_back(full_type_name);
}
}
CF_EXPECT(candidates.size() > 0, service_name + " is not found.");
CF_EXPECT(candidates.size() < 2, service_name + " is ambiguous.");
return candidates[0];
}
Result<void> HandleLsCmd(const std::vector<std::string>& server_address_list,
const std::vector<std::string>& args,
const std::vector<std::string>& options) {
CF_EXPECT(args.size() < 3, "too many arguments");
if (args.size() > 0) {
std::vector<std::string> grpc_arguments{"grpc_cli", "ls"};
const auto& service_name = args[0];
const auto& server_address =
CF_EXPECT(GetServerAddress(server_address_list, service_name));
grpc_arguments.push_back(server_address);
if (args.size() > 1) {
// ls subcommand with 2 arguments; service_name and method_name
const auto& method_name = args[1];
const auto& full_method_name = CF_EXPECT(
GetFullMethodName(server_address, service_name, method_name));
grpc_arguments.push_back(full_method_name);
} else {
// ls subcommand with 1 argument; service_name
const auto& full_service_name =
CF_EXPECT(GetFullServiceName(server_address, service_name));
grpc_arguments.push_back(full_service_name);
}
grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end());
std::cout << RunGrpcCommand(grpc_arguments);
} else {
// ls subcommand with no arguments
for (const auto& server_address : server_address_list) {
std::vector<std::string> grpc_arguments{"grpc_cli", "ls", server_address};
grpc_arguments.insert(grpc_arguments.end(), options.begin(),
options.end());
std::cout << RunGrpcCommand(grpc_arguments);
}
}
return {};
}
Result<void> HandleTypeCmd(const std::vector<std::string>& server_address_list,
const std::vector<std::string>& args,
const std::vector<std::string>& options) {
CF_EXPECT(args.size() > 2,
"need to specify a service name, a method name, and type_name");
CF_EXPECT(args.size() < 4, "too many arguments");
std::vector<std::string> grpc_arguments{"grpc_cli", "type"};
const auto& service_name = args[0];
const auto& method_name = args[1];
const auto& type_name = args[2];
const auto& server_address =
CF_EXPECT(GetServerAddress(server_address_list, service_name));
grpc_arguments.push_back(server_address);
const auto& full_type_name = CF_EXPECT(
GetFullTypeName(server_address, service_name, method_name, type_name));
grpc_arguments.push_back(full_type_name);
grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end());
std::cout << RunGrpcCommand(grpc_arguments);
return {};
}
Result<void> HandleCallCmd(const std::vector<std::string>& server_address_list,
const std::vector<std::string>& args,
const std::vector<std::string>& options) {
CF_EXPECT(args.size() > 2,
"need to specify a service name, a method name, and text-formatted "
"proto");
CF_EXPECT(args.size() < 4, "too many arguments");
std::vector<std::string> grpc_arguments{"grpc_cli", "call"};
// TODO(b/265384449): support the case without text-formatted proto.
const auto& service_name = args[0];
const auto& method_name = args[1];
const auto& proto_text_format = args[2];
const auto& server_address =
CF_EXPECT(GetServerAddress(server_address_list, service_name));
grpc_arguments.push_back(server_address);
const auto& full_method_name =
CF_EXPECT(GetFullMethodName(server_address, service_name, method_name));
grpc_arguments.push_back(full_method_name);
grpc_arguments.push_back(proto_text_format);
grpc_arguments.insert(grpc_arguments.end(), options.begin(), options.end());
std::cout << RunGrpcCommand(grpc_arguments);
return {};
}
bool ContainHelpOption(int argc, char** argv) {
for (int i = 0; i < argc; i++) {
if (strcmp(argv[i], "--help") == 0 || strcmp(argv[i], "-help") == 0) {
return true;
}
}
return false;
}
Result<void> CvdEnvMain(int argc, char** argv) {
::android::base::InitLogging(argv, android::base::StderrLogger);
if (ContainHelpOption(argc, argv)) {
std::cout << kCvdEnvHelpMessage;
return {};
}
CF_EXPECT(argc >= 3, " need to specify a receiver and a command");
const auto& receiver = argv[1];
const auto& cmd = argv[2];
std::vector<std::string> options;
std::vector<std::string> args;
for (int i = 3; i < argc; i++) {
if (android::base::StartsWith(argv[i], '-')) {
options.push_back(argv[i]);
} else {
args.push_back(argv[i]);
}
}
const auto* config = CuttlefishConfig::Get();
CF_EXPECT(config != nullptr, "Unable to find the config");
std::vector<std::string> server_address_list;
const auto& instances = config->Instances();
auto receiver_instance = std::find_if(
begin(instances), end(instances), [&receiver](const auto& instance) {
return receiver == instance.instance_name();
});
CF_EXPECT(receiver_instance != std::end(instances),
"there is no instance of which name is "
<< receiver << ". please check instance name by cvd fleet");
for (const auto& entry : std::filesystem::directory_iterator(
receiver_instance->grpc_socket_path())) {
LOG(DEBUG) << "loading " << entry.path();
server_address_list.emplace_back("unix:" + entry.path().string());
}
auto command_map =
std::unordered_map<std::string, std::function<Result<void>(
const std::vector<std::string>&,
const std::vector<std::string>&,
const std::vector<std::string>&)>>{{
{"call", HandleCallCmd},
{"ls", HandleLsCmd},
{"type", HandleTypeCmd},
}};
CF_EXPECT(Contains(command_map, cmd), cmd << " isn't supported");
CF_EXPECT(command_map[cmd](server_address_list, args, options));
return {};
}
} // namespace
} // namespace cuttlefish
int main(int argc, char** argv) {
const auto& ret = cuttlefish::CvdEnvMain(argc, argv);
CHECK(ret.ok()) << ret.error().Message();
return 0;
}