LogCabin
Server/Main.cc
Go to the documentation of this file.
00001 /* Copyright (c) 2012 Stanford University
00002  * Copyright (c) 2014 Diego Ongaro
00003  *
00004  * Permission to use, copy, modify, and distribute this software for any
00005  * purpose with or without fee is hereby granted, provided that the above
00006  * copyright notice and this permission notice appear in all copies.
00007  *
00008  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR(S) DISCLAIM ALL WARRANTIES
00009  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
00010  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL AUTHORS BE LIABLE FOR
00011  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
00012  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
00013  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
00014  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
00015  */
00016 
00017 #include <getopt.h>
00018 #include <unistd.h>
00019 
00020 #include <iostream>
00021 #include <string>
00022 
00023 #include "Core/Debug.h"
00024 #include "Core/StringUtil.h"
00025 #include "Core/ThreadId.h"
00026 #include "Core/Util.h"
00027 #include "Server/Globals.h"
00028 #include "Server/RaftConsensus.h"
00029 
00030 namespace {
00031 
00032 /**
00033  * Parses argv for the main function.
00034  */
00035 class OptionParser {
00036   public:
00037     OptionParser(int& argc, char**& argv)
00038         : argc(argc)
00039         , argv(argv)
00040         , bootstrap(false)
00041         , configFilename("logcabin.conf")
00042         , daemon(false)
00043         , debugLogFilename() // empty for default
00044         , pidFilename() // empty for none
00045         , testConfig(false)
00046     {
00047         while (true) {
00048             static struct option longOptions[] = {
00049                {"bootstrap",  no_argument, NULL, 'b'},
00050                {"config",  required_argument, NULL, 'c'},
00051                {"daemon",  no_argument, NULL, 'd'},
00052                {"help",  no_argument, NULL, 'h'},
00053                {"log",  required_argument, NULL, 'l'},
00054                {"pidfile",  required_argument, NULL, 'p'},
00055                {"test",  no_argument, NULL, 't'},
00056                {0, 0, 0, 0}
00057             };
00058             int c = getopt_long(argc, argv, "bc:dhl:p:t", 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 'b':
00069                     bootstrap = true;
00070                     break;
00071                 case 'c':
00072                     configFilename = optarg;
00073                     break;
00074                 case 'd':
00075                     daemon = true;
00076                     break;
00077                 case 'l':
00078                     debugLogFilename = optarg;
00079                     break;
00080                 case 'p':
00081                     pidFilename = optarg;
00082                     break;
00083                 case 't':
00084                     testConfig = true;
00085                     break;
00086                 case '?':
00087                 default:
00088                     // getopt_long already printed an error message.
00089                     usage();
00090                     exit(1);
00091             }
00092         }
00093 
00094         // We don't expect any additional command line arguments (not options).
00095         if (optind != argc) {
00096             usage();
00097             exit(1);
00098         }
00099     }
00100 
00101     void usage() {
00102         std::cout
00103             << "Runs a LogCabin server."
00104             << std::endl
00105             << std::endl
00106             << "This program was released in LogCabin v1.0.0."
00107             << std::endl
00108             << std::endl
00109 
00110             << "Usage: " << argv[0] << " [options]"
00111             << std::endl
00112             << std::endl
00113 
00114             << "Options:"
00115             << std::endl
00116 
00117             << "  --bootstrap                  "
00118             << "Write a cluster configuration into the very"
00119             << std::endl
00120             << "                               "
00121             << "first server's log and exit. This must only"
00122             << std::endl
00123             << "                               "
00124             << "be run once on a single server in each cluster."
00125             << std::endl
00126 
00127             << "  -c <file>, --config=<file>   "
00128             << "Set the path to the configuration file"
00129             << std::endl
00130             << "                               "
00131             << "[default: logcabin.conf]"
00132             << std::endl
00133 
00134             << "  -d, --daemon                 "
00135             << "Detach and run in the background"
00136             << std::endl
00137             << "                               "
00138             << "(requires --log)"
00139             << std::endl
00140 
00141             << "  -h, --help                   "
00142             << "Print this usage information"
00143             << std::endl
00144 
00145             << "  -l <file>, --log=<file>      "
00146             << "Write debug logs to <file> instead of stderr"
00147             << std::endl
00148 
00149             << "  -p <file>, --pidfile=<file>  "
00150             << "Write process ID to <file>"
00151             << std::endl
00152 
00153             << "  -t, --test                   "
00154             << "Check the configuration file for basic errors"
00155             << std::endl
00156             << "                               "
00157             << "and exit"
00158             << std::endl
00159             << std::endl
00160 
00161             << "Signals:"
00162             << std::endl
00163 
00164             << "  SIGUSR1                      "
00165             << "Dump ServerStats to debug log (experimental)"
00166             << std::endl
00167 
00168             << "  SIGUSR2                      "
00169             << "Reopen the debug log file"
00170             << std::endl;
00171     }
00172 
00173     int& argc;
00174     char**& argv;
00175     bool bootstrap;
00176     std::string configFilename;
00177     bool daemon;
00178     std::string debugLogFilename;
00179     std::string pidFilename;
00180     bool testConfig;
00181 };
00182 
00183 /**
00184  * RAII-style class to manage a file containing the process ID.
00185  */
00186 class PidFile {
00187   public:
00188     explicit PidFile(const std::string& filename)
00189         : filename(filename)
00190         , written(-1)
00191     {
00192     }
00193 
00194     ~PidFile() {
00195         removeFile();
00196     }
00197 
00198     void writePid(int pid) {
00199         if (filename.empty())
00200             return;
00201         FILE* file = fopen(filename.c_str(), "w");
00202         if (file == NULL) {
00203             PANIC("Could not open %s for writing process ID: %s",
00204                   filename.c_str(),
00205                   strerror(errno));
00206         }
00207         std::string pidString =
00208             LogCabin::Core::StringUtil::format("%d\n", pid);
00209         size_t bytesWritten =
00210             fwrite(pidString.c_str(), 1, pidString.size(), file);
00211         if (bytesWritten != pidString.size()) {
00212             PANIC("Could not write process ID %s to pidfile %s: %s",
00213                   pidString.c_str(), filename.c_str(),
00214                   strerror(errno));
00215         }
00216         int r = fclose(file);
00217         if (r != 0) {
00218             PANIC("Could not close pidfile %s: %s",
00219                   filename.c_str(),
00220                   strerror(errno));
00221         }
00222         NOTICE("Wrote PID %d to %s",
00223                pid, filename.c_str());
00224         written = pid;
00225     }
00226 
00227     void removeFile() {
00228         if (written < 0)
00229             return;
00230         FILE* file = fopen(filename.c_str(), "r");
00231         if (file == NULL) {
00232             WARNING("Could not open %s for reading process ID prior to "
00233                     "removal: %s",
00234                     filename.c_str(),
00235                     strerror(errno));
00236             return;
00237         }
00238         char readbuf[10];
00239         memset(readbuf, 0, sizeof(readbuf));
00240         size_t bytesRead = fread(readbuf, 1, sizeof(readbuf), file);
00241         if (bytesRead == 0) {
00242             WARNING("PID could not be read from pidfile: "
00243                     "will not remove file %s",
00244                     filename.c_str());
00245             fclose(file);
00246             return;
00247         }
00248         int pidRead = atoi(readbuf);
00249         if (pidRead != written) {
00250             WARNING("PID read from pidfile (%d) does not match PID written "
00251                     "earlier (%d): will not remove file %s",
00252                     pidRead, written, filename.c_str());
00253             fclose(file);
00254             return;
00255         }
00256         int r = unlink(filename.c_str());
00257         if (r != 0) {
00258             WARNING("Could not unlink %s: %s",
00259                     filename.c_str(), strerror(errno));
00260             fclose(file);
00261             return;
00262         }
00263         written = -1;
00264         fclose(file);
00265         NOTICE("Removed pidfile %s", filename.c_str());
00266     }
00267 
00268     std::string filename;
00269     int written;
00270 };
00271 
00272 } // anonymous namespace
00273 
00274 int
00275 main(int argc, char** argv)
00276 {
00277     using namespace LogCabin;
00278 
00279     try {
00280         Core::ThreadId::setName("evloop");
00281 
00282         // Parse command line args.
00283         OptionParser options(argc, argv);
00284 
00285         if (options.testConfig) {
00286             Server::Globals globals;
00287             globals.config.readFile(options.configFilename.c_str());
00288             // The following settings are required, and Config::read() throws
00289             // an exception with an OK error message if they aren't found:
00290             globals.config.read<uint64_t>("serverId");
00291             globals.config.read<std::string>("listenAddresses");
00292             return 0;
00293         }
00294 
00295         // Set debug log file
00296         if (!options.debugLogFilename.empty()) {
00297             std::string error =
00298                 Core::Debug::setLogFilename(options.debugLogFilename);
00299             if (!error.empty()) {
00300                 ERROR("Failed to set debug log file: %s",
00301                       error.c_str());
00302             }
00303         }
00304 
00305         NOTICE("Using config file %s", options.configFilename.c_str());
00306 
00307         // Detach as daemon
00308         if (options.daemon) {
00309             if (options.debugLogFilename.empty()) {
00310                 ERROR("Refusing to run as daemon without a log file "
00311                       "(use /dev/null if you insist)");
00312             }
00313             NOTICE("Detaching");
00314             bool chdir = false; // leave the current working directory in case
00315                                 // the user has specified relative paths for
00316                                 // the config file, etc
00317             bool close = true;  // close stdin, stdout, stderr
00318             if (daemon(!chdir, !close) != 0) {
00319                 PANIC("Call to daemon() failed: %s", strerror(errno));
00320             }
00321             int pid = getpid();
00322             Core::Debug::processName = Core::StringUtil::format("%d", pid);
00323             NOTICE("Detached as daemon with pid %d", pid);
00324         }
00325 
00326         // Write PID file, removed upon destruction
00327         PidFile pidFile(options.pidFilename);
00328         pidFile.writePid(getpid());
00329 
00330         {
00331             // Initialize and run Globals.
00332             Server::Globals globals;
00333             globals.config.readFile(options.configFilename.c_str());
00334 
00335             // Set debug log policy.
00336             // A few log messages above already got through; oh well.
00337             Core::Debug::setLogPolicy(
00338                 Core::Debug::logPolicyFromString(
00339                     globals.config.read<std::string>("logPolicy", "NOTICE")));
00340 
00341             NOTICE("Config file settings:\n"
00342                    "# begin config\n"
00343                    "%s"
00344                    "# end config",
00345                    Core::StringUtil::toString(globals.config).c_str());
00346             globals.init();
00347             if (options.bootstrap) {
00348                 globals.raft->bootstrapConfiguration();
00349                 NOTICE("Done bootstrapping configuration. Exiting.");
00350             } else {
00351                 globals.leaveSignalsBlocked();
00352                 globals.run();
00353             }
00354         }
00355 
00356         google::protobuf::ShutdownProtobufLibrary();
00357         return 0;
00358 
00359     } catch (const Core::Config::Exception& e) {
00360         ERROR("Fatal exception from config file: %s",
00361               e.what());
00362     }
00363 }
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines