blob: 89a6c57dd797185c71afd1dcd1a0cd5909ac9105 [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"
Tyler Chatowa79419d2020-08-12 20:12:11 -07009#include "aos/init.h"
10#include "aos/json_to_flatbuffer.h"
milind upadhyaya87957a2021-03-06 20:46:30 -080011#include "aos/time/time.h"
Tyler Chatowa79419d2020-08-12 20:12:11 -070012#include "gflags/gflags.h"
13#include "starter_rpc_lib.h"
14
15DEFINE_string(config, "./config.json", "File path of aos configuration");
16
milind-u08dab882021-10-17 16:24:08 -070017DEFINE_bool(_bash_autocomplete, false,
18 "Internal use: Outputs commands or applications for use with "
19 "autocomplete script.");
20DEFINE_string(_bash_autocomplete_word, "",
21 "Internal use: Current word being autocompleted");
22
Philipp Schrader08537492021-01-23 16:17:55 -080023namespace {
Tyler Chatowa79419d2020-08-12 20:12:11 -070024
milind upadhyaya87957a2021-03-06 20:46:30 -080025namespace chrono = std::chrono;
26
Philipp Schrader08537492021-01-23 16:17:55 -080027static const std::unordered_map<std::string, aos::starter::Command>
28 kCommandConversions{{"start", aos::starter::Command::START},
29 {"stop", aos::starter::Command::STOP},
30 {"restart", aos::starter::Command::RESTART}};
Tyler Chatowa79419d2020-08-12 20:12:11 -070031
Austin Schuh43fceaf2021-10-16 14:20:22 -070032const aos::Node *MaybeMyNode(const aos::Configuration *configuration) {
33 if (!configuration->has_nodes()) {
34 return nullptr;
35 }
36
37 return aos::configuration::GetMyNode(configuration);
38}
39
40bool ValidApplication(const aos::Configuration *config,
41 std::string_view application_name) {
42 const aos::Node *node = MaybeMyNode(config);
43 const aos::Application *application =
44 aos::configuration::GetApplication(config, node, application_name);
45 if (application == nullptr) {
46 if (node) {
47 std::cout << "Unknown application '" << application_name << "' on node '"
48 << node->name()->string_view() << "'" << std::endl;
49 } else {
50 std::cout << "Unknown application '" << application_name << "'"
51 << std::endl;
52 }
53 return false;
54 }
55 return true;
56}
57
milind upadhyaya87957a2021-03-06 20:46:30 -080058void PrintKey() {
Austin Schuhf4334002021-10-16 14:19:51 -070059 absl::PrintF("%-30s %-8s %-6s %-9s\n", "Name", "State", "PID", "Uptime");
milind upadhyaya87957a2021-03-06 20:46:30 -080060}
61
62void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
milind upadhyay4272f382021-04-07 18:03:08 -070063 const aos::monotonic_clock::time_point &time) {
64 const auto last_start_time = aos::monotonic_clock::time_point(
65 chrono::nanoseconds(app_status->last_start_time()));
milind upadhyaya87957a2021-03-06 20:46:30 -080066 const auto time_running =
67 chrono::duration_cast<chrono::seconds>(time - last_start_time);
Austin Schuhf4334002021-10-16 14:19:51 -070068 if (app_status->state() == aos::starter::State::STOPPED) {
69 absl::PrintF("%-30s %-8s\n", app_status->name()->string_view(),
70 aos::starter::EnumNameState(app_status->state()));
71 } else {
Austin Schuh31bbdea2021-10-16 15:51:37 -070072 absl::PrintF("%-30s %-8s %-6d %-9s\n", app_status->name()->string_view(),
Austin Schuhf4334002021-10-16 14:19:51 -070073 aos::starter::EnumNameState(app_status->state()),
Austin Schuh31bbdea2021-10-16 15:51:37 -070074 app_status->pid(), std::to_string(time_running.count()) + 's');
Austin Schuhf4334002021-10-16 14:19:51 -070075 }
milind upadhyaya87957a2021-03-06 20:46:30 -080076}
77
Austin Schuh43fceaf2021-10-16 14:20:22 -070078// Prints the status for all applications.
79void GetAllStarterStatus(const aos::Configuration *config) {
milind-u08dab882021-10-17 16:24:08 -070080 // Print status for all processes.
81 const auto optional_status = aos::starter::GetStarterStatus(config);
82 if (optional_status) {
83 auto status = *optional_status;
84 const auto time = aos::monotonic_clock::now();
85 PrintKey();
86 for (const aos::starter::ApplicationStatus *app_status :
87 *status.message().statuses()) {
88 PrintApplicationStatus(app_status, time);
Philipp Schrader08537492021-01-23 16:17:55 -080089 }
milind-u08dab882021-10-17 16:24:08 -070090 } else {
91 LOG(WARNING) << "No status found";
92 }
Austin Schuh43fceaf2021-10-16 14:20:22 -070093}
94
95// Handles the "status" command. Returns true if the help message should be
96// printed.
97bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
98 if (argc == 1) {
99 GetAllStarterStatus(config);
Philipp Schrader08537492021-01-23 16:17:55 -0800100 } else if (argc == 2) {
101 // Print status for the specified process.
milind upadhyay4272f382021-04-07 18:03:08 -0700102 const auto application_name =
103 aos::starter::FindApplication(argv[1], config);
Austin Schuh43fceaf2021-10-16 14:20:22 -0700104 if (application_name == "all") {
105 GetAllStarterStatus(config);
106 return false;
107 }
108
109 if (!ValidApplication(config, application_name)) {
110 return false;
111 }
Philipp Schrader08537492021-01-23 16:17:55 -0800112 auto status = aos::starter::GetStatus(application_name, config);
milind upadhyaya87957a2021-03-06 20:46:30 -0800113 PrintKey();
114 PrintApplicationStatus(&status.message(), aos::monotonic_clock::now());
Philipp Schrader08537492021-01-23 16:17:55 -0800115 } else {
116 LOG(ERROR) << "The \"status\" command requires zero or one arguments.";
117 return true;
118 }
119 return false;
120}
Tyler Chatowa79419d2020-08-12 20:12:11 -0700121
Austin Schuh43fceaf2021-10-16 14:20:22 -0700122// Sends the provided command to all applications. Prints the success text on
123// success, and failure text on failure.
124void InteractWithAll(const aos::Configuration *config,
125 const aos::starter::Command command,
126 std::string_view success_text,
127 std::string_view failure_text) {
128 const auto optional_status = aos::starter::GetStarterStatus(config);
129 if (optional_status) {
130 auto status = *optional_status;
131 const aos::Node *my_node = MaybeMyNode(config);
132 std::vector<std::pair<aos::starter::Command, std::string_view>> commands;
133
134 for (const aos::Application *application : *config->applications()) {
135 // Ignore any applications which aren't supposed to be started on this
136 // node.
137 if (!aos::configuration::ApplicationShouldStart(config, my_node,
138 application)) {
139 continue;
140 }
141
142 const std::string_view application_name =
143 application->name()->string_view();
144 if (!application->autostart()) {
145 const aos::starter::ApplicationStatus *application_status =
146 aos::starter::FindApplicationStatus(status.message(),
147 application_name);
148 if (application_status->state() == aos::starter::State::STOPPED) {
149 std::cout << "Skipping " << application_name
150 << " because it is STOPPED\n";
151 continue;
152 }
153 }
154
155 commands.emplace_back(command, application_name);
156 }
157
158 // Restart each running process
159 if (aos::starter::SendCommandBlocking(commands, config,
160 chrono::seconds(5))) {
161 std::cout << success_text << "all \n";
162 } else {
163 std::cout << failure_text << "all \n";
164 }
165 } else {
166 LOG(WARNING) << "Starter not running";
167 }
168}
169
170// Handles the "start", "stop", and "restart" commands. Returns true if the
171// help message should be printed.
Philipp Schrader08537492021-01-23 16:17:55 -0800172bool InteractWithProgram(int argc, char **argv,
173 const aos::Configuration *config) {
174 const char *command_string = argv[0];
Philipp Schrader08537492021-01-23 16:17:55 -0800175 if (argc != 2) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700176 LOG(ERROR)
177 << "The \"" << command_string
178 << "\" command requires an application name or 'all' as an argument.";
Philipp Schrader08537492021-01-23 16:17:55 -0800179 return true;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700180 }
181
Philipp Schrader08537492021-01-23 16:17:55 -0800182 const auto command_search = kCommandConversions.find(command_string);
183 CHECK(command_search != kCommandConversions.end())
184 << "Internal error: \"" << command_string
185 << "\" is not in kCommandConversions.";
Philipp Schrader08537492021-01-23 16:17:55 -0800186 const aos::starter::Command command = command_search->second;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700187
188 std::string_view success_text;
189 const std::string failure_text =
190 std::string("Failed to ") + std::string(command_string) + " ";
191 switch (command) {
192 case aos::starter::Command::START:
193 success_text = "Successfully started ";
194 break;
195 case aos::starter::Command::STOP:
196 success_text = "Successfully stopped ";
197 break;
198 case aos::starter::Command::RESTART:
199 success_text = "Successfully restarted ";
200 break;
201 }
202
203 const std::string_view application_name =
204 aos::starter::FindApplication(argv[1], config);
205 if (application_name == "all") {
206 InteractWithAll(config, command, success_text, failure_text);
207 return false;
208 }
209 if (!ValidApplication(config, application_name)) {
210 return false;
211 }
212
Philipp Schrader08537492021-01-23 16:17:55 -0800213 if (aos::starter::SendCommandBlocking(command, application_name, config,
Austin Schuha07b3ce2021-10-10 12:33:21 -0700214 chrono::seconds(5))) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700215 std::cout << success_text << application_name << '\n';
Tyler Chatowa79419d2020-08-12 20:12:11 -0700216 } else {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700217 std::cout << failure_text << application_name << '\n';
Jacob Ismael6388db92021-06-28 22:51:24 -0700218 }
219 return false;
220}
221
Austin Schuh33cc4162021-10-16 14:20:28 -0700222bool Help(int /*argc*/, char ** /*argv*/,
223 const aos::Configuration * /*config*/);
224
Philipp Schrader08537492021-01-23 16:17:55 -0800225// This is the set of subcommands we support. Each subcommand accepts argc and
226// argv from its own point of view. So argv[0] is always the name of the
227// subcommand. argv[1] and up are the arguments to the subcommand.
228// The subcommand returns true if there was an error parsing the command line
229// arguments. It returns false when the command line arguments are parsed
230// successfully.
Austin Schuh33cc4162021-10-16 14:20:28 -0700231static const std::vector<
232 std::tuple<std::string,
233 std::function<bool(int argc, char **argv,
234 const aos::Configuration *config)>,
235 std::string_view>>
236 kCommands{
237 {"help", Help, ""},
238 {"status", GetStarterStatus,
239 " [application], Returns the status of the provided application, "
240 "or all applications by default"},
241 {"start", InteractWithProgram,
242 " application, Starts the provided application, "
243 "or all applications if all is provided"},
244 {"stop", InteractWithProgram,
245 " application, Stops the provided application, "
246 "or all applications if all is provided"},
247 {"restart", InteractWithProgram,
248 " application, Restarts the provided application, "
249 "or all applications if all is provided"}};
250
251bool Help(int /*argc*/, char ** /*argv*/,
252 const aos::Configuration * /*config*/) {
253 std::cout << "Valid commands are:" << std::endl;
254 for (auto entry : kCommands) {
255 std::cout << " - " << std::get<0>(entry) << std::get<2>(entry) << std::endl;
256 }
257 return false;
258}
Philipp Schrader08537492021-01-23 16:17:55 -0800259
milind-u08dab882021-10-17 16:24:08 -0700260void Autocomplete(int argc, char **argv, const aos::Configuration *config) {
261 const std::string_view command = (argc >= 2 ? argv[1] : "");
262 const std::string_view app_name = (argc >= 3 ? argv[2] : "");
263
264 std::cout << "COMPREPLY=(";
265 if (FLAGS__bash_autocomplete_word == command) {
266 // Autocomplete the starter command
267 for (const auto &entry : kCommands) {
268 if (std::get<0>(entry).find(command) == 0) {
269 std::cout << '\'' << std::get<0>(entry) << "' ";
270 }
271 }
272 } else {
273 // Autocomplete the app name
274 for (const auto *app : *config->applications()) {
275 if (app->has_name() && app->name()->string_view().find(app_name) == 0) {
276 std::cout << '\'' << app->name()->string_view() << "' ";
277 }
278 }
279
280 // Autocomplete with "all"
281 if (std::string_view("all").find(FLAGS__bash_autocomplete_word) == 0) {
282 std::cout << "'all'";
283 }
284 }
285 std::cout << ')';
286}
287
Philipp Schrader08537492021-01-23 16:17:55 -0800288} // namespace
289
290int main(int argc, char **argv) {
291 aos::InitGoogle(&argc, &argv);
292
293 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
294 aos::configuration::ReadConfig(FLAGS_config);
295
milind-u08dab882021-10-17 16:24:08 -0700296 if (FLAGS__bash_autocomplete) {
297 Autocomplete(argc, argv, &config.message());
298 return 0;
299 }
300
Philipp Schrader08537492021-01-23 16:17:55 -0800301 bool parsing_failed = false;
302
303 if (argc < 2) {
304 parsing_failed = true;
305 } else {
306 const char *command = argv[1];
Austin Schuh33cc4162021-10-16 14:20:28 -0700307 auto it = std::find_if(
308 kCommands.begin(), kCommands.end(),
309 [command](const std::tuple<
310 std::string,
311 std::function<bool(int argc, char **argv,
312 const aos::Configuration *config)>,
313 std::string_view> &t) { return std::get<0>(t) == command; });
314
Philipp Schrader08537492021-01-23 16:17:55 -0800315 if (it == kCommands.end()) {
316 parsing_failed = true;
317 } else {
Austin Schuh33cc4162021-10-16 14:20:28 -0700318 parsing_failed = std::get<1>(*it)(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800319 }
320 }
321
322 if (parsing_failed) {
Austin Schuh33cc4162021-10-16 14:20:28 -0700323 Help(argc - 1, argv + 1, &config.message());
Philipp Schrader08537492021-01-23 16:17:55 -0800324 return 1;
325 }
326
327 return 0;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700328}