357 lines
13 KiB
C++
357 lines
13 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 <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;
|
||
|
|
}
|