// Copyright (C) 2013-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #ifndef D_CONTROLLER_H #define D_CONTROLLER_H #include #include #include #include #include #include #include #include #include #include #include #include namespace isc { namespace process { /// @brief Exception thrown when the command line is invalid. /// Can be used to transmit negative messages too. class InvalidUsage : public isc::Exception { public: InvalidUsage(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Exception used to convey version info upwards. /// Since command line argument parsing is done as part of /// DControllerBase::launch(), it uses this exception to propagate /// version information up to main(), when command line argument /// -v, -V or -W is given. Can be used to transmit positive messages too. class VersionMessage : public isc::Exception { public: VersionMessage(const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Exception thrown when the controller launch fails. class LaunchError: public isc::Exception { public: LaunchError (const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Exception thrown when the application process fails. class ProcessInitError: public isc::Exception { public: ProcessInitError (const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Exception thrown when the application process encounters an /// operation in its event loop (i.e. run method). class ProcessRunError: public isc::Exception { public: ProcessRunError (const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Exception thrown when the controller encounters an operational error. class DControllerBaseError : public isc::Exception { public: DControllerBaseError (const char* file, size_t line, const char* what) : isc::Exception(file, line, what) { }; }; /// @brief Defines a shared pointer to DControllerBase. class DControllerBase; typedef boost::shared_ptr DControllerBasePtr; /// @brief Application Controller /// /// DControllerBase is an abstract singleton which provides the framework and /// services for managing an application process that implements the /// DProcessBase interface. It runs the process like a stand-alone, command /// line driven executable, which must be supplied a configuration file at /// startup. It coordinates command line argument parsing, process /// instantiation and initialization, and runtime control through external /// command and configuration event handling. /// It creates the IOService instance which is used for runtime control /// events and passes the IOService into the application process at process /// creation. /// It provides the callback handlers for command and configuration events /// which could be triggered by an external source. Such sources are intended /// to be registered with and monitored by the controller's IOService such that /// the appropriate handler can be invoked. /// /// DControllerBase provides dynamic configuration file reloading upon receipt /// of SIGHUP, and graceful shutdown upon receipt of either SIGINT or SIGTERM. /// /// NOTE: Derivations must supply their own static singleton instance method(s) /// for creating and fetching the instance. The base class declares the instance /// member in order for it to be available for static callback functions. class DControllerBase : public Daemon { public: /// @brief Constructor /// /// @param app_name is display name of the application under control. This /// name appears in log statements. /// @param bin_name is the name of the application executable. DControllerBase(const char* app_name, const char* bin_name); /// @brief Destructor virtual ~DControllerBase(); /// @brief returns Kea version on stdout and exit. /// redeclaration/redefinition. @ref isc::process::Daemon::getVersion() std::string getVersion(bool extended); /// @brief Acts as the primary entry point into the controller execution /// and provides the outermost application control logic: /// /// 1. parse command line arguments /// 2. instantiate and initialize the application process /// 3. load the configuration file /// 4. record the start timestamp /// 5. initialize signal handling /// 6. start and wait on the application process event loop /// 7. exit to the caller /// /// It is intended to be called from main() and be given the command line /// arguments. /// /// This function can be run in "test mode". It prevents initialization /// of module logger. This is used in unit tests which initialize logger /// in their main function. Such a logger uses environmental variables to /// control severity, verbosity etc. /// /// @param argc is the number of command line arguments supplied /// @param argv is the array of string (char *) command line arguments /// @param test_mode is a bool value which indicates if /// @c DControllerBase::launch should be run in the test mode (if true). /// This parameter doesn't have default value to force test implementers to /// enable test mode explicitly. /// /// @throw throws one of the following exceptions: /// InvalidUsage - Indicates invalid command line. /// ProcessInitError - Failed to create and initialize application /// process object. /// ProcessRunError - A fatal error occurred while in the application /// process event loop. /// @return The value from @c Daemon::getExitValue(). virtual int launch(int argc, char* argv[], const bool test_mode); /// @brief Instance method invoked by the configuration event handler and /// which processes the actual configuration update. Provides behavioral /// path for both integrated and stand-alone modes. The current /// implementation will merge the configuration update into the existing /// configuration and then invoke the application process' configure method. /// /// @param new_config is the new configuration /// /// @return returns an Element that contains the results of configuration /// update composed of an integer status value (0 means successful, /// non-zero means failure), and a string explanation of the outcome. virtual isc::data::ConstElementPtr updateConfig(isc::data::ConstElementPtr new_config); /// @brief Instance method invoked by the configuration event handler and /// which processes the actual configuration check. Provides behavioral /// path for both integrated and stand-alone modes. The current /// implementation will merge the configuration update into the existing /// configuration and then invoke the application process' configure method /// with a final rollback. /// /// @param new_config is the new configuration /// /// @return returns an Element that contains the results of configuration /// update composed of an integer status value (0 means successful, /// non-zero means failure), and a string explanation of the outcome. virtual isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr new_config); /// @brief Reconfigures the process from a configuration file /// /// By default the file is assumed to be a JSON text file whose contents /// include at least: /// /// @code /// { "": {} /// } /// /// where: /// module-name : is a label which uniquely identifies the /// configuration data for this controller's application /// /// module-config: a set of zero or more JSON elements which comprise /// the application's configuration values /// @endcode /// /// To translate the JSON content into Elements, @c parseFile() is called /// first. This virtual method provides derivations a means to parse the /// file content using an alternate parser. If it returns an empty pointer /// than the JSON parsing providing by Element::fromJSONFile() is called. /// /// Once parsed, the method extracts the set of configuration /// elements for the module-name that matches the controller's app_name_, /// looks for the loggers entry and, if present uses it to configure /// logging. It then passes that set into @c updateConfig() (or /// @c checkConfig()). /// /// The file may contain an arbitrary number of other modules. /// /// @return returns an Element that contains the results of configuration /// update composed of an integer status value (0 means successful, /// non-zero means failure), and a string explanation of the outcome. virtual isc::data::ConstElementPtr configFromFile(); /// @brief Fetches the name of the application under control. /// /// @return returns the controller service name string std::string getAppName() const { return (app_name_); } /// @brief Fetches the name of the application executable. /// /// @return returns the controller logger name string std::string getBinName() const { return (bin_name_); } /// @brief handler for version-get command /// /// This method handles the version-get command. It returns the basic and /// extended version. /// /// @param command (ignored) /// @param args (ignored) /// @return answer with version details. isc::data::ConstElementPtr versionGetHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for 'build-report' command /// /// This method handles build-report command. It returns the output printed /// by configure script which contains most compilation parameters. /// /// @param command (ignored) /// @param args (ignored) /// @return answer with build report isc::data::ConstElementPtr buildReportHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for config-get command /// /// This method handles the config-get command, which retrieves /// the current configuration and returns it in response. /// /// @param command (ignored) /// @param args (ignored) /// @return current configuration wrapped in a response isc::data::ConstElementPtr configGetHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for config-write command /// /// This handle processes write-config command, which writes the /// current configuration to disk. This command takes one optional /// parameter called filename. If specified, the current configuration /// will be written to that file. If not specified, the file used during /// Kea start-up will be used. To avoid any exploits, the path is /// always relative and .. is not allowed in the filename. This is /// a security measure against exploiting file writes remotely. /// /// @param command (ignored) /// @param args may contain optional string argument filename /// @return status of the configuration file write isc::data::ConstElementPtr configWriteHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for config-test command /// /// This method handles the config-test command, which checks /// configuration specified in args parameter. /// /// @param command (ignored) /// @param args configuration to be checked. /// @return status of the command isc::data::ConstElementPtr configTestHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for config-reload command /// /// This method handles the config-reload command, which reloads /// the configuration file. /// /// @param command (ignored) /// @param args (ignored) /// @return status of the command isc::data::ConstElementPtr configReloadHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for config-set command /// /// This method handles the config-set command, which loads /// configuration specified in args parameter. /// /// @param command (ignored) /// @param args configuration to be checked. /// @return status of the command isc::data::ConstElementPtr configSetHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for 'shutdown' command /// /// This method handles shutdown command. It initiates the shutdown procedure /// using CPL methods. /// @param command (ignored) /// @param args (ignored) /// @return answer confirming that the shutdown procedure is started isc::data::ConstElementPtr shutdownHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for server-tag-get command /// /// This method handles the server-tag-get command, which retrieves /// the current server tag and returns it in response. /// /// @param command (ignored) /// @param args (ignored) /// @return current configuration wrapped in a response isc::data::ConstElementPtr serverTagGetHandler(const std::string& command, isc::data::ConstElementPtr args); /// @brief handler for status-get command /// /// This method handles the status-get command, which retrieves /// the server process information i.e. the pid and returns it in /// response. /// /// @param command (ignored) /// @param args (ignored) /// @return process information wrapped in a response isc::data::ConstElementPtr statusGetHandler(const std::string& command, isc::data::ConstElementPtr args); protected: /// @brief Virtual method that provides derivations the opportunity to /// support additional command line options. It is invoked during command /// line argument parsing (see parseArgs method) if the option is not /// recognized as a stock DControllerBase option. /// /// @param option is the option "character" from the command line, without /// any prefixing hyphen(s) /// @param optarg is the argument value (if one) associated with the option /// /// @return must return true if the option was valid, false if it is /// invalid. (Note the default implementation always returns false.) virtual bool customOption(int option, char *optarg); /// @brief Abstract method that is responsible for instantiating the /// application process object. It is invoked by the controller after /// command line argument parsing as part of the process initialization /// (see initProcess method). /// /// @return returns a pointer to the new process object (DProcessBase*) /// or NULL if the create fails. /// Note this value is subsequently wrapped in a smart pointer. virtual DProcessBase* createProcess() = 0; /// @brief Virtual method which can be used to contribute derivation /// specific usage text. It is invoked by the usage() method under /// invalid usage conditions. /// /// @return returns the desired text. virtual const std::string getUsageText() const { return (""); } /// @brief Virtual method which returns a string containing the option /// letters for any custom command line options supported by the derivation. /// These are added to the stock options of "c", "d", ..., during command /// line interpretation. /// /// @return returns a string containing the custom option letters. virtual const std::string getCustomOpts() const { return (""); } /// @brief Check the configuration /// /// Called by @c launch() when @c check_only_ mode is enabled /// @throw VersionMessage when successful but a message should be displayed /// @throw InvalidUsage when an error was detected void checkConfigOnly(); /// @brief Application-level signal processing method. /// /// This method is the last step in processing a OS signal occurrence. /// It currently supports the following signals as follows: /// -# SIGHUP - instigates reloading the configuration file /// -# SIGINT - instigates a graceful shutdown /// -# SIGTERM - instigates a graceful shutdown /// If it receives any other signal, it will issue a debug statement and /// discard it. /// Derivations wishing to support additional signals could override this /// method with one that: processes the signal if it is one of additional /// signals, otherwise invoke this method (DControllerBase::processSignal()) /// with the signal value. /// @todo Provide a convenient way for derivations to register additional /// signals. virtual void processSignal(int signum); /// @brief Supplies whether or not verbose logging is enabled. /// /// @return returns true if verbose logging is enabled. bool isVerbose() const { return (verbose_); } /// @brief Method for enabling or disabling verbose logging. /// /// @param value is the new value to assign the flag. void setVerbose(bool value) { verbose_ = value; } /// @brief Supplies whether or not check only mode is enabled. /// /// @return returns true if check only is enabled. bool isCheckOnly() const { return (check_only_); } /// @brief Method for enabling or disabling check only mode. /// /// @todo this method and @c setVerbose are currently not used. /// /// @param value is the new value to assign the flag. void setCheckOnly(bool value) { check_only_ = value; } /// @brief Getter for fetching the controller's IOService /// /// @return returns a pointer reference to the IOService. asiolink::IOServicePtr& getIOService() { return (io_service_); } /// @brief Static getter which returns the singleton instance. /// /// @return returns a pointer reference to the private singleton instance /// member. static DControllerBasePtr& getController() { return (controller_); } /// @brief Static setter which sets the singleton instance. /// /// @param controller is a pointer to the singleton instance. /// /// @throw throws DControllerBase error if an attempt is made to set the /// instance a second time. static void setController(const DControllerBasePtr& controller); /// @brief Processes the command line arguments. It is the first step /// taken after the controller has been launched. It combines the stock /// list of options with those returned by getCustomOpts(), and uses /// cstdlib's getopt to loop through the command line. /// It handles stock options directly, and passes any custom options into /// the customOption method. Currently there are only some stock options /// -c/t for specifying the configuration file, -d for verbose logging, /// and -v/V/W for version reports. /// /// @param argc is the number of command line arguments supplied /// @param argv is the array of string (char *) command line arguments /// /// @throw InvalidUsage when there are usage errors. /// @throw VersionMessage if the -v, -V or -W arguments is given. void parseArgs(int argc, char* argv[]); ///@brief Parse a given file into Elements /// /// This method provides a means for deriving classes to use alternate /// parsing mechanisms to parse configuration files into the corresponding /// isc::data::Elements. The elements produced must be equivalent to those /// which would be produced by the original JSON parsing. Implementations /// should throw when encountering errors. /// /// The default implementation returns an empty pointer, signifying to /// callers that they should submit the file to the original parser. /// /// @param file_name pathname of the file to parse /// /// @return pointer to the elements created /// virtual isc::data::ConstElementPtr parseFile(const std::string& file_name); ///@brief Parse text into Elements /// /// This method provides a means for deriving classes to use alternate /// parsing mechanisms to parse configuration text into the corresponding /// isc::data::Elements. The elements produced must be equivalent to those /// which would be produced by the original JSON parsing. Implementations /// should throw when encountering errors. /// /// The default implementation returns an empty pointer, signifying to /// callers that they should submit the text to the original parser. /// /// @param input text to parse /// /// @return pointer to the elements created /// virtual isc::data::ConstElementPtr parseText(const std::string& input) { static_cast(input); // just tu shut up the unused parameter warning isc::data::ConstElementPtr elements; return (elements); } /// @brief Instantiates the application process and then initializes it. /// This is the second step taken during launch, following successful /// command line parsing. It is used to invoke the derivation-specific /// implementation of createProcess, following by an invoking of the /// newly instantiated process's init method. /// /// @throw throws DControllerBaseError or indirectly DProcessBaseError /// if there is a failure creating or initializing the application process. void initProcess(); /// @brief Invokes the application process's event loop,(DBaseProcess::run). /// It is called during launch only after successfully completing the /// requested setup: command line parsing, application initialization, /// and session establishment (if not stand-alone). /// The process event loop is expected to only return upon application /// shutdown either in response to the shutdown command or due to an /// unrecoverable error. /// // @throw throws DControllerBaseError or indirectly DProcessBaseError void runProcess(); /// @brief Initiates shutdown procedure. This method is invoked /// by executeCommand in response to the shutdown command. It will invoke /// the application process's shutdown method which causes the process to /// to begin its shutdown process. /// /// Note, it is assumed that the process of shutting down is neither /// instantaneous nor synchronous. This method does not "block" waiting /// until the process has halted. Rather it is used to convey the /// need to shutdown. A successful return indicates that the shutdown /// has successfully commenced, but does not indicate that the process /// has actually exited. /// /// @return returns an Element that contains the results of shutdown /// command composed of an integer status value (0 means successful, /// non-zero means failure), and a string explanation of the outcome. /// /// @param args is a set of derivation-specific arguments (if any) /// for the shutdown command. isc::data::ConstElementPtr shutdownProcess(isc::data::ConstElementPtr args); /// @brief Initializes signal handling /// /// This method configures the controller to catch and handle signals. /// It instantiates a IOSignalSet which listens for SIGHUP, SIGINT, and /// SIGTERM. void initSignalHandling(); /// @brief Fetches the current process /// /// @return a pointer to the current process instance. DProcessBasePtr getProcess() { return (process_); } /// @brief Prints the program usage text to std error. /// /// @param text is a string message which will preceded the usage text. /// This is intended to be used for specific usage violation messages. void usage(const std::string& text); /// @brief Fetches text containing additional version specifics /// /// This method is provided so derivations can append any additional /// desired information such as library dependencies to the extended /// version text returned when DControllerBase::getVersion(true) is /// invoked. /// @return a string containing additional version info virtual std::string getVersionAddendum() { return (""); } /// @brief Deals with other (i.e. not application name) global objects. /// /// Code shared between configuration handlers: /// - check obsolete or unknown (aka unsupported) objects. /// /// @param args Command arguments. /// @return Error message or empty string. std::string handleOtherObjects(isc::data::ConstElementPtr args); private: /// @brief Name of the service under control. /// This name is used as the configuration module name and appears in log /// statements. std::string app_name_; /// @brief Name of the service executable. /// By convention this matches the executable name. It is also used to /// establish the logger name. std::string bin_name_; /// @brief Indicates if the verbose logging mode is enabled. bool verbose_; /// @brief Indicates if the check only mode for the configuration /// is enabled (usually specified by the command line -t argument). bool check_only_; /// @brief Pointer to the instance of the process. /// /// This is required for config and command handlers to gain access to /// the process DProcessBasePtr process_; /// @brief Shared pointer to an IOService object, used for ASIO operations. isc::asiolink::IOServicePtr io_service_; /// @brief ASIO signal set. isc::asiolink::IOSignalSetPtr io_signal_set_; /// @brief Singleton instance value. static DControllerBasePtr controller_; // DControllerTest is named a friend class to facilitate unit testing while // leaving the intended member scopes intact. friend class DControllerTest; }; } // namespace isc::process } // namespace isc #endif