blob: 28119a8d21f185f001fb7fa447ea2a867a655209 [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
Philipp Schrader08537492021-01-23 16:17:55 -080017namespace {
Tyler Chatowa79419d2020-08-12 20:12:11 -070018
milind upadhyaya87957a2021-03-06 20:46:30 -080019namespace chrono = std::chrono;
20
Philipp Schrader08537492021-01-23 16:17:55 -080021static const std::unordered_map<std::string, aos::starter::Command>
22 kCommandConversions{{"start", aos::starter::Command::START},
23 {"stop", aos::starter::Command::STOP},
24 {"restart", aos::starter::Command::RESTART}};
Tyler Chatowa79419d2020-08-12 20:12:11 -070025
Austin Schuh43fceaf2021-10-16 14:20:22 -070026const aos::Node *MaybeMyNode(const aos::Configuration *configuration) {
27 if (!configuration->has_nodes()) {
28 return nullptr;
29 }
30
31 return aos::configuration::GetMyNode(configuration);
32}
33
34bool ValidApplication(const aos::Configuration *config,
35 std::string_view application_name) {
36 const aos::Node *node = MaybeMyNode(config);
37 const aos::Application *application =
38 aos::configuration::GetApplication(config, node, application_name);
39 if (application == nullptr) {
40 if (node) {
41 std::cout << "Unknown application '" << application_name << "' on node '"
42 << node->name()->string_view() << "'" << std::endl;
43 } else {
44 std::cout << "Unknown application '" << application_name << "'"
45 << std::endl;
46 }
47 return false;
48 }
49 return true;
50}
51
milind upadhyaya87957a2021-03-06 20:46:30 -080052void PrintKey() {
Austin Schuhf4334002021-10-16 14:19:51 -070053 absl::PrintF("%-30s %-8s %-6s %-9s\n", "Name", "State", "PID", "Uptime");
milind upadhyaya87957a2021-03-06 20:46:30 -080054}
55
56void PrintApplicationStatus(const aos::starter::ApplicationStatus *app_status,
milind upadhyay4272f382021-04-07 18:03:08 -070057 const aos::monotonic_clock::time_point &time) {
58 const auto last_start_time = aos::monotonic_clock::time_point(
59 chrono::nanoseconds(app_status->last_start_time()));
milind upadhyaya87957a2021-03-06 20:46:30 -080060 const auto time_running =
61 chrono::duration_cast<chrono::seconds>(time - last_start_time);
Austin Schuhf4334002021-10-16 14:19:51 -070062 if (app_status->state() == aos::starter::State::STOPPED) {
63 absl::PrintF("%-30s %-8s\n", app_status->name()->string_view(),
64 aos::starter::EnumNameState(app_status->state()));
65 } else {
66 absl::PrintF("%-30s %-8s %-6d %-9ds\n", app_status->name()->string_view(),
67 aos::starter::EnumNameState(app_status->state()),
68 app_status->pid(), time_running.count());
69 }
milind upadhyaya87957a2021-03-06 20:46:30 -080070}
71
Austin Schuh43fceaf2021-10-16 14:20:22 -070072// Prints the status for all applications.
73void GetAllStarterStatus(const aos::Configuration *config) {
Philipp Schrader08537492021-01-23 16:17:55 -080074 // Print status for all processes.
milind upadhyaya87957a2021-03-06 20:46:30 -080075 const auto optional_status = aos::starter::GetStarterStatus(config);
76 if (optional_status) {
77 auto status = *optional_status;
78 const auto time = aos::monotonic_clock::now();
79 PrintKey();
80 for (const aos::starter::ApplicationStatus *app_status :
81 *status.message().statuses()) {
82 PrintApplicationStatus(app_status, time);
83 }
84 } else {
85 LOG(WARNING) << "No status found";
Philipp Schrader08537492021-01-23 16:17:55 -080086 }
Austin Schuh43fceaf2021-10-16 14:20:22 -070087}
88
89// Handles the "status" command. Returns true if the help message should be
90// printed.
91bool GetStarterStatus(int argc, char **argv, const aos::Configuration *config) {
92 if (argc == 1) {
93 GetAllStarterStatus(config);
Philipp Schrader08537492021-01-23 16:17:55 -080094 } else if (argc == 2) {
95 // Print status for the specified process.
milind upadhyay4272f382021-04-07 18:03:08 -070096 const auto application_name =
97 aos::starter::FindApplication(argv[1], config);
Austin Schuh43fceaf2021-10-16 14:20:22 -070098 if (application_name == "all") {
99 GetAllStarterStatus(config);
100 return false;
101 }
102
103 if (!ValidApplication(config, application_name)) {
104 return false;
105 }
Philipp Schrader08537492021-01-23 16:17:55 -0800106 auto status = aos::starter::GetStatus(application_name, config);
milind upadhyaya87957a2021-03-06 20:46:30 -0800107 PrintKey();
108 PrintApplicationStatus(&status.message(), aos::monotonic_clock::now());
Philipp Schrader08537492021-01-23 16:17:55 -0800109 } else {
110 LOG(ERROR) << "The \"status\" command requires zero or one arguments.";
111 return true;
112 }
113 return false;
114}
Tyler Chatowa79419d2020-08-12 20:12:11 -0700115
Austin Schuh43fceaf2021-10-16 14:20:22 -0700116// Sends the provided command to all applications. Prints the success text on
117// success, and failure text on failure.
118void InteractWithAll(const aos::Configuration *config,
119 const aos::starter::Command command,
120 std::string_view success_text,
121 std::string_view failure_text) {
122 const auto optional_status = aos::starter::GetStarterStatus(config);
123 if (optional_status) {
124 auto status = *optional_status;
125 const aos::Node *my_node = MaybeMyNode(config);
126 std::vector<std::pair<aos::starter::Command, std::string_view>> commands;
127
128 for (const aos::Application *application : *config->applications()) {
129 // Ignore any applications which aren't supposed to be started on this
130 // node.
131 if (!aos::configuration::ApplicationShouldStart(config, my_node,
132 application)) {
133 continue;
134 }
135
136 const std::string_view application_name =
137 application->name()->string_view();
138 if (!application->autostart()) {
139 const aos::starter::ApplicationStatus *application_status =
140 aos::starter::FindApplicationStatus(status.message(),
141 application_name);
142 if (application_status->state() == aos::starter::State::STOPPED) {
143 std::cout << "Skipping " << application_name
144 << " because it is STOPPED\n";
145 continue;
146 }
147 }
148
149 commands.emplace_back(command, application_name);
150 }
151
152 // Restart each running process
153 if (aos::starter::SendCommandBlocking(commands, config,
154 chrono::seconds(5))) {
155 std::cout << success_text << "all \n";
156 } else {
157 std::cout << failure_text << "all \n";
158 }
159 } else {
160 LOG(WARNING) << "Starter not running";
161 }
162}
163
164// Handles the "start", "stop", and "restart" commands. Returns true if the
165// help message should be printed.
Philipp Schrader08537492021-01-23 16:17:55 -0800166bool InteractWithProgram(int argc, char **argv,
167 const aos::Configuration *config) {
168 const char *command_string = argv[0];
Philipp Schrader08537492021-01-23 16:17:55 -0800169 if (argc != 2) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700170 LOG(ERROR)
171 << "The \"" << command_string
172 << "\" command requires an application name or 'all' as an argument.";
Philipp Schrader08537492021-01-23 16:17:55 -0800173 return true;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700174 }
175
Philipp Schrader08537492021-01-23 16:17:55 -0800176 const auto command_search = kCommandConversions.find(command_string);
177 CHECK(command_search != kCommandConversions.end())
178 << "Internal error: \"" << command_string
179 << "\" is not in kCommandConversions.";
Philipp Schrader08537492021-01-23 16:17:55 -0800180 const aos::starter::Command command = command_search->second;
Austin Schuh43fceaf2021-10-16 14:20:22 -0700181
182 std::string_view success_text;
183 const std::string failure_text =
184 std::string("Failed to ") + std::string(command_string) + " ";
185 switch (command) {
186 case aos::starter::Command::START:
187 success_text = "Successfully started ";
188 break;
189 case aos::starter::Command::STOP:
190 success_text = "Successfully stopped ";
191 break;
192 case aos::starter::Command::RESTART:
193 success_text = "Successfully restarted ";
194 break;
195 }
196
197 const std::string_view application_name =
198 aos::starter::FindApplication(argv[1], config);
199 if (application_name == "all") {
200 InteractWithAll(config, command, success_text, failure_text);
201 return false;
202 }
203 if (!ValidApplication(config, application_name)) {
204 return false;
205 }
206
Philipp Schrader08537492021-01-23 16:17:55 -0800207 if (aos::starter::SendCommandBlocking(command, application_name, config,
Austin Schuha07b3ce2021-10-10 12:33:21 -0700208 chrono::seconds(5))) {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700209 std::cout << success_text << application_name << '\n';
Tyler Chatowa79419d2020-08-12 20:12:11 -0700210 } else {
Austin Schuh43fceaf2021-10-16 14:20:22 -0700211 std::cout << failure_text << application_name << '\n';
Jacob Ismael6388db92021-06-28 22:51:24 -0700212 }
213 return false;
214}
215
Philipp Schrader08537492021-01-23 16:17:55 -0800216// This is the set of subcommands we support. Each subcommand accepts argc and
217// argv from its own point of view. So argv[0] is always the name of the
218// subcommand. argv[1] and up are the arguments to the subcommand.
219// The subcommand returns true if there was an error parsing the command line
220// arguments. It returns false when the command line arguments are parsed
221// successfully.
222static const std::unordered_map<
223 std::string, std::function<bool(int argc, char **argv,
224 const aos::Configuration *config)>>
Jacob Ismael6388db92021-06-28 22:51:24 -0700225 kCommands{{"status", GetStarterStatus},
226 {"start", InteractWithProgram},
227 {"stop", InteractWithProgram},
Austin Schuh43fceaf2021-10-16 14:20:22 -0700228 {"restart", InteractWithProgram}};
Philipp Schrader08537492021-01-23 16:17:55 -0800229
230} // namespace
231
232int main(int argc, char **argv) {
233 aos::InitGoogle(&argc, &argv);
234
235 aos::FlatbufferDetachedBuffer<aos::Configuration> config =
236 aos::configuration::ReadConfig(FLAGS_config);
237
238 bool parsing_failed = false;
239
240 if (argc < 2) {
241 parsing_failed = true;
242 } else {
243 const char *command = argv[1];
244 auto it = kCommands.find(command);
245 if (it == kCommands.end()) {
246 parsing_failed = true;
247 } else {
248 parsing_failed = it->second(argc - 1, argv + 1, &config.message());
249 }
250 }
251
252 if (parsing_failed) {
253 LOG(ERROR) << "Parsing failed. Valid commands are:";
milind upadhyay4272f382021-04-07 18:03:08 -0700254 for (auto entry : kCommands) {
Philipp Schrader08537492021-01-23 16:17:55 -0800255 LOG(ERROR) << " - " << entry.first;
256 }
257 return 1;
258 }
259
260 return 0;
Tyler Chatowa79419d2020-08-12 20:12:11 -0700261}