| // 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); | |
| } | |
| } |