blob: 65861e17a8546ce9ad61cafce25508b028ae04a9 [file] [log] [blame]
Tyler Chatowa79419d2020-08-12 20:12:11 -07001#include <chrono>
Philipp Schrader08537492021-01-23 16:17:55 -08002#include <functional>
Tyler Chatowa79419d2020-08-12 20:12:11 -07003#include <iostream>
milind upadhyaya87957a2021-03-06 20:46:30 -08004#include <optional>
milind upadhyay4272f382021-04-07 18:03:08 -07005#include <string_view>
Tyler Chatowa79419d2020-08-12 20:12:11 -07006#include <unordered_map>
7
Philipp Schrader08537492021-01-23 16:17:55 -08008#include "absl/strings/str_format.h"
James Kuszmaul293b2172021-11-10 16:20:48 -08009#include "absl/strings/str_join.h"
Tyler Chatowa79419d2020-08-12 20:12:11 -070010#include "aos/init.h"
11#include "aos/json_to_flatbuffer.h"
milind upadhyaya87957a2021-03-06 20:46:30 -080012#include "aos/time/time.h"
Tyler Chatowa79419d2020-08-12 20:12:11 -070013#include "gflags/gflags.h"
14#include "starter_rpc_lib.h"
15
16DEFINE_string(config, "./config.json", "File path of aos configuration");
James Kuszmaul293b2172021-11-10 16:20:48 -080017// TODO(james): Bash autocompletion for node names.
18DEFINE_string(
19 node, "",
20 "Node to interact with. If empty, just interact with local node.");
21DEFINE_bool(all_nodes, false, "Interact with all nodes.");
Tyler Chatowa79419d2020-08-12 20:12:11 -070022
milind-u08dab882021-10-17 16:24:08 -070023DEFINE_bool(_bash_autocomplete, false,
24 "Internal use: Outputs commands or applications for use with "
25 "autocomplete script.");
26DEFINE_string(_bash_autocomplete_word, "",
27 "Internal use: Current word being autocompleted");
28
Philipp Schrader08537492021-01-23 16:17:55 -080029namespace {
Tyler Chatowa79419d2020-08-12 20:12:11 -070030
milind upadhyaya87957a2021-03-06 20:46:30 -080031namespace chrono = std::chrono;
32
Philipp Schrader08537492021-01-23 16:17:55 -080033static const std::unordered_map<std::string, aos::starter::Command>
34 kCommandConversions{{"start", aos::starter::Command::START},
35 {"stop", aos::starter::Command::STOP},
36 {"restart", aos::starter::Command::RESTART}};
Tyler Chatowa79419d2020-08-12 20:12:11 -070037
James Kuszmaul293b2172021-11-10 16:20:48 -080038std::vector<const aos::Node *> InteractNodes(
39 const aos::Configuration *configuration) {
Austin Schuh43fceaf2021-10-16 14:20:22 -070040 if (!configuration->has_nodes()) {
James Kuszmaul293b2172021-11-10 16:20:48 -080041 return {nullptr};
Austin Schuh43fceaf2021-10-16 14:20:22 -070042 }
43
James Kuszmaul293b2172021-11-10 16:20:48 -080044 if (!FLAGS_node.empty()) {
45 CHECK(!FLAGS_all_nodes) << "Can't specify both --node and --all_nodes.";
46 return {aos::configuration::GetNode(configuration, FLAGS_node)};
47 }
48
49 if (FLAGS_all_nodes) {
50 return aos::configuration::GetNodes(configuration);
51 }
52
53 return {aos::configuration::GetMyNode(configuration)};
Austin Schuh43fceaf2021-10-16 14:20:22 -070054}
55
James Kuszmaul293b2172021-11-10 16:20:48 -080056std::vector<const aos::Node *> InteractNodesForApplication(
57 const aos::Configuration *config, std::string_view application_name) {
58 const std::vector<const aos::Node *> interact_nodes = InteractNodes(config);
59 std::vector<const aos::Node *> application_nodes;
60 std::vector<std::string> debug_node_names;
61 for (const aos::Node *node : interact_nodes) {
62 if (aos::configuration::GetApplication(config, node, application_name) !=
63 nullptr) {
64 application_nodes.push_back(node);
Austin Schuh43fceaf2021-10-16 14:20:22 -070065 }
James Kuszmaul293b2172021-11-10 16:20:48 -080066 if (node != nullptr) {
67 debug_node_names.push_back(node->name()->str());
68 }
Austin Schuh43fceaf2021-10-16 14:20:22 -070069 }
James Kuszmaul293b2172021-11-10 16:20:48 -080070
71 if (application_nodes.empty()) {
72 if (interact_nodes.size() == 1 && interact_nodes[0] == nullptr) {
73 std::cout << "Unknown application " << application_name << std::endl;
74 } else {
75 std::cout << "Unknown application " << application_name
76 << " on any of node(s) "
77 << absl::StrJoin(debug_node_names, ", ") << std::endl;
78 }
79 }
80 return application_nodes;
Austin Schuh43fceaf2021-10-16 14:20:22 -070081}
82
milind upadhyaya87957a2021-03-06 20:46:30 -080083void PrintKey() {
James Kuszmaul293b2172021-11-10 16:20:48 -080084 absl::PrintF("%-30s %-10s %-8s %-6s %-9s\n", "Name", "Node", "State", "PID",
85 "Uptime");
milind upadhyaya87957a2021-03-06 20:46:30 -080086}
87
88void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
James Kuszmaul293b2172021-11-10 16:20:48 -080089 const aos::monotonic_clock::time_point &time,
90 const aos::Node *node) {
milind upadhyay4272f382021-04-07 18:03:08 -070091 const auto last_start_time = aos::monotonic_clock::time_point(
92 chrono::nanoseconds(app_status->last_start_time()));
milind upadhyaya87957a2021-03-06 20:46:30 -080093 const auto time_running =
94 chrono::duration_cast<chrono::seconds>(time - last_start_time);
Austin Schuhf4334002021-10-16 14:19:51 -070095 if (app_status->state() == aos::starter::State::STOPPED) {
James Kuszmaul293b2172021-11-10 16:20:48 -080096 absl::PrintF("%-30s %-10s %-8s\n", app_status->name()->string_view(),
97 (node == nullptr) ? "none" : node->name()->string_view(),
Austin Schuhf4334002021-10-16 14:19:51 -070098 aos::starter::EnumNameState(app_status->state()));
99 } else {
James Kuszmaul293b2172021-11-10 16:20:48 -0800100 absl::PrintF("%-30s %-10s %-8s %-6d %-9s\n",
101 app_status->name()->string_view(),
102 (node == nullptr) ? "none" : node->name()->string_view(),
Austin Schuhf4334002021-10-16 14:19:51 -0700103 aos::starter::EnumNameState(app_status->state()),
Austin Schuh31bbdea2021-10-16 15:51:37 -0700104 app_status->pid(), std::to_string(time_running.count()) + 's');
Austin Schuhf4334002021-10-16 14:19:51 -0700105 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800106}
107
Austin Schuh43fceaf2021-10-16 14:20:22 -0700108// Prints the status for all applications.
109void GetAllStarterStatus(const aos::Configuration *config) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800110 PrintKey();
111 std::vector<const aos::Node *> missing_nodes;
112 for (const aos::Node *node : InteractNodes(config)) {
113 // Print status for all processes.
114 const auto optional_status = aos::starter::GetStarterStatus(config, node);
115 if (optional_status) {
116 const aos::FlatbufferVector<aos::starter::Status> &status =
117 optional_status->second;
118 const aos::monotonic_clock::time_point time = optional_status->first;
119 for (const aos::starter::ApplicationStatus *app_status :
120 *status.message().statuses()) {
121 PrintApplicationStatus(app_status, time, node);
122 }
123 } else {
124 missing_nodes.push_back(node);
Philipp Schrader08537492021-01-23 16:17:55 -0800125 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800126 }
127 for (const aos::Node *node : missing_nodes) {
128 if (node == nullptr) {
129 LOG(WARNING) << "No status found.";
130 } else {
131 LOG(WARNING) << "No status found for node "
132 << node->name()->string_view();
133 }
milind-u08dab882021-10-17 16:24:08 -0700134 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700135}
136
137// Handles the "status" command. Returns true if the help message should be
138// printed.
139bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
140 if (argc == 1) {
141 GetAllStarterStatus(config);
Philipp Schrader08537492021-01-23 16:17:55 -0800142 } else if (argc == 2) {
143 // Print status for the specified process.
milind upadhyay4272f382021-04-07 18:03:08 -0700144 const auto application_name =
145 aos::starter::FindApplication(argv[1], config);
Austin Schuh43fceaf2021-10-16 14:20:22 -0700146 if (application_name == "all") {
147 GetAllStarterStatus(config);
148 return false;
149 }
150
James Kuszmaul293b2172021-11-10 16:20:48 -0800151 const std::vector<const aos::Node *> application_nodes =
152 InteractNodesForApplication(config, application_name);
153 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700154 return false;
155 }
milind upadhyaya87957a2021-03-06 20:46:30 -0800156 PrintKey();
James Kuszmaul293b2172021-11-10 16:20:48 -0800157 for (const aos::Node *node : application_nodes) {
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800158 auto optional_status =
159 aos::starter::GetStatus(application_name, config, node);
160 if (optional_status.has_value()) {
James Kuszmaul2ca441b2022-01-07 18:16:23 -0800161 PrintApplicationStatus(&optional_status.value().second.message(),
162 optional_status.value().first, node);
James Kuszmaule4bb0a22022-01-07 18:14:43 -0800163 } else {
164 if (node != nullptr) {
165 LOG(ERROR) << "No status available yet for \"" << application_name
166 << "\" on node \"" << node->name()->string_view() << "\".";
167 } else {
168 LOG(ERROR) << "No status available yet for \"" << application_name
169 << "\".";
170 }
171 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800172 }
Philipp Schrader08537492021-01-23 16:17:55 -0800173 } else {
174 LOG(ERROR) << "The \"status\" command requires zero or one arguments.";
175 return true;
176 }
177 return false;
178}
Tyler Chatowa79419d2020-08-12 20:12:11 -0700179
Austin Schuh43fceaf2021-10-16 14:20:22 -0700180// Sends the provided command to all applications. Prints the success text on
181// success, and failure text on failure.
182void InteractWithAll(const aos::Configuration *config,
183 const aos::starter::Command command,
184 std::string_view success_text,
185 std::string_view failure_text) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800186 std::map<const aos::Node *,
187 std::unique_ptr<aos::FlatbufferVector<aos::starter::Status>>>
188 statuses;
189
190 for (const aos::Node *node : InteractNodes(config)) {
191 std::optional<std::pair<aos::monotonic_clock::time_point,
192 const aos::FlatbufferVector<aos::starter::Status>>>
193 optional_status = aos::starter::GetStarterStatus(config, node);
194 if (optional_status.has_value()) {
195 statuses[node] =
196 std::make_unique<aos::FlatbufferVector<aos::starter::Status>>(
197 optional_status.value().second);
198 } else {
199 if (node == nullptr) {
200 LOG(WARNING) << "Starter not running";
201 } else {
202 LOG(WARNING) << "Starter not running on node "
203 << node->name()->string_view();
204 }
205 }
206 }
207
208 if (!statuses.empty()) {
209 std::vector<aos::starter::ApplicationCommand> commands;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700210
211 for (const aos::Application *application : *config->applications()) {
James Kuszmaul293b2172021-11-10 16:20:48 -0800212 const std::string_view application_name =
213 application->name()->string_view();
214 const std::vector<const aos::Node *> application_nodes =
215 InteractNodesForApplication(config, application_name);
216 // Ignore any applications which aren't supposed to be started.
217 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700218 continue;
219 }
220
James Kuszmaul293b2172021-11-10 16:20:48 -0800221 std::vector<const aos::Node *> running_nodes;
222 if (application->autostart()) {
223 running_nodes = application_nodes;
224 } else {
225 for (const aos::Node *node : application_nodes) {
226 const aos::starter::ApplicationStatus *application_status =
227 aos::starter::FindApplicationStatus(statuses[node]->message(),
228 application_name);
229 if (application_status->state() == aos::starter::State::STOPPED) {
230 if (node == nullptr) {
231 std::cout << "Skipping " << application_name
232 << " because it is STOPPED\n";
233 } else {
234 std::cout << "Skipping " << application_name << " on "
235 << node->name()->string_view()
236 << " because it is STOPPED\n";
237 }
238 continue;
239 } else {
240 running_nodes.push_back(node);
241 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700242 }
243 }
244
James Kuszmaul293b2172021-11-10 16:20:48 -0800245 if (!running_nodes.empty()) {
246 commands.emplace_back(aos::starter::ApplicationCommand{
247 command, application_name, running_nodes});
248 }
Austin Schuh43fceaf2021-10-16 14:20:22 -0700249 }
250
251 // Restart each running process
252 if (aos::starter::SendCommandBlocking(commands, config,
253 chrono::seconds(5))) {
254 std::cout << success_text << "all \n";
255 } else {
256 std::cout << failure_text << "all \n";
257 }
258 } else {
James Kuszmaul293b2172021-11-10 16:20:48 -0800259 LOG(WARNING) << "None of the starters we care about are running.";
Austin Schuh43fceaf2021-10-16 14:20:22 -0700260 }
261}
262
263// Handles the "start", "stop", and "restart" commands. Returns true if the
264// help message should be printed.
Philipp Schrader08537492021-01-23 16:17:55 -0800265bool InteractWithProgram(int argc, char **argv,
266 const aos::Configuration *config) {
267 const char *command_string = argv[0];
Philipp Schrader08537492021-01-23 16:17:55 -0800268 if (argc != 2) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700269 LOG(ERROR)
270 << "The \"" << command_string
271 << "\" command requires an application name or 'all' as an argument.";
Philipp Schrader08537492021-01-23 16:17:55 -0800272 return true;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700273 }
274
Philipp Schrader08537492021-01-23 16:17:55 -0800275 const auto command_search = kCommandConversions.find(command_string);
276 CHECK(command_search != kCommandConversions.end())
277 << "Internal error: \"" << command_string
278 << "\" is not in kCommandConversions.";
Philipp Schrader08537492021-01-23 16:17:55 -0800279 const aos::starter::Command command = command_search->second;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700280
281 std::string_view success_text;
282 const std::string failure_text =
283 std::string("Failed to ") + std::string(command_string) + " ";
284 switch (command) {
285 case aos::starter::Command::START:
286 success_text = "Successfully started ";
287 break;
288 case aos::starter::Command::STOP:
289 success_text = "Successfully stopped ";
290 break;
291 case aos::starter::Command::RESTART:
292 success_text = "Successfully restarted ";
293 break;
294 }
295
296 const std::string_view application_name =
297 aos::starter::FindApplication(argv[1], config);
298 if (application_name == "all") {
299 InteractWithAll(config, command, success_text, failure_text);
300 return false;
301 }
James Kuszmaul293b2172021-11-10 16:20:48 -0800302
303 const std::vector<const aos::Node *> application_nodes =
304 InteractNodesForApplication(config, application_name);
305 if (application_nodes.empty()) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700306 return false;
307 }
308
Philipp Schrader08537492021-01-23 16:17:55 -0800309 if (aos::starter::SendCommandBlocking(command, application_name, config,
James Kuszmaul293b2172021-11-10 16:20:48 -0800310 chrono::seconds(5),
311 application_nodes)) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700312 std::cout << success_text << application_name << '\n';
Tyler Chatowa79419d2020-08-12 20:12:11 -0700313 } else {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700314 std::cout << failure_text << application_name << '\n';
Jacob Ismael6388db92021-06-28 22:51:24 -0700315 }
316 return false;
317}
318
Austin Schuh33cc4162021-10-16 14:20:28 -0700319bool Help(int /*argc*/, char ** /*argv*/,
320 const aos::Configuration * /*config*/);
321
Philipp Schrader08537492021-01-23 16:17:55 -0800322// This is the set of subcommands we support. Each subcommand accepts argc and
323// argv from its own point of view. So argv[0] is always the name of the
324// subcommand. argv[1] and up are the arguments to the subcommand.
325// The subcommand returns true if there was an error parsing the command line
326// arguments. It returns false when the command line arguments are parsed
327// successfully.
Austin Schuh33cc4162021-10-16 14:20:28 -0700328static const std::vector<
329 std::tuple<std::string,
330 std::function<bool(int argc, char **argv,
331 const aos::Configuration *config)>,
332 std::string_view>>
333 kCommands{
334 {"help", Help, ""},
335 {"status", GetStarterStatus,
336 " [application], Returns the status of the provided application, "
337 "or all applications by default"},
338 {"start", InteractWithProgram,
339 " application, Starts the provided application, "
340 "or all applications if all is provided"},
341 {"stop", InteractWithProgram,
342 " application, Stops the provided application, "
343 "or all applications if all is provided"},
344 {"restart", InteractWithProgram,
345 " application, Restarts the provided application, "
346 "or all applications if all is provided"}};
347
348bool Help(int /*argc*/, char ** /*argv*/,
349 const aos::Configuration * /*config*/) {
350 std::cout << "Valid commands are:" << std::endl;
351 for (auto entry : kCommands) {
352 std::cout << " - " << std::get<0>(entry) << std::get<2>(entry) << std::endl;
353 }
354 return false;
355}
Philipp Schrader08537492021-01-23 16:17:55 -0800356
milind-u08dab882021-10-17 16:24:08 -0700357void Autocomplete(int argc, char **argv, const aos::Configuration *config) {
358 const std::string_view command = (argc >= 2 ? argv[1] : "");
359 const std::string_view app_name = (argc >= 3 ? argv[2] : "");
360
361 std::cout << "COMPREPLY=(";
362 if (FLAGS__bash_autocomplete_word == command) {
363 // Autocomplete the starter command
364 for (const auto &entry : kCommands) {
365 if (std::get<0>(entry).find(command) == 0) {
366 std::cout << '\'' << std::get<0>(entry) << "' ";
367 }
368 }
369 } else {
370 // Autocomplete the app name
371 for (const auto *app : *config->applications()) {
372 if (app->has_name() && app->name()->string_view().find(app_name) == 0) {
373 std::cout << '\'' << app->name()->string_view() << "' ";
374 }
375 }
376
377 // Autocomplete with "all"
milind-u95296dd2021-10-19 07:42:17 -0700378 if (std::string_view("all").find(app_name) == 0) {
milind-u08dab882021-10-17 16:24:08 -0700379 std::cout << "'all'";
380 }
381 }
382 std::cout << ')';
383}
384
Philipp Schrader08537492021-01-23 16:17:55 -0800385} // namespace
386
387int main(int argc, char **argv) {
388 aos::InitGoogle(&argc, &argv);
389
390 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
391 aos::configuration::ReadConfig(FLAGS_config);
392
milind-u08dab882021-10-17 16:24:08 -0700393 if (FLAGS__bash_autocomplete) {
394 Autocomplete(argc, argv, &config.message());
395 return 0;
396 }
397
Philipp Schrader08537492021-01-23 16:17:55 -0800398 bool parsing_failed = false;
399
400 if (argc < 2) {
401 parsing_failed = true;
402 } else {
403 const char *command = argv[1];
Austin Schuh33cc4162021-10-16 14:20:28 -0700404 auto it = std::find_if(
405 kCommands.begin(), kCommands.end(),
406 [command](const std::tuple<
407 std::string,
408 std::function<bool(int argc, char **argv,
409 const aos::Configuration *config)>,
410 std::string_view> &t) { return std::get<0>(t) == command; });
411
Philipp Schrader08537492021-01-23 16:17:55 -0800412 if (it == kCommands.end()) {
413 parsing_failed = true;
414 } else {
Austin Schuh33cc4162021-10-16 14:20:28 -0700415 parsing_failed = std::get<1>(*it)(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800416 }
417 }
418
419 if (parsing_failed) {
Austin Schuh33cc4162021-10-16 14:20:28 -0700420 Help(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800421 return 1;
422 }
423
424 return 0;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700425}