LogCabin
|
00001 /* Copyright (c) 2015 Diego Ongaro 00002 * 00003 * Permission to use, copy, modify, and distribute this software for any 00004 * purpose with or without fee is hereby granted, provided that the above 00005 * copyright notice and this permission notice appear in all copies. 00006 * 00007 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES 00008 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 00009 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR 00010 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 00011 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 00012 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 00013 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 00014 */ 00015 00016 #include <cassert> 00017 #include <getopt.h> 00018 #include <iostream> 00019 #include <string> 00020 #include <vector> 00021 00022 #include "Client/ClientImpl.h" 00023 #include "Core/ProtoBuf.h" 00024 #include "build/Protocol/ServerControl.pb.h" 00025 #include "include/LogCabin/Client.h" 00026 #include "include/LogCabin/Debug.h" 00027 #include "include/LogCabin/Util.h" 00028 00029 namespace LogCabin { 00030 namespace Client { 00031 namespace { 00032 00033 using Client::Util::parseNonNegativeDuration; 00034 00035 /** 00036 * Parses argv for the main function. 00037 */ 00038 class OptionParser { 00039 public: 00040 OptionParser(int& argc, char**& argv) 00041 : argc(argc) 00042 , argv(argv) 00043 , args() 00044 , lastIndex(0) 00045 , logPolicy("") 00046 , server("localhost:5254") 00047 , timeout(parseNonNegativeDuration("0s")) 00048 { 00049 while (true) { 00050 static struct option longOptions[] = { 00051 {"help", no_argument, NULL, 'h'}, 00052 {"server", required_argument, NULL, 's'}, 00053 {"timeout", required_argument, NULL, 't'}, 00054 {"verbose", no_argument, NULL, 'v'}, 00055 {"verbosity", required_argument, NULL, 256}, 00056 {0, 0, 0, 0} 00057 }; 00058 int c = getopt_long(argc, argv, "s:t:hv", longOptions, NULL); 00059 00060 // Detect the end of the options. 00061 if (c == -1) 00062 break; 00063 00064 switch (c) { 00065 case 'h': 00066 usage(); 00067 exit(0); 00068 case 's': 00069 server = optarg; 00070 break; 00071 case 't': 00072 timeout = parseNonNegativeDuration(optarg); 00073 break; 00074 case 'v': 00075 logPolicy = "VERBOSE"; 00076 break; 00077 case 256: 00078 logPolicy = optarg; 00079 break; 00080 case '?': 00081 default: 00082 // getopt_long already printed an error message. 00083 usage(); 00084 exit(1); 00085 } 00086 } 00087 00088 args.assign(&argv[optind], &argv[argc]); 00089 } 00090 00091 /** 00092 * Return the positional argument at the given index, 00093 * or panic if there were not enough arguments. 00094 */ 00095 std::string at(uint64_t index) { 00096 if (args.size() <= index) 00097 usageError("Missing arguments"); 00098 lastIndex = index; 00099 return args.at(index); 00100 } 00101 00102 /** 00103 * Return all arguments at index or following it. 00104 */ 00105 std::string remaining(uint64_t index) { 00106 lastIndex = args.size(); 00107 std::string r; 00108 while (index < args.size()) { 00109 r += args.at(index); 00110 if (index < args.size() - 1) 00111 r += " "; 00112 ++index; 00113 } 00114 if (index < args.size()) 00115 r += args.at(index); 00116 return r; 00117 } 00118 00119 /** 00120 * Panic if are any unused arguments remain. 00121 */ 00122 void done() { 00123 if (args.size() > lastIndex + 1) 00124 usageError("Too many arguments"); 00125 } 00126 00127 /** 00128 * Print an error and the usage message and exit nonzero. 00129 */ 00130 void usageError(const std::string& message) { 00131 std::cerr << message << std::endl; 00132 usage(); 00133 exit(1); 00134 } 00135 00136 /** 00137 * Helper for spacing in usage() message. 00138 */ 00139 std::string ospace(std::string option) { 00140 std::string after; 00141 if (option.size() < 31 - 2) 00142 after = std::string(31 - 2 - option.size(), ' '); 00143 return " " + option + after; 00144 } 00145 00146 void usage() { 00147 std::cout << "Inspect or modify the state of a single LogCabin server." 00148 << std::endl 00149 << std::endl 00150 << "This program was added in LogCabin v1.1.0." 00151 << std::endl; 00152 std::cout << std::endl; 00153 00154 std::cout << "Usage: " << argv[0] << " [options] <command> [<args>]" 00155 << std::endl; 00156 std::cout << std::endl; 00157 00158 std::string space(31, ' '); 00159 std::cout << "Commands:" << std::endl; 00160 std::cout 00161 << ospace("info get") 00162 << "Print server ID and addresses." 00163 << std::endl 00164 00165 << ospace("debug filename get") 00166 << "Print the server's debug log filename." 00167 << std::endl 00168 00169 << ospace("debug filename set <path>") 00170 << "Change the server's debug log filename." 00171 << std::endl 00172 00173 << ospace("debug policy get") 00174 << "Print the server's debug log policy." 00175 << std::endl 00176 00177 << ospace("debug policy set <value>") 00178 << "Change the server's debug log policy." 00179 << std::endl 00180 00181 << ospace("debug rotate") 00182 << "Rotate the server's debug log file." 00183 << std::endl 00184 00185 << ospace("snapshot inhibit get") 00186 << "Print the remaining time for which the server" 00187 << std::endl << space 00188 << "was prevented from taking snapshots." 00189 << std::endl 00190 00191 << ospace("snapshot inhibit set [<time>]") 00192 << " Abort the server's current snapshot if one is" 00193 << std::endl << space 00194 << " in progress, and disallow the server from" 00195 << std::endl << space 00196 << " starting automated snapshots for the given" 00197 << std::endl << space 00198 << " duration [default: 1week]." 00199 << std::endl 00200 00201 << ospace("snapshot inhibit clear") 00202 << "Allow the server to take snapshots normally." 00203 << std::endl 00204 00205 << ospace("snapshot start") 00206 << "Begin taking a snapshot if none is in progress." 00207 << std::endl 00208 00209 << ospace("snapshot stop") 00210 << "Abort the current snapshot if one is in" 00211 << std::endl << space 00212 << "progress." 00213 << std::endl 00214 00215 << ospace("snapshot restart") 00216 << "Abort the current snapshot if one is in" 00217 << std::endl << space 00218 << "progress, then begin taking a new snapshot." 00219 << std::endl 00220 00221 << ospace("stats get") 00222 << "Print detailed server metrics." 00223 << std::endl 00224 00225 << ospace("stats dump") 00226 << "Write detailed server metrics to server's debug" 00227 << std::endl << space 00228 << "log." 00229 << std::endl 00230 << std::endl; 00231 00232 std::cout << "Options:" << std::endl; 00233 std::cout 00234 << ospace("-h, --help") 00235 << "Print this usage information and exit" 00236 << std::endl 00237 00238 << " -s <addresses>, --server=<addresses> " 00239 << "Network addresses of the target" 00240 << std::endl 00241 << " " 00242 << "LogCabin server, comma-separated" 00243 << std::endl 00244 << " " 00245 << "[default: localhost:5254]" 00246 << std::endl 00247 00248 << ospace("-t <time>, --timeout=<time>") 00249 << "Set timeout for the operation" 00250 << std::endl << space 00251 << "(0 means wait forever) [default: 0s]" 00252 << std::endl 00253 00254 << ospace("-v, --verbose") 00255 << "Same as --verbosity=VERBOSE" 00256 << std::endl 00257 00258 << ospace("--verbosity=<policy>") 00259 << "Set which log messages are shown." 00260 << std::endl << space 00261 << "Comma-separated LEVEL or PATTERN@LEVEL rules." 00262 << std::endl << space 00263 << "Levels: SILENT, ERROR, WARNING, NOTICE, VERBOSE." 00264 << std::endl << space 00265 << "Patterns match filename prefixes or suffixes." 00266 << std::endl << space 00267 << "Example: Client@NOTICE,Test.cc@SILENT,VERBOSE." 00268 << std::endl; 00269 00270 // TODO(ongaro): human-readable vs machine-readable output? 00271 } 00272 00273 int& argc; 00274 char**& argv; 00275 std::vector<std::string> args; 00276 uint64_t lastIndex; 00277 std::string logPolicy; 00278 std::string server; 00279 uint64_t timeout; 00280 }; 00281 00282 /** 00283 * Print an error message and exit nonzero. 00284 */ 00285 void 00286 error(const std::string& message) 00287 { 00288 std::cerr << "Error: " << message << std::endl; 00289 exit(1); 00290 } 00291 00292 namespace Proto = Protocol::ServerControl; 00293 00294 /** 00295 * Wrapper for invoking ServerControl RPCs. 00296 */ 00297 class ServerControl { 00298 public: 00299 ServerControl(const std::string& server, ClientImpl::TimePoint timeout) 00300 : clientImpl() 00301 , server(server) 00302 , timeout(timeout) 00303 { 00304 clientImpl.init("-INVALID-"); // shouldn't attempt to connect to this 00305 } 00306 00307 #define DEFINE_RPC(type, opcode) \ 00308 void type(const Proto::type::Request& request, \ 00309 Proto::type::Response& response) { \ 00310 Result result = clientImpl.serverControl( \ 00311 server, \ 00312 timeout, \ 00313 Proto::OpCode::opcode, \ 00314 request, response); \ 00315 if (result.status != Status::OK) { \ 00316 error(result.error); \ 00317 } \ 00318 } 00319 00320 DEFINE_RPC(DebugFilenameGet, DEBUG_FILENAME_GET) 00321 DEFINE_RPC(DebugFilenameSet, DEBUG_FILENAME_SET) 00322 DEFINE_RPC(DebugPolicyGet, DEBUG_POLICY_GET) 00323 DEFINE_RPC(DebugPolicySet, DEBUG_POLICY_SET) 00324 DEFINE_RPC(DebugRotate, DEBUG_ROTATE) 00325 DEFINE_RPC(ServerInfoGet, SERVER_INFO_GET) 00326 DEFINE_RPC(ServerStatsDump, SERVER_STATS_DUMP) 00327 DEFINE_RPC(ServerStatsGet, SERVER_STATS_GET) 00328 DEFINE_RPC(SnapshotControl, SNAPSHOT_CONTROL) 00329 DEFINE_RPC(SnapshotInhibitGet, SNAPSHOT_INHIBIT_GET) 00330 DEFINE_RPC(SnapshotInhibitSet, SNAPSHOT_INHIBIT_SET) 00331 00332 #undef DEFINE_RPC 00333 00334 void snapshotControl(Proto::SnapshotCommand command) { 00335 Proto::SnapshotControl::Request request; 00336 Proto::SnapshotControl::Response response; 00337 request.set_command(command); 00338 SnapshotControl(request, response); 00339 if (response.has_error()) 00340 error(response.error()); 00341 } 00342 00343 ClientImpl clientImpl; 00344 std::string server; 00345 ClientImpl::TimePoint timeout; 00346 }; 00347 00348 } // namespace LogCabin::Client::<anonymous> 00349 } // namespace LogCabin::Client 00350 } // namespace LogCabin 00351 00352 00353 int 00354 main(int argc, char** argv) 00355 { 00356 using namespace LogCabin; 00357 using namespace LogCabin::Client; 00358 using Core::ProtoBuf::dumpString; 00359 00360 try { 00361 Client::OptionParser options(argc, argv); 00362 Client::Debug::setLogPolicy( 00363 Client::Debug::logPolicyFromString(options.logPolicy)); 00364 ServerControl server(options.server, 00365 ClientImpl::absTimeout(options.timeout)); 00366 00367 if (options.at(0) == "info") { 00368 if (options.at(1) == "get") { 00369 options.done(); 00370 Proto::ServerInfoGet::Request request; 00371 Proto::ServerInfoGet::Response response; 00372 server.ServerInfoGet(request, response); 00373 std::cout << dumpString(response); 00374 return 0; 00375 } 00376 } else if (options.at(0) == "debug") { 00377 if (options.at(1) == "filename") { 00378 if (options.at(2) == "get") { 00379 options.done(); 00380 Proto::DebugFilenameGet::Request request; 00381 Proto::DebugFilenameGet::Response response; 00382 server.DebugFilenameGet(request, response); 00383 std::cout << response.filename() << std::endl; 00384 return 0; 00385 } else if (options.at(2) == "set") { 00386 std::string value = options.at(3); 00387 options.done(); 00388 Proto::DebugFilenameSet::Request request; 00389 Proto::DebugFilenameSet::Response response; 00390 request.set_filename(value); 00391 server.DebugFilenameSet(request, response); 00392 if (response.has_error()) 00393 error(response.error()); 00394 return 0; 00395 } 00396 } else if (options.at(1) == "policy") { 00397 if (options.at(2) == "get") { 00398 options.done(); 00399 Proto::DebugPolicyGet::Request request; 00400 Proto::DebugPolicyGet::Response response; 00401 server.DebugPolicyGet(request, response); 00402 std::cout << response.policy() << std::endl; 00403 return 0; 00404 } else if (options.at(2) == "set") { 00405 std::string value = options.at(3); 00406 options.done(); 00407 Proto::DebugPolicySet::Request request; 00408 Proto::DebugPolicySet::Response response; 00409 request.set_policy(value); 00410 server.DebugPolicySet(request, response); 00411 return 0; 00412 } 00413 } else if (options.at(1) == "rotate") { 00414 options.done(); 00415 Proto::DebugRotate::Request request; 00416 Proto::DebugRotate::Response response; 00417 server.DebugRotate(request, response); 00418 if (response.has_error()) 00419 error(response.error()); 00420 return 0; 00421 } 00422 } else if (options.at(0) == "snapshot") { 00423 using Proto::SnapshotCommand; 00424 if (options.at(1) == "start") { 00425 options.done(); 00426 server.snapshotControl(SnapshotCommand::START_SNAPSHOT); 00427 return 0; 00428 } else if (options.at(1) == "stop") { 00429 options.done(); 00430 server.snapshotControl(SnapshotCommand::STOP_SNAPSHOT); 00431 return 0; 00432 } else if (options.at(1) == "restart") { 00433 options.done(); 00434 server.snapshotControl(SnapshotCommand::RESTART_SNAPSHOT); 00435 return 0; 00436 } else if (options.at(1) == "inhibit") { 00437 if (options.at(2) == "get") { 00438 options.done(); 00439 Proto::SnapshotInhibitGet::Request request; 00440 Proto::SnapshotInhibitGet::Response response; 00441 server.SnapshotInhibitGet(request, response); 00442 std::chrono::nanoseconds ns(response.nanoseconds()); 00443 std::cout << ns << std::endl; 00444 return 0; 00445 } else if (options.at(2) == "set") { 00446 Proto::SnapshotInhibitSet::Request request; 00447 std::string time = options.remaining(3); 00448 if (time.empty()) 00449 time = "1week"; 00450 request.set_nanoseconds(parseNonNegativeDuration(time)); 00451 Proto::SnapshotInhibitSet::Response response; 00452 server.SnapshotInhibitSet(request, response); 00453 if (response.has_error()) 00454 error(response.error()); 00455 return 0; 00456 } else if (options.at(2) == "clear") { 00457 options.done(); 00458 Proto::SnapshotInhibitSet::Request request; 00459 request.set_nanoseconds(0); 00460 Proto::SnapshotInhibitSet::Response response; 00461 server.SnapshotInhibitSet(request, response); 00462 if (response.has_error()) 00463 error(response.error()); 00464 return 0; 00465 } 00466 } 00467 } else if (options.at(0) == "stats") { 00468 if (options.at(1) == "get") { 00469 options.done(); 00470 Proto::ServerStatsGet::Request request; 00471 Proto::ServerStatsGet::Response response; 00472 server.ServerStatsGet(request, response); 00473 std::cout << dumpString(response.server_stats()); 00474 return 0; 00475 } else if (options.at(1) == "dump") { 00476 options.done(); 00477 Proto::ServerStatsDump::Request request; 00478 Proto::ServerStatsDump::Response response; 00479 server.ServerStatsDump(request, response); 00480 return 0; 00481 } 00482 } 00483 options.usageError("Unknown command"); 00484 00485 } catch (const LogCabin::Client::Exception& e) { 00486 std::cerr << "Exiting due to LogCabin::Client::Exception: " 00487 << e.what() 00488 << std::endl; 00489 exit(1); 00490 } 00491 }