blob: deac2edfc7c2b130fea0f87c1b4969f62e3d8802 [file] [log] [blame]
// Copyright 2023 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <iostream>
#include <base/at_exit.h>
#include <base/check.h>
#include <base/functional/bind.h>
#include <base/no_destructor.h>
#include <base/notreached.h>
#include <base/run_loop.h>
#include <base/task/single_thread_task_executor.h>
#include <base/task/single_thread_task_runner.h>
#include <brillo/flag_helper.h>
#include <brillo/syslog_logging.h>
#include <chromeos/mojo/service_constants.h>
#include <mojo/core/embedder/embedder.h>
#include <mojo/core/embedder/scoped_ipc_support.h>
#include <mojo/public/cpp/bindings/remote.h>
#include <mojo_service_manager/lib/connect.h>
#include "heartd/mojom/heartd.mojom.h"
namespace {
namespace mojom = ::ash::heartd::mojom;
chromeos::mojo_service_manager::mojom::ServiceManager*
GetServiceManagerProxy() {
static const base::NoDestructor<
mojo::Remote<chromeos::mojo_service_manager::mojom::ServiceManager>>
remote(chromeos::mojo_service_manager::ConnectToMojoServiceManager());
CHECK(remote->is_bound()) << "Failed to connect to mojo service manager.";
return remote->get();
}
// Requests a service and sets the disconnect handler to raise fatal error on
// disconnect.
template <typename T>
void RequestMojoServiceWithDisconnectHandler(const std::string& service_name,
mojo::Remote<T>& remote) {
GetServiceManagerProxy()->Request(
service_name, /*timeout=*/std::nullopt,
remote.BindNewPipeAndPassReceiver().PassPipe());
remote.set_disconnect_with_reason_handler(base::BindOnce(
[](const std::string& service_name, uint32_t error,
const std::string& reason) {
LOG(FATAL) << "Service " << service_name
<< " disconnected, error: " << error
<< ", reason: " << reason;
},
service_name));
}
mojom::ActionType GetActionEnum(std::string& action) {
if (action == "kNoOperation") {
return mojom::ActionType::kNoOperation;
} else if (action == "kNormalReboot") {
return mojom::ActionType::kNormalReboot;
} else if (action == "kForceReboot") {
return mojom::ActionType::kForceReboot;
} else if (action == "kSyncData") {
return mojom::ActionType::kSyncData;
}
LOG(ERROR) << "Unknow action: " << action;
NOTREACHED_NORETURN();
}
} // namespace
int main(int argc, char* argv[]) {
brillo::InitLog(brillo::kLogToStderr);
DEFINE_bool(enable_normal_reboot, false, "Whether to enable normal reboot.");
DEFINE_bool(enable_force_reboot, false, "Whether to enable force reboot.");
DEFINE_bool(stop_monitor, false,
"Stop monitor, this will be called before simulating client "
"missing.");
DEFINE_bool(simulate_client_missing, false,
"When this is set to true, this tool exits immediately after "
"registration, so it won't send heartbeat anymore.");
DEFINE_bool(run_action_now, false,
"When this is set to true, this tool asks heartd to run action1 "
"immediately and then exit.");
DEFINE_uint32(verification_window_seconds, 70,
"The verification window. Minimum is 70 seconds.");
// For testing, supporting two actions should be enough.
DEFINE_string(action1, "kNoOperation",
"The first action to take: "
"[kNoOperation, kNormalReboot, kForceReboot, kSyncData]");
DEFINE_string(action2, "kNoOperation",
"The second action to take: "
"[kNoOperation, kNormalReboot, kForceReboot, kSyncData]");
brillo::FlagHelper::Init(argc, argv, "Heartd test tool");
// Initialize the mojo environment.
base::AtExitManager at_exit_manager;
base::SingleThreadTaskExecutor task_executor(base::MessagePumpType::IO);
mojo::core::Init();
mojo::core::ScopedIPCSupport ipc_support(
base::SingleThreadTaskRunner::
GetCurrentDefault() /* io_thread_task_runner */,
mojo::core::ScopedIPCSupport::ShutdownPolicy::
CLEAN /* blocking shutdown */);
mojo::Remote<mojom::HeartdControl> control_remote;
RequestMojoServiceWithDisconnectHandler(
chromeos::mojo_services::kHeartdControl, control_remote);
if (FLAGS_enable_normal_reboot) {
control_remote->EnableNormalRebootAction();
}
if (FLAGS_enable_force_reboot) {
control_remote->EnableForceRebootAction();
}
if (FLAGS_run_action_now) {
base::RunLoop run_loop;
control_remote->RunAction(
GetActionEnum(FLAGS_action1),
base::BindOnce([](base::OnceClosure quit_closure,
bool success) { std::move(quit_closure).Run(); },
run_loop.QuitClosure()));
run_loop.Run();
return EXIT_SUCCESS;
}
auto argument = mojom::HeartbeatServiceArgument::New();
argument->verification_window_seconds = FLAGS_verification_window_seconds;
argument->actions.push_back(mojom::Action::New(
/*failure_count = */ 1, GetActionEnum(FLAGS_action1)));
argument->actions.push_back(mojom::Action::New(
/*failure_count = */ 2, GetActionEnum(FLAGS_action2)));
base::RunLoop register_run_loop;
mojo::Remote<mojom::HeartbeatService> hb_remote;
RequestMojoServiceWithDisconnectHandler(
chromeos::mojo_services::kHeartdHeartbeatService, hb_remote);
mojo::Remote<mojom::Pacemaker> pacemaker;
hb_remote->Register(mojom::ServiceName::kKiosk, std::move(argument),
pacemaker.BindNewPipeAndPassReceiver(),
base::BindOnce(
[](base::OnceClosure quit_closure, bool success) {
if (success) {
LOG(INFO) << "Registration success";
std::cout << "Registration success" << std::endl;
} else {
LOG(FATAL) << "Registration fail";
}
std::move(quit_closure).Run();
},
register_run_loop.QuitClosure()));
register_run_loop.Run();
if (FLAGS_stop_monitor) {
pacemaker->StopMonitor(base::DoNothing());
return EXIT_SUCCESS;
}
// Exit without sending heartbeat. This makes heartd to perform the actions.
if (FLAGS_simulate_client_missing) {
return EXIT_SUCCESS;
}
// Registration is complete, we can start sending heartbeat now.
LOG(INFO) << "Start sending heartbeat for every minute. Kill this process to "
<< "simulate client error at any time.";
while (true) {
pacemaker->SendHeartbeat(base::DoNothing());
sleep(60);
}
}