LogCabin
Client/ServerControl.cc
Go to the documentation of this file.
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 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines