summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--doc/guide/ctrl-channel.xml59
-rw-r--r--doc/guide/dhcp4-srv.xml1
-rw-r--r--doc/guide/dhcp6-srv.xml1
-rw-r--r--src/bin/agent/ca_controller.cc20
-rw-r--r--src/bin/agent/ca_controller.h7
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.cc64
-rw-r--r--src/bin/dhcp4/ctrl_dhcp4_srv.h27
-rw-r--r--src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc143
-rw-r--r--src/bin/dhcp6/ctrl_dhcp6_srv.cc64
-rw-r--r--src/bin/dhcp6/ctrl_dhcp6_srv.h27
-rw-r--r--src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc147
-rw-r--r--src/bin/shell/tests/shell_process_tests.sh.in2
-rw-r--r--src/lib/process/d_controller.cc119
-rw-r--r--src/lib/process/d_controller.h41
-rw-r--r--src/lib/process/d_process.h9
-rw-r--r--src/lib/process/testutils/d_test_stubs.cc42
-rw-r--r--src/lib/process/testutils/d_test_stubs.h44
17 files changed, 715 insertions, 102 deletions
diff --git a/doc/guide/ctrl-channel.xml b/doc/guide/ctrl-channel.xml
index abfefcdd4d..266282ff39 100644
--- a/doc/guide/ctrl-channel.xml
+++ b/doc/guide/ctrl-channel.xml
@@ -154,7 +154,62 @@ will be sent to Kea and the responses received from Kea printed to standard outp
}
</screen>
</para>
- </section>
+ </section> <!-- end of command-config-get -->
+
+ <section id="command-config-test">
+ <title>config-test</title>
+
+ <para>
+ The <emphasis>config-test</emphasis> command instructs the server to check
+ whether the new configuration supplied in the command's arguments can
+ be loaded. The supplied configuration is expected to be the full
+ configuration for the target server along with an optional Logger
+ configuration. As for the <command>-t</command> command some sanity checks
+ are not performed so it is possible a configuration which successfully
+ passes this command will still fail in <command>set-config</command>
+ command or at launch time.
+ The structure of the command is as follows:
+ </para>
+<screen>
+{
+ "command": "config-test",
+ "arguments": {
+ "&#60;server&#62;": {
+ },
+ "Logging": {
+ }
+ }
+}
+</screen>
+ <para>
+ where &#60;server&#62; is the configuration element name for a given server
+ such as "Dhcp4" or "Dhcp6". For example:
+ </para>
+<screen>
+{
+ "command": "config-test",
+ "arguments": {
+ "Dhcp6": {
+ :
+ },
+ "Logging": {
+ :
+ }
+ }
+}
+</screen>
+ <para>
+ The server's response will contain a numeric code, "result" (0 for success,
+ non-zero on failure), and a string, "text", describing the outcome:
+<screen>
+ {"result": 0, "text": "Configuration seems sane..." }
+
+ or
+
+ {"result": 1, "text": "unsupported parameter: BOGUS (&#60;string&#62;:16:26)" }
+</screen>
+ </para>
+ </section> <!-- end of command-config-test -->
<section id="command-config-write">
<title>config-write</title>
@@ -178,7 +233,7 @@ will be sent to Kea and the responses received from Kea printed to standard outp
}
</screen>
</para>
- </section>
+ </section> <!-- end of command-config-write -->
<section id="command-leases-reclaim">
<title>leases-reclaim</title>
diff --git a/doc/guide/dhcp4-srv.xml b/doc/guide/dhcp4-srv.xml
index 71a032a01e..70089c1fba 100644
--- a/doc/guide/dhcp4-srv.xml
+++ b/doc/guide/dhcp4-srv.xml
@@ -3725,6 +3725,7 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
<itemizedlist>
<listitem>build-report</listitem>
<listitem>config-get</listitem>
+ <listitem>config-test</listitem>
<listitem>config-write</listitem>
<listitem>leases-reclaim</listitem>
<listitem>list-commands</listitem>
diff --git a/doc/guide/dhcp6-srv.xml b/doc/guide/dhcp6-srv.xml
index e811d97b58..618d4092dc 100644
--- a/doc/guide/dhcp6-srv.xml
+++ b/doc/guide/dhcp6-srv.xml
@@ -4133,6 +4133,7 @@ If not specified, the default value is:
<itemizedlist>
<listitem>build-report</listitem>
<listitem>config-get</listitem>
+ <listitem>config-test</listitem>
<listitem>config-write</listitem>
<listitem>leases-reclaim</listitem>
<listitem>list-commands</listitem>
diff --git a/src/bin/agent/ca_controller.cc b/src/bin/agent/ca_controller.cc
index 38ae98388e..a5fce22fd1 100644
--- a/src/bin/agent/ca_controller.cc
+++ b/src/bin/agent/ca_controller.cc
@@ -50,21 +50,33 @@ CtrlAgentController::parseFile(const std::string& name) {
void
CtrlAgentController::registerCommands() {
- CtrlAgentCommandMgr::instance().registerCommand(VERSION_GET_COMMAND,
- boost::bind(&DControllerBase::versionGetHandler, this, _1, _2));
-
CtrlAgentCommandMgr::instance().registerCommand(BUILD_REPORT_COMMAND,
boost::bind(&DControllerBase::buildReportHandler, this, _1, _2));
+ CtrlAgentCommandMgr::instance().registerCommand(CONFIG_GET_COMMAND,
+ boost::bind(&DControllerBase::configGetHandler, this, _1, _2));
+
+ CtrlAgentCommandMgr::instance().registerCommand(CONFIG_TEST_COMMAND,
+ boost::bind(&DControllerBase::configTestHandler, this, _1, _2));
+
+ CtrlAgentCommandMgr::instance().registerCommand(CONFIG_WRITE_COMMAND,
+ boost::bind(&DControllerBase::configWriteHandler, this, _1, _2));
+
CtrlAgentCommandMgr::instance().registerCommand(SHUT_DOWN_COMMAND,
boost::bind(&DControllerBase::shutdownHandler, this, _1, _2));
+
+ CtrlAgentCommandMgr::instance().registerCommand(VERSION_GET_COMMAND,
+ boost::bind(&DControllerBase::versionGetHandler, this, _1, _2));
}
void
CtrlAgentController::deregisterCommands() {
- CtrlAgentCommandMgr::instance().deregisterCommand(VERSION_GET_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(BUILD_REPORT_COMMAND);
+ CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_GET_COMMAND);
+ CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_TEST_COMMAND);
+ CtrlAgentCommandMgr::instance().deregisterCommand(CONFIG_WRITE_COMMAND);
CtrlAgentCommandMgr::instance().deregisterCommand(SHUT_DOWN_COMMAND);
+ CtrlAgentCommandMgr::instance().deregisterCommand(VERSION_GET_COMMAND);
}
CtrlAgentController::CtrlAgentController()
diff --git a/src/bin/agent/ca_controller.h b/src/bin/agent/ca_controller.h
index 179d35d710..2cca081298 100644
--- a/src/bin/agent/ca_controller.h
+++ b/src/bin/agent/ca_controller.h
@@ -48,16 +48,9 @@ public:
parseFile(const std::string& name);
/// @brief Register commands.
- ///
- /// For all commands in the commands_ set at the exception of
- /// list-commands register the command with the generic
- /// @ref isc::process::DControllerBase::executeCommand() handler.
void registerCommands();
/// @brief Deregister commands.
- ///
- /// For all commands in the commands_ set at the exception of
- /// list-commands deregister the command.
void deregisterCommands();
private:
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.cc b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
index 30872a653c..7134bc4b3d 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.cc
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.cc
@@ -207,6 +207,44 @@ ControlledDhcpv4Srv::commandSetConfigHandler(const string&,
}
ConstElementPtr
+ControlledDhcpv4Srv::commandConfigTestHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = 1; // 1 indicates an error
+ ConstElementPtr dhcp4;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp4": { ... }, "Logging": { ... } }
+ // The Logging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp4 = args->get("Dhcp4");
+ if (!dhcp4) {
+ message = "Missing mandatory 'Dhcp4' parameter.";
+ } else if (dhcp4->getType() != Element::map) {
+ message = "'Dhcp4' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(dhcp4));
+}
+
+ConstElementPtr
ControlledDhcpv4Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
ElementPtr extended = Element::create(Dhcpv4Srv::getVersion(true));
ElementPtr arguments = Element::createMap();
@@ -282,6 +320,9 @@ ControlledDhcpv4Srv::processCommand(const string& command,
} else if (command == "config-get") {
return (srv->commandConfigGetHandler(command, args));
+ } else if (command == "config-test") {
+ return (srv->commandConfigTestHandler(command, args));
+
} else if (command == "version-get") {
return (srv->commandVersionGetHandler(command, args));
@@ -414,6 +455,25 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
return (answer);
}
+isc::data::ConstElementPtr
+ControlledDhcpv4Srv::checkConfig(isc::data::ConstElementPtr config) {
+
+ LOG_DEBUG(dhcp4_logger, DBG_DHCP4_COMMAND, DHCP4_CONFIG_RECEIVED)
+ .arg(config->str());
+
+ ControlledDhcpv4Srv* srv = ControlledDhcpv4Srv::getInstance();
+
+ // Single stream instance used in all error clauses
+ std::ostringstream err;
+
+ if (!srv) {
+ err << "Server object not initialized, can't process config.";
+ return (isc::config::createAnswer(1, err.str()));
+ }
+
+ return (configureDhcp4Server(*srv, config, true));
+}
+
ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
: Dhcpv4Srv(port), io_service_(), timer_mgr_(TimerMgr::instance()) {
if (getInstance()) {
@@ -432,6 +492,9 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
/// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
+ CommandMgr::instance().registerCommand("config-test",
+ boost::bind(&ControlledDhcpv4Srv::commandConfigTestHandler, this, _1, _2));
+
CommandMgr::instance().registerCommand("config-write",
boost::bind(&ControlledDhcpv4Srv::commandConfigWriteHandler, this, _1, _2));
@@ -491,6 +554,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
// Deregister any registered commands (please keep in alphabetic order)
CommandMgr::instance().deregisterCommand("build-report");
CommandMgr::instance().deregisterCommand("config-get");
+ CommandMgr::instance().deregisterCommand("config-test");
CommandMgr::instance().deregisterCommand("config-write");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("libreload");
diff --git a/src/bin/dhcp4/ctrl_dhcp4_srv.h b/src/bin/dhcp4/ctrl_dhcp4_srv.h
index 163f167e5c..a245e02fe3 100644
--- a/src/bin/dhcp4/ctrl_dhcp4_srv.h
+++ b/src/bin/dhcp4/ctrl_dhcp4_srv.h
@@ -64,6 +64,7 @@ public:
/// - libreload
/// - config-reload
/// - leases-reclaim
+ /// ...
///
/// @note It never throws.
///
@@ -89,6 +90,16 @@ public:
static isc::data::ConstElementPtr
processConfig(isc::data::ConstElementPtr new_config);
+ /// @brief Configuration checker
+ ///
+ /// This is a method for checking incoming configuration.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config check
+ isc::data::ConstElementPtr
+ checkConfig(isc::data::ConstElementPtr new_config);
+
/// @brief Returns pointer to the sole instance of Dhcpv4Srv
///
/// @return server instance (may return NULL, if called before server is spawned)
@@ -187,7 +198,21 @@ private:
commandSetConfigHandler(const std::string& command,
isc::data::ConstElementPtr args);
- /// @brief handler for processing 'version-get' command
+ /// @brief handler for processing 'config-test' command
+ ///
+ /// This handler processes config-test command, which checks
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be checked. Expected format:
+ /// map containing Dhcp4 map that contains DHCPv4 server configuration.
+ /// May also contain Logging map that specifies logging configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandConfigTestHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @Brief handler for processing 'version-get' command
///
/// This handler processes version-get command, which returns
/// over the control channel the -v and -V command line arguments.
diff --git a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
index e6554c2eec..496634182d 100644
--- a/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
+++ b/src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc
@@ -790,6 +790,149 @@ TEST_F(CtrlChannelDhcpv4SrvTest, configGet) {
EXPECT_TRUE(cfg->get("Dhcp4"));
}
+// Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelDhcpv4SrvTest, configTest) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"set-config\" \n";
+ string config_test_txt = "{ \"command\": \"config-test\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp4_cfg_txt =
+ " \"Dhcp4\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet4\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"192.2.0.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.0.1-192.2.0.50\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"192.2.1.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.1.1-192.2.1.50\" }]}\n";
+ string bad_subnet =
+ " {\"BOGUS\": \"192.2.2.0/24\", \n"
+ " \"pools\": [{ \"pool\": \"192.2.2.1-192.2.2.50\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " \"Logging\": { \n"
+ " \"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n"
+ " } \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ << ","
+ << logger_txt
+ << "}}";
+
+ // Send the set-config command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet4Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp4
+ "}}";
+
+ // Send the config-test command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' parameter is missing for a subnet being configured (<string>:20:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp4_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp4
+ << "}}";
+
+ /* Verify the control channel socket exists */
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the config-test command
+ sendUnixCommand(os.str(), response);
+
+ /* Verify the control channel socket still exists */
+ EXPECT_TRUE(fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. Control-socket, hook-libraries, and D2 configuration were sanity checked, but not applied.\" }",
+ response);
+
+ // Check that the config was not applied
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
// Tests if config-write can be called without any parameters.
TEST_F(CtrlChannelDhcpv4SrvTest, writeConfigNoFilename) {
createUnixChannelServer();
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.cc b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
index 10363c30b8..8f5b1cffc8 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.cc
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.cc
@@ -211,6 +211,43 @@ ControlledDhcpv6Srv::commandSetConfigHandler(const string&,
return (result);
}
+ConstElementPtr
+ControlledDhcpv6Srv::commandConfigTestHandler(const string&,
+ ConstElementPtr args) {
+ const int status_code = 1; // 1 indicates an error
+ ConstElementPtr dhcp6;
+ string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp6": { ... }, "Logging": { ... } }
+ // The Logging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp6 = args->get("Dhcp6");
+ if (!dhcp6) {
+ message = "Missing mandatory 'Dhcp6' parameter.";
+ } else if (dhcp6->getType() != Element::map) {
+ message = "'Dhcp6' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(dhcp6));
+}
ConstElementPtr
ControlledDhcpv6Srv::commandVersionGetHandler(const string&, ConstElementPtr) {
@@ -287,6 +324,9 @@ ControlledDhcpv6Srv::processCommand(const std::string& command,
} else if (command == "config-get") {
return (srv->commandConfigGetHandler(command, args));
+ } else if (command == "config-test") {
+ return (srv->commandConfigTestHandler(command, args));
+
} else if (command == "version-get") {
return (srv->commandVersionGetHandler(command, args));
@@ -442,6 +482,26 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
return (answer);
}
+isc::data::ConstElementPtr
+ControlledDhcpv6Srv::checkConfig(isc::data::ConstElementPtr config) {
+
+ LOG_DEBUG(dhcp6_logger, DBG_DHCP6_COMMAND, DHCP6_CONFIG_RECEIVED)
+ .arg(config->str());
+
+ ControlledDhcpv6Srv* srv = ControlledDhcpv6Srv::getInstance();
+
+ // Single stream instance used in all error clauses
+ std::ostringstream err;
+
+ if (!srv) {
+ ConstElementPtr no_srv = isc::config::createAnswer(1,
+ "Server object not initialized, can't process config.");
+ return (no_srv);
+ }
+
+ return (configureDhcp6Server(*srv, config, true));
+}
+
ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
: Dhcpv6Srv(port), io_service_(), timer_mgr_(TimerMgr::instance()) {
if (server_) {
@@ -460,6 +520,9 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
/// @todo: register config-reload (see CtrlDhcpv6Srv::commandConfigReloadHandler)
+ CommandMgr::instance().registerCommand("config-test",
+ boost::bind(&ControlledDhcpv6Srv::commandConfigTestHandler, this, _1, _2));
+
CommandMgr::instance().registerCommand("config-write",
boost::bind(&ControlledDhcpv6Srv::commandConfigWriteHandler, this, _1, _2));
@@ -518,6 +581,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
// Deregister any registered commands (please keep in alphabetic order)
CommandMgr::instance().deregisterCommand("build-report");
CommandMgr::instance().deregisterCommand("config-get");
+ CommandMgr::instance().deregisterCommand("config-test");
CommandMgr::instance().deregisterCommand("config-write");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("libreload");
diff --git a/src/bin/dhcp6/ctrl_dhcp6_srv.h b/src/bin/dhcp6/ctrl_dhcp6_srv.h
index 3749fbc580..6037048149 100644
--- a/src/bin/dhcp6/ctrl_dhcp6_srv.h
+++ b/src/bin/dhcp6/ctrl_dhcp6_srv.h
@@ -64,6 +64,7 @@ public:
/// - libreload
/// - config-reload
/// - leases-reclaim
+ /// ...
///
/// @note It never throws.
///
@@ -89,6 +90,16 @@ public:
static isc::data::ConstElementPtr
processConfig(isc::data::ConstElementPtr new_config);
+ /// @brief Configuration checker
+ ///
+ /// This is a method for checking incoming configuration.
+ ///
+ /// @param new_config textual representation of the new configuration
+ ///
+ /// @return status of the config check
+ isc::data::ConstElementPtr
+ checkConfig(isc::data::ConstElementPtr new_config);
+
/// @brief returns pointer to the sole instance of Dhcpv6Srv
///
/// @return server instance (may return NULL, if called before server is spawned)
@@ -187,7 +198,21 @@ private:
commandSetConfigHandler(const std::string& command,
isc::data::ConstElementPtr args);
- /// @brief handler for processing 'version-get' command
+ /// @brief handler for processing 'config-test' command
+ ///
+ /// This handler processes config-test command, which checks
+ /// configuration specified in args parameter.
+ /// @param command (parameter ignored)
+ /// @param args configuration to be checked. Expected format:
+ /// map containing Dhcp6 map that contains DHCPv6 server configuration.
+ /// May also contain Logging map that specifies logging configuration.
+ ///
+ /// @return status of the command
+ isc::data::ConstElementPtr
+ commandConfigTestHandler(const std::string& command,
+ isc::data::ConstElementPtr args);
+
+ /// @Brief handler for processing 'version-get' command
///
/// This handler processes version-get command, which returns
/// over the control channel the -v and -V command line arguments.
diff --git a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
index c7efdcaf97..eebfa4ac5f 100644
--- a/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
+++ b/src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
@@ -573,6 +573,150 @@ TEST_F(CtrlChannelDhcpv6SrvTest, set_config) {
CfgMgr::instance().clear();
}
+ // Verify that the "config-test" command will do what we expect.
+TEST_F(CtrlChannelDhcpv6SrvTest, config_test) {
+ createUnixChannelServer();
+
+ // Define strings to permutate the config arguments
+ // (Note the line feeds makes errors easy to find)
+ string set_config_txt = "{ \"command\": \"set-config\" \n";
+ string config_test_txt = "{ \"command\": \"config-test\" \n";
+ string args_txt = " \"arguments\": { \n";
+ string dhcp6_cfg_txt =
+ " \"Dhcp6\": { \n"
+ " \"interfaces-config\": { \n"
+ " \"interfaces\": [\"*\"] \n"
+ " }, \n"
+ " \"preferred-lifetime\": 3000, \n"
+ " \"valid-lifetime\": 4000, \n"
+ " \"renew-timer\": 1000, \n"
+ " \"rebind-timer\": 2000, \n"
+ " \"lease-database\": { \n"
+ " \"type\": \"memfile\", \n"
+ " \"persist\":false, \n"
+ " \"lfc-interval\": 0 \n"
+ " }, \n"
+ " \"expired-leases-processing\": { \n"
+ " \"reclaim-timer-wait-time\": 0, \n"
+ " \"hold-reclaimed-time\": 0, \n"
+ " \"flush-reclaimed-timer-wait-time\": 0 \n"
+ " },"
+ " \"subnet6\": [ \n";
+ string subnet1 =
+ " {\"subnet\": \"3002::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3002::100-3002::200\" }]}\n";
+ string subnet2 =
+ " {\"subnet\": \"3003::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3003::100-3003::200\" }]}\n";
+ string bad_subnet =
+ " {\"BOGUS\": \"3005::/64\", \n"
+ " \"pools\": [{ \"pool\": \"3005::100-3005::200\" }]}\n";
+ string subnet_footer =
+ " ] \n";
+ string control_socket_header =
+ " ,\"control-socket\": { \n"
+ " \"socket-type\": \"unix\", \n"
+ " \"socket-name\": \"";
+ string control_socket_footer =
+ "\" \n} \n";
+ string logger_txt =
+ " \"Logging\": { \n"
+ " \"loggers\": [ { \n"
+ " \"name\": \"kea\", \n"
+ " \"severity\": \"FATAL\", \n"
+ " \"output_options\": [{ \n"
+ " \"output\": \"/dev/null\" \n"
+ " }] \n"
+ " }] \n"
+ " } \n";
+
+ std::ostringstream os;
+
+ // Create a valid config with all the parts should parse
+ os << set_config_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ << ","
+ << logger_txt
+ << "}}";
+
+ // Send the set-config command
+ std::string response;
+ sendUnixCommand(os.str(), response);
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
+ response);
+
+ // Check that the config was indeed applied.
+ const Subnet6Collection* subnets =
+ CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a config with malformed subnet that should fail to parse.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << bad_subnet
+ << subnet_footer
+ << control_socket_header
+ << socket_path_
+ << control_socket_footer
+ << "}\n" // close dhcp6
+ "}}";
+
+ // Send the config-test command
+ sendUnixCommand(os.str(), response);
+
+ // Should fail with a syntax error
+ EXPECT_EQ("{ \"result\": 1, "
+ "\"text\": \"subnet configuration failed: mandatory 'subnet' parameter is missing for a subnet being configured (<string>:21:17)\" }",
+ response);
+
+ // Check that the config was not lost
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Create a valid config with two subnets and no command channel.
+ os.str("");
+ os << config_test_txt << ","
+ << args_txt
+ << dhcp6_cfg_txt
+ << subnet1
+ << ",\n"
+ << subnet2
+ << subnet_footer
+ << "}\n" // close dhcp6
+ << "}}";
+
+ /* Verify the control channel socket exists */
+ ASSERT_TRUE(fileExists(socket_path_));
+
+ // Send the config-test command
+ sendUnixCommand(os.str(), response);
+
+ /* Verify the control channel socket still exists */
+ EXPECT_TRUE(fileExists(socket_path_));
+
+ // Verify the configuration was successful.
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration seems sane. Control-socket, hook-libraries, and D2 configuration were sanity checked, but not applied.\" }",
+ response);
+
+ // Check that the config was not applied.
+ subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
+ EXPECT_EQ(1, subnets->size());
+
+ // Clean up after the test.
+ CfgMgr::instance().clear();
+}
+
typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
@@ -834,12 +978,15 @@ TEST_F(CtrlChannelDhcpv6SrvTest, commandsList) {
EXPECT_NO_THROW(rsp = Element::fromJSON(response));
// We expect the server to report at least the following commands:
+ checkListCommands(rsp, "build-report");
checkListCommands(rsp, "config-get");
+ checkListCommands(rsp, "config-test");
checkListCommands(rsp, "config-write");
checkListCommands(rsp, "list-commands");
checkListCommands(rsp, "leases-reclaim");
checkListCommands(rsp, "libreload");
checkListCommands(rsp, "set-config");
+ checkListCommands(rsp, "version-get");
checkListCommands(rsp, "shutdown");
checkListCommands(rsp, "statistic-get");
checkListCommands(rsp, "statistic-get-all");
diff --git a/src/bin/shell/tests/shell_process_tests.sh.in b/src/bin/shell/tests/shell_process_tests.sh.in
index a220640eec..746b2f5aae 100644
--- a/src/bin/shell/tests/shell_process_tests.sh.in
+++ b/src/bin/shell/tests/shell_process_tests.sh.in
@@ -178,6 +178,6 @@ version_test() {
version_test "shell.version"
shell_command_test "shell.list-commands" "list-commands" \
- "[ { \"arguments\": [ \"build-report\", \"list-commands\", \"shutdown\", \"version-get\" ], \"result\": 0 } ]" ""
+ "[ { \"arguments\": [ \"build-report\", \"config-get\", \"config-test\", \"config-write\", \"list-commands\", \"shutdown\", \"version-get\" ], \"result\": 0 } ]" ""
shell_command_test "shell.bogus" "give-me-a-beer" \
"[ { \"result\": 1, \"text\": \"'give-me-a-beer' command not supported.\" } ]" ""
diff --git a/src/lib/process/d_controller.cc b/src/lib/process/d_controller.cc
index 974e178392..5b112d4331 100644
--- a/src/lib/process/d_controller.cc
+++ b/src/lib/process/d_controller.cc
@@ -406,6 +406,125 @@ DControllerBase::checkConfig(ConstElementPtr new_config) {
}
ConstElementPtr
+DControllerBase::configGetHandler(const std::string&,
+ ConstElementPtr /*args*/) {
+ ConstElementPtr config = process_->getCfgMgr()->getContext()->toElement();
+
+ return (createAnswer(COMMAND_SUCCESS, config));
+}
+
+ConstElementPtr
+DControllerBase::configWriteHandler(const std::string&,
+ ConstElementPtr args) {
+ std::string filename;
+
+ if (args) {
+ if (args->getType() != Element::map) {
+ return (createAnswer(COMMAND_ERROR, "Argument must be a map"));
+ }
+ ConstElementPtr filename_param = args->get("filename");
+ if (filename_param) {
+ if (filename_param->getType() != Element::string) {
+ return (createAnswer(COMMAND_ERROR,
+ "passed parameter 'filename' "
+ "is not a string"));
+ }
+ filename = filename_param->stringValue();
+ }
+ }
+
+ if (filename.empty()) {
+ // filename parameter was not specified, so let's use
+ // whatever we remember
+ filename = getConfigFile();
+ if (filename.empty()) {
+ return (createAnswer(COMMAND_ERROR,
+ "Unable to determine filename."
+ "Please specify filename explicitly."));
+ }
+ }
+
+ // Now do the sanity checks on the filename
+ if (filename.find("..") != std::string::npos) {
+ // Trying to escape the directory.. nope.
+ return (createAnswer(COMMAND_ERROR,
+ "Using '..' in filename is not allowed."));
+ }
+
+ if (filename.find("\\") != std::string::npos) {
+ // Trying to inject escapes (possibly to inject quotes and something
+ // nasty afterward)
+ return (createAnswer(COMMAND_ERROR,
+ "Using \\ in filename is not allowed."));
+ }
+
+ if (filename[0] == '/') {
+ // Absolute paths are not allowed.
+ return (createAnswer(COMMAND_ERROR,
+ "Absolute path in filename is not allowed."));
+ }
+
+ // Ok, it's time to write the file.
+ size_t size = 0;
+ try {
+ size = writeConfigFile(filename);
+ } catch (const isc::Exception& ex) {
+ return (createAnswer(COMMAND_ERROR,
+ std::string("Error during write-config:")
+ + ex.what()));
+ }
+ if (size == 0) {
+ return (createAnswer(COMMAND_ERROR,
+ "Error writing configuration to " + filename));
+ }
+
+ // Ok, it's time to return the successful response.
+ ElementPtr params = Element::createMap();
+ params->set("size", Element::create(static_cast<long long>(size)));
+ params->set("filename", Element::create(filename));
+
+ return (createAnswer(CONTROL_RESULT_SUCCESS, "Configuration written to "
+ + filename + " successful", params));
+}
+
+ConstElementPtr
+DControllerBase::configTestHandler(const std::string&, ConstElementPtr args) {
+ const int status_code = CONTROL_RESULT_SUCCESS; // 1 indicates an error
+ ConstElementPtr dhcp4;
+ std::string message;
+
+ // Command arguments are expected to be:
+ // { "Dhcp4": { ... }, "Logging": { ... } }
+ // The Lnogging component is technically optional. If it's not supplied
+ // logging will revert to default logging.
+ if (!args) {
+ message = "Missing mandatory 'arguments' parameter.";
+ } else {
+ dhcp4 = args->get("Dhcp4");
+ if (!dhcp4) {
+ message = "Missing mandatory 'Dhcp4' parameter.";
+ } else if (dhcp4->getType() != Element::map) {
+ message = "'Dhcp4' parameter expected to be a map.";
+ }
+ }
+
+ if (!message.empty()) {
+ // Something is amiss with arguments, return a failure response.
+ ConstElementPtr result = isc::config::createAnswer(status_code,
+ message);
+ return (result);
+ }
+
+ // We are starting the configuration process so we should remove any
+ // staging configuration that has been created during previous
+ // configuration attempts.
+ isc::dhcp::CfgMgr::instance().rollback();
+
+ // Now we check the server proper.
+ return (checkConfig(dhcp4));
+}
+
+ConstElementPtr
DControllerBase::versionGetHandler(const std::string&, ConstElementPtr args) {
ConstElementPtr answer;
diff --git a/src/lib/process/d_controller.h b/src/lib/process/d_controller.h
index f4b536202b..c7c3aa11fe 100644
--- a/src/lib/process/d_controller.h
+++ b/src/lib/process/d_controller.h
@@ -256,6 +256,47 @@ public:
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 comamnd, 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 'shutdown' command
///
/// This method handles shutdown command. It initiates the shutdown procedure
diff --git a/src/lib/process/d_process.h b/src/lib/process/d_process.h
index 0ab13133d5..d7c58d9f02 100644
--- a/src/lib/process/d_process.h
+++ b/src/lib/process/d_process.h
@@ -31,6 +31,15 @@ static const std::string VERSION_GET_COMMAND("version-get");
/// @brief String value for the build-report command.
static const std::string BUILD_REPORT_COMMAND("build-report");
+/// @brief String value for the config-get command.
+static const std::string CONFIG_GET_COMMAND("config-get");
+
+/// @brief String value for the config-write command.
+static const std::string CONFIG_WRITE_COMMAND("config-write");
+
+/// @brief String value for the config-test command.
+static const std::string CONFIG_TEST_COMMAND("config-test");
+
/// @brief String value for the shutdown command.
static const std::string SHUT_DOWN_COMMAND("shutdown");
diff --git a/src/lib/process/testutils/d_test_stubs.cc b/src/lib/process/testutils/d_test_stubs.cc
index 8bff0f2acc..d265232161 100644
--- a/src/lib/process/testutils/d_test_stubs.cc
+++ b/src/lib/process/testutils/d_test_stubs.cc
@@ -19,9 +19,6 @@ namespace process {
// Initialize the static failure flag.
SimFailure::FailureType SimFailure::failure_type_ = SimFailure::ftNoFailure;
-// Define custom process command supported by DStubProcess.
-const char* DStubProcess::stub_proc_command_("cool_proc_cmd");
-
DStubProcess::DStubProcess(const char* name, asiolink::IOServicePtr io_service)
: DProcessBase(name, io_service, DCfgMgrBasePtr(new DStubCfgMgr())) {
};
@@ -77,32 +74,11 @@ DStubProcess::configure(isc::data::ConstElementPtr config_set, bool check_only)
return (getCfgMgr()->parseConfig(config_set, check_only));
}
-isc::data::ConstElementPtr
-DStubProcess::command(const std::string& command,
- isc::data::ConstElementPtr /* args */) {
- isc::data::ConstElementPtr answer;
- if (SimFailure::shouldFailOn(SimFailure::ftProcessCommand)) {
- // Simulates a process command execution failure.
- answer = isc::config::createAnswer(COMMAND_ERROR,
- "SimFailure::ftProcessCommand");
- } else if (command.compare(stub_proc_command_) == 0) {
- answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
- } else {
- answer = isc::config::createAnswer(COMMAND_INVALID,
- "Unrecognized command:" + command);
- }
-
- return (answer);
-}
-
DStubProcess::~DStubProcess() {
};
//************************** DStubController *************************
-// Define custom controller command supported by DStubController.
-const char* DStubController::stub_ctl_command_("spiffy");
-
// Define custom command line option command supported by DStubController.
const char* DStubController::stub_option_x_ = "x";
@@ -162,24 +138,6 @@ DProcessBase* DStubController::createProcess() {
return (new DStubProcess(getAppName().c_str(), getIOService()));
}
-isc::data::ConstElementPtr
-DStubController::customControllerCommand(const std::string& command,
- isc::data::ConstElementPtr /* args */) {
- isc::data::ConstElementPtr answer;
- if (SimFailure::shouldFailOn(SimFailure::ftControllerCommand)) {
- // Simulates command failing to execute.
- answer = isc::config::createAnswer(COMMAND_ERROR,
- "SimFailure::ftControllerCommand");
- } else if (command.compare(stub_ctl_command_) == 0) {
- answer = isc::config::createAnswer(COMMAND_SUCCESS, "Command accepted");
- } else {
- answer = isc::config::createAnswer(COMMAND_INVALID,
- "Unrecognized command:" + command);
- }
-
- return (answer);
-}
-
const std::string DStubController::getCustomOpts() const {
// Return the "list" of custom options supported by DStubController.
return (std::string(stub_option_x_));
diff --git a/src/lib/process/testutils/d_test_stubs.h b/src/lib/process/testutils/d_test_stubs.h
index 09c2298787..334adac7bf 100644
--- a/src/lib/process/testutils/d_test_stubs.h
+++ b/src/lib/process/testutils/d_test_stubs.h
@@ -45,8 +45,6 @@ public:
ftCreateProcessNull,
ftProcessInit,
ftProcessConfigure,
- ftControllerCommand,
- ftProcessCommand,
ftProcessShutdown,
ftElementBuild,
ftElementCommit,
@@ -104,9 +102,6 @@ public:
class DStubProcess : public DProcessBase {
public:
- /// @brief Static constant that defines a custom process command string.
- static const char* stub_proc_command_;
-
/// @brief Constructor
///
/// @param name name is a text label for the process. Generally used
@@ -152,24 +147,6 @@ public:
virtual isc::data::ConstElementPtr
configure(isc::data::ConstElementPtr config_set, bool check_only);
- /// @brief Executes the given command.
- ///
- /// This implementation will recognizes one "custom" process command,
- /// stub_proc_command_. It will fail if SimFailure is set to
- /// ftProcessCommand.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- /// @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:
- /// COMMAND_SUCCESS if the command is recognized and executes successfully.
- /// COMMAND_ERROR if the command is recognized but fails to execute.
- /// COMMAND_INVALID if the command is not recognized.
- virtual isc::data::ConstElementPtr command(const std::string& command,
- isc::data::ConstElementPtr args);
-
/// @brief Returns configuration summary in the textual format.
///
/// @return Always an empty string.
@@ -198,10 +175,6 @@ public:
/// @return returns a pointer reference to the singleton instance.
static DControllerBasePtr& instance();
- /// @brief Defines a custom controller command string. This is a
- /// custom command supported by DStubController.
- static const char* stub_ctl_command_;
-
/// @brief Defines a custom command line option supported by
/// DStubController.
static const char* stub_option_x_;
@@ -266,23 +239,6 @@ protected:
/// ftCreateProcessException.
virtual DProcessBase* createProcess();
- /// @brief Executes custom controller commands are supported by
- /// DStubController. This implementation supports one custom controller
- /// command, stub_ctl_command_. It will fail if SimFailure is set
- /// to ftControllerCommand.
- ///
- /// @param command is a string label representing the command to execute.
- /// @param args is a set of arguments (if any) required for the given
- /// command.
- /// @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:
- /// COMMAND_SUCCESS if the command is recognized and executes successfully.
- /// COMMAND_ERROR if the command is recognized but fails to execute.
- /// COMMAND_INVALID if the command is not recognized.
- virtual isc::data::ConstElementPtr customControllerCommand(
- const std::string& command, isc::data::ConstElementPtr args);
-
/// @brief Provides a string of the additional command line options
/// supported by DStubController. DStubController supports one
/// addition option, stub_option_x_.