Parcourir la source

[master] Merged trac5152 (check D2 and CA config)

Francis Dupont il y a 8 ans
Parent
commit
7c349367bb

Fichier diff supprimé car celui-ci est trop grand
+ 597 - 592
doc/guide/ddns.xml


+ 2 - 1
src/bin/agent/ca_process.h

@@ -84,7 +84,8 @@ public:
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     virtual isc::data::ConstElementPtr
-    configure(isc::data::ConstElementPtr config_set, bool check_only);
+    configure(isc::data::ConstElementPtr config_set,
+              bool check_only = false);
 
     /// @brief Processes the given command.
     ///

+ 12 - 1
src/bin/agent/kea-ctrl-agent.xml

@@ -52,7 +52,8 @@
       <arg><option>-V</option></arg>
       <arg><option>-W</option></arg>
       <arg><option>-d</option></arg>
-      <arg><option>-s</option></arg>
+      <arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
+      <arg><option>-t<replaceable class="parameter">config-file</replaceable></option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
@@ -115,6 +116,16 @@
         </para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>-t</option></term>
+        <listitem><para>
+          Check the syntax of the configuration file and report the
+          first error if any. Note that not all parameters are
+          completely checked, in particular, service and client
+          sockets are not opened, and hook libraries are not loaded.
+        </para></listitem>
+      </varlistentry>
+
     </variablelist>
   </refsect1>
 

+ 11 - 2
src/bin/agent/main.cc

@@ -25,9 +25,18 @@ int main(int argc, char* argv[]) {
         // 'false' value disables test mode.
         controller->launch(argc, argv, false);
     } catch (const VersionMessage& ex) {
-        std::cout << ex.what() << std::endl;
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cout << msg << std::endl;
+        }
+    } catch (const InvalidUsage& ex) {
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cerr << msg << std::endl;
+        }
+        ret = EXIT_FAILURE;
     } catch (const isc::Exception& ex) {
-        std::cerr << "Service failed:" << ex.what() << std::endl;
+        std::cerr << "Service failed: " << ex.what() << std::endl;
         ret = EXIT_FAILURE;
     }
 

+ 47 - 2
src/bin/agent/tests/ca_process_tests.sh.in

@@ -1,4 +1,4 @@
-# Copyright (C) 2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2016-2017 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
@@ -34,12 +34,55 @@ CONFIG="{
     }
 }"
 
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+CONFIG_BAD_SYNTAX="{
+    \"Control-agent\":
+    {
+        \"http-port\": BOGUS
+    }
+}"
+
+# Invalid configuration (out of range port) to check that Kea can check syntax.
+CONFIG_BAD_VALUE="{
+    \"Control-agent\":
+    {
+        \"http-port\": 80000
+    }
+}"
+
 bin="kea-ctrl-agent"
 bin_path=@abs_top_builddir@/src/bin/agent
 
 # Import common test library.
 . @abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh
 
+# This test verifies that syntax checking works properly. This function
+# requires 3 parameters:
+# testname
+# config - string with a content of the config (will be written to a file)
+# exp_code - expected exit code returned by kea (0 - success, 1 - failure)
+syntax_check_test() {
+    local TESTNAME="${1}"
+    local CONFIG="${2}"
+    local EXP_CODE="${3}"
+
+    # Log the start of the test and print test name.
+    test_start $TESTNAME
+    # Remove dangling Kea instances and remove log files.
+    cleanup
+    # Create correct configuration file.
+    create_config "${CONFIG}"
+    # Check it
+    printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
+    ${bin_path}/${bin} -t ${CFG_FILE}
+    exit_code=$?
+    if [ ${exit_code} -ne $EXP_CODE ]; then
+        printf "ERROR: expected exit code ${EXP_CODE}, got ${exit_code}\n"
+        clean_exit 1
+    fi
+    test_finish 0
+}
+
 # This test verifies that Control Agent is shut down gracefully when it
 # receives a SIGINT or SIGTERM signal.
 shutdown_test() {
@@ -102,4 +145,6 @@ shutdown_test() {
 server_pid_file_test "${CONFIG}" DCTL_ALREADY_RUNNING
 shutdown_test "ctrl-agent.sigterm_test" 15
 shutdown_test "ctrl-agent.sigint_test" 2
-
+syntax_check_test "ctrl-agent.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "ctrl-agent.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "ctrl-agent.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1

+ 1 - 1
src/bin/d2/d2_messages.mes

@@ -37,7 +37,7 @@ to shutdown and has met the required criteria to exit.
 This is a debug message issued when the DHCP-DDNS application command method
 has been invoked.
 
-% DHCP_DDNS_CONFIGURE configuration update received: %1
+% DHCP_DDNS_CONFIGURE configuration %1 received: %2
 This is a debug message issued when the DHCP-DDNS application configure method
 has been invoked.
 

+ 6 - 5
src/bin/d2/d2_process.cc

@@ -192,17 +192,18 @@ D2Process::shutdown(isc::data::ConstElementPtr args) {
 
 isc::data::ConstElementPtr
 D2Process::configure(isc::data::ConstElementPtr config_set, bool check_only) {
-    LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC,
-              DHCP_DDNS_CONFIGURE).arg(config_set->str());
+    LOG_DEBUG(d2_logger, DBGLVL_TRACE_BASIC, DHCP_DDNS_CONFIGURE)
+        .arg(check_only ? "check" : "update")
+        .arg(config_set->str());
 
-    /// @todo: Implement this eventually.
+    isc::data::ConstElementPtr answer;
+    answer = getCfgMgr()->parseConfig(config_set, check_only);;
     if (check_only) {
-        return (isc::config::createAnswer(0, "Configuration check is not supported by D2."));
+        return (answer);
     }
 
     int rcode = 0;
     isc::data::ConstElementPtr comment;
-    isc::data::ConstElementPtr answer = getCfgMgr()->parseConfig(config_set);;
     comment = isc::config::parseAnswer(rcode, answer);
 
     if (rcode) {

+ 2 - 1
src/bin/d2/d2_process.h

@@ -158,7 +158,8 @@ public:
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     virtual isc::data::ConstElementPtr
-    configure(isc::data::ConstElementPtr config_set, bool check_only);
+    configure(isc::data::ConstElementPtr config_set,
+              bool check_only = false);
 
     /// @brief Processes the given command.
     ///

+ 11 - 11
src/bin/d2/kea-dhcp-ddns.xml

@@ -2,7 +2,7 @@
                "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"
                [<!ENTITY mdash "&#8212;">]>
 <!--
- - Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+ - Copyright (C) 2013-2017 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
@@ -52,8 +52,8 @@
       <arg><option>-V</option></arg>
       <arg><option>-W</option></arg>
       <arg><option>-d</option></arg>
-      <!-- not yet <arg><option>-t</option></arg> -->
-      <arg><option>-c</option></arg>
+      <arg><option>-c<replaceable class="parameter">config-file</replaceable></option></arg>
+      <arg><option>-t<replaceable class="parameter">config-file</replaceable></option></arg>
     </cmdsynopsis>
   </refsynopsisdiv>
 
@@ -105,21 +105,21 @@
         </para></listitem>
       </varlistentry>
 
-<!-- not yet
       <varlistentry>
-        <term><option>-t</option></term>
+        <term><option>-c</option></term>
         <listitem><para>
-          Check the syntax of the configuration file and report the first
-          error if any.
+          Configuration file including the configuration for DHCP-DDNS server.
+          It may also contain configuration entries for other Kea services.
         </para></listitem>
       </varlistentry>
--->
 
       <varlistentry>
-        <term><option>-c</option></term>
+        <term><option>-t</option></term>
         <listitem><para>
-          Configuration file including the configuration for DHCP-DDNS server.
-          It may also contain configuration entries for other Kea services.
+          Check the syntax of the configuration file and report the
+          first error if any. Note that not all parameters are
+          completely checked, in particular, service socket is
+          not opened.
         </para></listitem>
       </varlistentry>
 

+ 12 - 3
src/bin/d2/main.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 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
@@ -34,9 +34,18 @@ int main(int argc, char* argv[]) {
         // 'false' value disables test mode.
         controller->launch(argc, argv, false);
     } catch (const VersionMessage& ex) {
-        std::cout << ex.what() << std::endl;
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cout << msg << std::endl;
+        }
+    } catch (const InvalidUsage& ex) {
+        std::string msg(ex.what());
+        if (!msg.empty()) {
+            std::cerr << msg << std::endl;
+        }
+        ret = EXIT_FAILURE;
     } catch (const isc::Exception& ex) {
-        std::cerr << "Service failed:" << ex.what() << std::endl;
+        std::cerr << "Service failed: " << ex.what() << std::endl;
         ret = EXIT_FAILURE;
     }
 

+ 3 - 2
src/bin/d2/tests/d2_cfg_mgr_unittests.cc

@@ -168,7 +168,8 @@ public:
 
         // The JSON parsed ok and we've added the defaults, pass the config
         // into the Element parser and check for the expected outcome.
-        data::ConstElementPtr answer = cfg_mgr_->parseConfig(config_set_);
+        data::ConstElementPtr answer;
+        answer = cfg_mgr_->parseConfig(config_set_, false);
 
         // Extract the result and error text from the answer.
         int rcode = 0;
@@ -601,7 +602,7 @@ TEST_F(D2CfgMgrTest, fullConfig) {
 
     // Verify that parsing the exact same configuration a second time
     // does not cause a duplicate value errors.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     ASSERT_TRUE(checkAnswer(0));
 }
 

+ 10 - 0
src/bin/d2/tests/d2_controller_unittests.cc

@@ -181,12 +181,22 @@ TEST_F(D2ControllerTest, configUpdateTests) {
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);
 
+    // Verify that given a valid config we get a successful check result.
+    answer = checkConfig(config_set);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(0, rcode);
+
     // Use an invalid configuration to verify parsing error return.
     std::string config = "{ \"bogus\": 1000 } ";
     config_set = isc::data::Element::fromJSON(config);
     answer = updateConfig(config_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(1, rcode);
+
+    // Use an invalid configuration to verify checking error return.
+    answer = checkConfig(config_set);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(1, rcode);
 }
 
 /// @brief Command execution tests.

+ 84 - 2
src/bin/d2/tests/d2_process_tests.sh.in

@@ -1,4 +1,4 @@
-# Copyright (C) 2014-2016 Internet Systems Consortium, Inc. ("ISC")
+# Copyright (C) 2014-2017 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
@@ -36,7 +36,59 @@ CONFIG="{
     }
 }"
 
-# Invalid configuration (invalid port) to check that D2
+# Invalid configuration (syntax error) to check that Kea can check syntax.
+CONFIG_BAD_SYNTAX="{
+    \"DhcpDdns\":
+    {
+        \"ip-address\": \"127.0.0.1\",
+        \"port\": BOGUS,
+        \"tsig-keys\": [],
+        \"forward-ddns\" : {},
+        \"reverse-ddns\" : {}
+    },
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp-ddns\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
+    }
+}"
+
+# Invalid configuration (out of range port) to check that Kea can check syntax.
+CONFIG_BAD_VALUE="{
+    \"DhcpDdns\":
+    {
+        \"ip-address\": \"127.0.0.1\",
+        \"port\": 80000,
+        \"tsig-keys\": [],
+        \"forward-ddns\" : {},
+        \"reverse-ddns\" : {}
+    },
+    \"Logging\":
+    {
+        \"loggers\": [
+        {
+            \"name\": \"kea-dhcp-ddns\",
+            \"output_options\": [
+                {
+                    \"output\": \"$LOG_FILE\"
+                }
+            ],
+            \"severity\": \"INFO\"
+        }
+        ]
+    }
+}"
+
+# Invalid value configuration (invalid port) to check that D2
 # gracefully handles reconfiguration errors.
 CONFIG_INVALID="{
     \"DhcpDdns\":
@@ -70,6 +122,33 @@ bin_path=@abs_top_builddir@/src/bin/d2
 # Import common test library.
 . @abs_top_builddir@/src/lib/testutils/dhcp_test_lib.sh
 
+# This test verifies that syntax checking works properly. This function
+# requires 3 parameters:
+# testname
+# config - string with a content of the config (will be written to a file)
+# exp_code - expected exit code returned by kea (0 - success, 1 - failure)
+syntax_check_test() {
+    local TESTNAME="${1}"
+    local CONFIG="${2}"
+    local EXP_CODE="${3}"
+
+    # Log the start of the test and print test name.
+    test_start $TESTNAME
+    # Remove dangling Kea instances and remove log files.
+    cleanup
+    # Create correct configuration file.
+    create_config "${CONFIG}"
+    # Check it
+    printf "Running command %s.\n" "\"${bin_path}/${bin} -t ${CFG_FILE}\""
+    ${bin_path}/${bin} -t ${CFG_FILE}
+    exit_code=$?
+    if [ ${exit_code} -ne $EXP_CODE ]; then
+        printf "ERROR: expected exit code ${EXP_CODE}, got ${exit_code}\n"
+        clean_exit 1
+    fi
+    test_finish 0
+}
+
 # This test verifies that D2 can be reconfigured with a SIGHUP signal.
 dynamic_reconfiguration_test() {
     # Log the start of the test and print test name.
@@ -233,3 +312,6 @@ shutdown_test "dhcp-ddns.sigterm_test" 15
 shutdown_test "dhcp-ddns.sigint_test" 2
 version_test "dhcp-ddns.version"
 logger_vars_test "dhcp-ddns.variables"
+syntax_check_test "dhcp-ddns.syntax_check_success" "${CONFIG}" 0
+syntax_check_test "dhcp-ddns.syntax_check_bad_syntax" "${CONFIG_BAD_SYNTAX}" 1
+syntax_check_test "dhcp-ddns.syntax_check_bad_values" "${CONFIG_BAD_VALUE}" 1

+ 1 - 1
src/bin/d2/tests/get_config_unittest.cc

@@ -152,7 +152,7 @@ public:
         // try DHCPDDNS configure
         ConstElementPtr status;
         try {
-            status = srv_->parseConfig(d2);
+            status = srv_->parseConfig(d2, false);
         } catch (const std::exception& ex) {
             ADD_FAILURE() << "configure for " << operation
                           << " failed with " << ex.what()

+ 17 - 4
src/lib/process/d_cfg_mgr.cc

@@ -122,7 +122,8 @@ DCfgMgrBase::setContext(DCfgContextBasePtr& context) {
 }
 
 isc::data::ConstElementPtr
-DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
+DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set,
+                         bool check_only) {
     LOG_DEBUG(dctl_logger, DBGLVL_COMMAND,
                 DCTL_CONFIG_START).arg(config_set->str());
 
@@ -245,9 +246,15 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
         }
 
         // Everything was fine. Configuration set processed successfully.
-        LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
-        answer = isc::config::createAnswer(0, "Configuration committed.");
-
+        if (!check_only) {
+            LOG_INFO(dctl_logger, DCTL_CONFIG_COMPLETE).arg(getConfigSummary(0));
+            answer = isc::config::createAnswer(0, "Configuration committed.");
+        } else {
+            answer = isc::config::createAnswer(0, "Configuration seems sane.");
+            LOG_INFO(dctl_logger, DCTL_CONFIG_CHECK_COMPLETE)
+                .arg(getConfigSummary(0))
+                .arg(config::answerToText(answer));
+        }
     } catch (const std::exception& ex) {
         LOG_ERROR(dctl_logger, DCTL_PARSER_FAIL).arg(ex.what());
         answer = isc::config::createAnswer(1, ex.what());
@@ -257,6 +264,12 @@ DCfgMgrBase::parseConfig(isc::data::ConstElementPtr config_set) {
         return (answer);
     }
 
+    if (check_only) {
+        // If this is a configuration check only, then don't actually apply
+        // the configuration and reverse to the previous one.
+        context_ = original_context;
+    }
+
     return (answer);
 }
 

+ 6 - 4
src/lib/process/d_cfg_mgr.h

@@ -246,7 +246,7 @@ typedef std::vector<std::string> ElementIdList;
 ///        update context with parsed results
 ///        break on error
 ///
-///    if an error occurred
+///    if an error occurred or this is only a check
 ///        restore configuration context from backup
 /// @endcode
 ///
@@ -281,7 +281,7 @@ typedef std::vector<std::string> ElementIdList;
 /// 1. implementation calls simpleParseConfig from its configure method.
 /// 2. simpleParseConfig makes a configuration context
 /// 3. parse method from the derived class is called
-/// 4. if the configuration was unsuccessful of this is only a check, the
+/// 4. if the configuration was unsuccessful or this is only a check, the
 ///    old context is reinstantiated. If not, the configuration is kept.
 ///
 /// See @ref isc::agent::CtrlAgentCfgMgr and @ref isc::agent::CtrlAgentProcess
@@ -303,12 +303,14 @@ public:
     /// the parsing as described in the class brief.
     ///
     /// @param config_set is a set of configuration elements to be parsed.
+    /// @param check_only true if the config is to be checked only, but not applied
     ///
     /// @return an Element that contains the results of configuration composed
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
-    isc::data::ConstElementPtr parseConfig(isc::data::ConstElementPtr
-                                           config_set);
+    isc::data::ConstElementPtr
+    parseConfig(isc::data::ConstElementPtr config_set,
+                bool check_only = false);
 
 
     /// @brief Acts as the receiver of new configurations.

+ 79 - 5
src/lib/process/d_controller.cc

@@ -37,7 +37,7 @@ DControllerBasePtr DControllerBase::controller_;
 // Note that the constructor instantiates the controller's primary IOService.
 DControllerBase::DControllerBase(const char* app_name, const char* bin_name)
     : app_name_(app_name), bin_name_(bin_name),
-      verbose_(false), spec_file_name_(""),
+      verbose_(false), check_only_(false), spec_file_name_(""),
       io_service_(new isc::asiolink::IOService()),
       io_signal_queue_() {
 }
@@ -68,11 +68,17 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
         parseArgs(argc, argv);
     } catch (const InvalidUsage& ex) {
         usage(ex.what());
-        throw; // rethrow it
+        // rethrow it with an empty message
+        isc_throw(InvalidUsage, "");
     }
 
     setProcName(bin_name_);
 
+    if (isCheckOnly()) {
+        checkConfigOnly();
+        return;
+    }
+
     // It is important that we set a default logger name because this name
     // will be used when the user doesn't provide the logging configuration
     // in the Kea configuration file.
@@ -147,15 +153,70 @@ DControllerBase::launch(int argc, char* argv[], const bool test_mode) {
 }
 
 void
+DControllerBase::checkConfigOnly() {
+    try {
+        // We need to initialize logging, in case any error
+        // messages are to be printed.
+        // This is just a test, so we don't care about lockfile.
+        setenv("KEA_LOCKFILE_DIR", "none", 0);
+        isc::dhcp::CfgMgr::instance().setDefaultLoggerName(bin_name_);
+        isc::dhcp::CfgMgr::instance().setVerbose(verbose_);
+        Daemon::loggerInit(bin_name_.c_str(), verbose_);
+
+        // Check the syntax first.
+        std::string config_file = getConfigFile();
+        if (config_file.empty()) {
+            // Basic sanity check: file name must not be empty.
+            isc_throw(InvalidUsage, "JSON configuration file not specified");
+        }
+        isc::data::ConstElementPtr whole_config = parseFile(config_file);
+        if (!whole_config) {
+            // No fallback to fromJSONFile
+            isc_throw(InvalidUsage, "No configuration found");
+        }
+        if (verbose_) {
+            std::cerr << "Syntax check OK" << std::endl;
+        }
+
+        // Check the logic next.
+        isc::data::ConstElementPtr module_config;
+        module_config = whole_config->get(getAppName());
+        if (!module_config) {
+            isc_throw(InvalidUsage, "Config file " << config_file <<
+                      " does not include '" << getAppName() << "' entry");
+        }
+
+        // Get an application process object.
+        initProcess();
+
+        isc::data::ConstElementPtr answer;
+        answer = checkConfig(module_config);
+        int rcode = 0;
+        answer = isc::config::parseAnswer(rcode, answer);
+        if (rcode != 0) {
+            isc_throw(InvalidUsage, "Error encountered: "
+                      << answer->stringValue());
+        }
+    } catch (const VersionMessage&) {
+        throw;
+    } catch (const InvalidUsage&) {
+        throw;
+    } catch (const std::exception& ex) {
+        isc_throw(InvalidUsage, "Syntax check failed with: " << ex.what());
+    }
+    return;
+}
+
+void
 DControllerBase::parseArgs(int argc, char* argv[])
 {
     // Iterate over the given command line options. If its a stock option
-    // ("s" or "v") handle it here.  If its a valid custom option, then
+    // ("c" or "d") handle it here.  If its a valid custom option, then
     // invoke customOption.
     int ch;
     opterr = 0;
     optind = 1;
-    std::string opts("dvVWc:" + getCustomOpts());
+    std::string opts("dvVWc:t:" + getCustomOpts());
     while ((ch = getopt(argc, argv, opts.c_str())) != -1) {
         switch (ch) {
         case 'd':
@@ -182,12 +243,17 @@ DControllerBase::parseArgs(int argc, char* argv[])
             break;
 
         case 'c':
+        case 't':
             // config file name
             if (optarg == NULL) {
                 isc_throw(InvalidUsage, "configuration file name missing");
             }
 
             setConfigFile(optarg);
+
+            if (ch == 't') {
+                check_only_ = true;
+            }
             break;
 
         case '?': {
@@ -333,6 +399,12 @@ DControllerBase::updateConfig(isc::data::ConstElementPtr new_config) {
     return (process_->configure(new_config, false));
 }
 
+// Instance method for checking new config
+isc::data::ConstElementPtr
+DControllerBase::checkConfig(isc::data::ConstElementPtr new_config) {
+    return (process_->configure(new_config, true));
+}
+
 
 // Instance method for executing commands
 isc::data::ConstElementPtr
@@ -468,7 +540,9 @@ DControllerBase::usage(const std::string & text)
               << std::endl
               << "  -d: optional, verbose output " << std::endl
               << "  -c <config file name> : mandatory,"
-              <<   " specifies name of configuration file " << std::endl;
+              << " specify name of configuration file" << std::endl
+              << "  -t <config file name> : check the"
+              << " configuration file and exit" << std::endl;
 
     // add any derivation specific usage
     std::cerr << getUsageText() << std::endl;

+ 57 - 13
src/lib/process/d_controller.h

@@ -24,6 +24,7 @@ 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) :
@@ -34,7 +35,7 @@ public:
 /// 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 or -V is given.
+/// -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) :
@@ -128,7 +129,7 @@ public:
     /// arguments.
     ///
     /// This function can be run in "test mode". It prevents initialization
-    /// of D2 module logger. This is used in unit tests which initialize logger
+    /// 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.
     ///
@@ -161,6 +162,21 @@ public:
     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
@@ -191,7 +207,7 @@ public:
     ///
     /// It then extracts the set of configuration elements for the
     /// module-name that matches the controller's app_name_ and passes that
-    /// set into @c updateConfig().
+    /// set into @c updateConfig() (or @c checkConfig()).
     ///
     /// The file may contain an arbitrary number of other modules.
     ///
@@ -223,10 +239,10 @@ public:
     /// @return an Element that contains the results of command composed
     /// of an integer status value and a string explanation of the outcome.
     /// The status value is one of the following:
-    ///   D2::COMMAND_SUCCESS - Command executed successfully
-    ///   D2::COMMAND_ERROR - Command is valid but suffered an operational
+    ///   COMMAND_SUCCESS - Command executed successfully
+    ///   COMMAND_ERROR - Command is valid but suffered an operational
     ///   failure.
-    ///   D2::COMMAND_INVALID - Command is not recognized as valid be either
+    ///   COMMAND_INVALID - Command is not recognized as valid be either
     ///   the controller or the application process.
     virtual isc::data::ConstElementPtr executeCommand(const std::string&
                                                       command,
@@ -283,10 +299,10 @@ protected:
     /// @return an Element that contains the results of command composed
     /// of an integer status value and a string explanation of the outcome.
     /// The status value is one of the following:
-    ///   D2::COMMAND_SUCCESS - Command executed successfully
-    ///   D2::COMMAND_ERROR - Command is valid but suffered an operational
+    ///   COMMAND_SUCCESS - Command executed successfully
+    ///   COMMAND_ERROR - Command is valid but suffered an operational
     ///   failure.
-    ///   D2::COMMAND_INVALID - Command is not recognized as a valid custom
+    ///   COMMAND_INVALID - Command is not recognized as a valid custom
     ///   controller command.
     virtual isc::data::ConstElementPtr customControllerCommand(
             const std::string& command, isc::data::ConstElementPtr args);
@@ -302,7 +318,7 @@ protected:
 
     /// @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" and "v" during command
+    /// These are added to the stock options of "c", "d", ..., during command
     /// line interpretation.
     ///
     /// @return returns a string containing the custom option letters.
@@ -310,6 +326,13 @@ protected:
         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
@@ -342,6 +365,22 @@ protected:
         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.
@@ -385,14 +424,15 @@ protected:
     /// 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 two stock options
-    /// -c for specifying the configuration file, and -v for verbose logging.
+    /// 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 or -V arguments is given.
+    /// @throw VersionMessage if the -v, -V or -W arguments is given.
     void parseArgs(int argc, char* argv[]);
 
 
@@ -536,6 +576,10 @@ private:
     /// @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 The absolute file name of the JSON spec file.
     std::string spec_file_name_;
 

+ 3 - 2
src/lib/process/d_process.h

@@ -102,7 +102,7 @@ public:
     ///  
     /// @throw DProcessBaseError if an operational error is encountered.
     virtual isc::data::ConstElementPtr 
-        shutdown(isc::data::ConstElementPtr args) = 0;
+    shutdown(isc::data::ConstElementPtr args) = 0;
 
     /// @brief Processes the given configuration.
     ///
@@ -118,7 +118,8 @@ public:
     /// of an integer status value (0 means successful, non-zero means failure),
     /// and a string explanation of the outcome.
     virtual isc::data::ConstElementPtr
-    configure(isc::data::ConstElementPtr config_set, bool check_only) = 0;
+    configure(isc::data::ConstElementPtr config_set,
+              bool check_only = false) = 0;
 
     /// @brief Processes the given command.
     ///

+ 90 - 11
src/lib/process/tests/d_cfg_mgr_unittests.cc

@@ -115,13 +115,22 @@ TEST_F(DStubCfgMgrTest, basicParseTest) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that we can parse a simple configuration.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
+    EXPECT_TRUE(checkAnswer(0));
+
+    // Verify that we can check a simple configuration.
+    answer_ = cfg_mgr_->parseConfig(config_set_, true);
     EXPECT_TRUE(checkAnswer(0));
 
     // Verify that an unknown element error is caught and returns a failed
     // parse result.
     SimFailure::set(SimFailure::ftElementUnknown);
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
+    EXPECT_TRUE(checkAnswer(1));
+
+    // Verify that an error is caught too when the config is checked for.
+    SimFailure::set(SimFailure::ftElementUnknown);
+    answer_ = cfg_mgr_->parseConfig(config_set_, true);
     EXPECT_TRUE(checkAnswer(1));
 }
 
@@ -181,7 +190,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
     EXPECT_EQ(0, cfg_mgr_->getParseOrder().size());
 
     // Parse the configuration, verify it parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(0));
 
     // Verify that the parsed order matches what we expected.
@@ -197,7 +206,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
     EXPECT_EQ(1, cfg_mgr_->getParseOrder().size());
 
     // Verify the configuration fails.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(1));
 
     // Verify that the configuration parses correctly, when the parse order
@@ -212,7 +221,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
     cfg_mgr_->parsed_order_.clear();
 
     // Verify the configuration parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(0));
 
     // Build expected order
@@ -238,7 +247,7 @@ TEST_F(DStubCfgMgrTest, parseOrderTest) {
     EXPECT_EQ(4, cfg_mgr_->getParseOrder().size());
 
     // Verify the configuration fails.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(1));
 }
 
@@ -262,7 +271,7 @@ TEST_F(DStubCfgMgrTest, simpleTypesTest) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that the configuration parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     ASSERT_TRUE(checkAnswer(0));
     DStubContextPtr context = getStubContext();
     ASSERT_TRUE(context);
@@ -301,7 +310,7 @@ TEST_F(DStubCfgMgrTest, simpleTypesTest) {
     ASSERT_TRUE(fromJSON(config2));
 
     // Verify that the configuration parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(0));
     context = getStubContext();
     ASSERT_TRUE(context);
@@ -352,7 +361,7 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that the configuration parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(0));
     DStubContextPtr context = getStubContext();
     ASSERT_TRUE(context);
@@ -389,7 +398,7 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
 
     // Force a failure on the last element
     SimFailure::set(SimFailure::ftElementUnknown);
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     EXPECT_TRUE(checkAnswer(1));
     context = getStubContext();
     ASSERT_TRUE(context);
@@ -414,6 +423,76 @@ TEST_F(DStubCfgMgrTest, rollBackTest) {
     EXPECT_TRUE(object);
 }
 
+/// @brief Tests that the configuration context is preserved during
+/// check only  parsing.
+TEST_F(DStubCfgMgrTest, checkOnly) {
+    // Create a configuration with all of the parameters.
+    string config = "{ \"bool_test\": true , "
+                    "  \"uint32_test\": 77 , "
+                    "  \"string_test\": \"hmmm chewy\" , "
+                    "  \"map_test\" : {} , "
+                    "  \"list_test\": [] }";
+    ASSERT_TRUE(fromJSON(config));
+
+    // Verify that the configuration parses without error.
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
+    EXPECT_TRUE(checkAnswer(0));
+    DStubContextPtr context = getStubContext();
+    ASSERT_TRUE(context);
+
+    // Verify that all of parameters have the expected values.
+    bool actual_bool = false;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_EQ(true, actual_bool);
+
+    uint32_t actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(77, actual_uint32);
+
+    std::string actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("hmmm chewy", actual_string);
+
+    isc::data::ConstElementPtr object;
+    EXPECT_NO_THROW(context->getObjectParam("map_test", object));
+    EXPECT_TRUE(object);
+
+    EXPECT_NO_THROW(context->getObjectParam("list_test", object));
+    EXPECT_TRUE(object);
+
+    // Create a configuration which "updates" all of the parameter values.
+    string config2 = "{ \"bool_test\": false , "
+                    "  \"uint32_test\": 88 , "
+                    "  \"string_test\": \"ewww yuk!\" , "
+                    "  \"map_test2\" : {} , "
+                    "  \"list_test2\": [] }";
+    ASSERT_TRUE(fromJSON(config2));
+
+    answer_ = cfg_mgr_->parseConfig(config_set_, true);
+    EXPECT_TRUE(checkAnswer(0));
+    context = getStubContext();
+    ASSERT_TRUE(context);
+
+    // Verify that all of parameters have the original values.
+    actual_bool = false;
+    EXPECT_NO_THROW(context->getParam("bool_test", actual_bool));
+    EXPECT_EQ(true, actual_bool);
+
+    actual_uint32 = 0;
+    EXPECT_NO_THROW(context->getParam("uint32_test", actual_uint32));
+    EXPECT_EQ(77, actual_uint32);
+
+    actual_string = "";
+    EXPECT_NO_THROW(context->getParam("string_test", actual_string));
+    EXPECT_EQ("hmmm chewy", actual_string);
+
+    EXPECT_NO_THROW(context->getObjectParam("map_test", object));
+    EXPECT_TRUE(object);
+
+    EXPECT_NO_THROW(context->getObjectParam("list_test", object));
+    EXPECT_TRUE(object);
+}
+
 // Tests that configuration element position is returned by getParam variants.
 TEST_F(DStubCfgMgrTest, paramPosition) {
     // Create a configuration with one of each scalar types.  We end them
@@ -424,7 +503,7 @@ TEST_F(DStubCfgMgrTest, paramPosition) {
     ASSERT_TRUE(fromJSON(config));
 
     // Verify that the configuration parses without error.
-    answer_ = cfg_mgr_->parseConfig(config_set_);
+    answer_ = cfg_mgr_->parseConfig(config_set_, false);
     ASSERT_TRUE(checkAnswer(0));
     DStubContextPtr context = getStubContext();
     ASSERT_TRUE(context);

+ 12 - 1
src/lib/process/tests/d_controller_unittests.cc

@@ -1,4 +1,4 @@
-// Copyright (C) 2013-2016 Internet Systems Consortium, Inc. ("ISC")
+// Copyright (C) 2013-2017 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
@@ -272,11 +272,22 @@ TEST_F(DStubControllerTest, configUpdateTests) {
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(0, rcode);
 
+    // Verify that a valid config gets a successful check result.
+    answer = checkConfig(config_set);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(0, rcode);
+
     // Verify that an error in process configure method is handled.
     SimFailure::set(SimFailure::ftProcessConfigure);
     answer = updateConfig(config_set);
     isc::config::parseAnswer(rcode, answer);
     EXPECT_EQ(1, rcode);
+
+    // Verify that an error is handled too when the config is checked for.
+    SimFailure::set(SimFailure::ftProcessConfigure);
+    answer = checkConfig(config_set);
+    isc::config::parseAnswer(rcode, answer);
+    EXPECT_EQ(1, rcode);
 }
 
 /// @brief Command execution tests.

+ 1 - 6
src/lib/process/testutils/d_test_stubs.cc

@@ -68,18 +68,13 @@ DStubProcess::shutdown(isc::data::ConstElementPtr /* args */) {
 
 isc::data::ConstElementPtr
 DStubProcess::configure(isc::data::ConstElementPtr config_set, bool check_only) {
-    if (check_only) {
-        return (isc::config::createAnswer(1,
-                "Configuration checking is not supported."));
-    }
-
     if (SimFailure::shouldFailOn(SimFailure::ftProcessConfigure)) {
         // Simulates a process configure failure.
         return (isc::config::createAnswer(1,
                 "Simulated process configuration error."));
     }
 
-    return (getCfgMgr()->parseConfig(config_set));
+    return (getCfgMgr()->parseConfig(config_set, check_only));
 }
 
 isc::data::ConstElementPtr

+ 7 - 0
src/lib/process/testutils/d_test_stubs.h

@@ -492,6 +492,13 @@ public:
         return (getController()->updateConfig(new_config));
     }
 
+    /// @Wrapper to invoke the Controller's checkConfig method.  Please
+    /// refer to DControllerBase::checkConfig for details.
+    isc::data::ConstElementPtr checkConfig(isc::data::ConstElementPtr
+                                           new_config) {
+        return (getController()->checkConfig(new_config));
+    }
+
     /// @Wrapper to invoke the Controller's executeCommand method.  Please
     /// refer to DControllerBase::executeCommand for details.
     isc::data::ConstElementPtr executeCommand(const std::string& command,