Commit 3ac8487a authored by Francis Dupont's avatar Francis Dupont

[5150a] Rebased

parent e38e8d97
......@@ -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>
......
......@@ -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>
......
......@@ -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>
......
......@@ -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()
......
......@@ -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:
......
......@@ -206,6 +206,44 @@ ControlledDhcpv4Srv::commandSetConfigHandler(const string&,
return (result);
}
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));
......@@ -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");
......
......@@ -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.
......
......@@ -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();
......
......@@ -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");
......
......@@ -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.
......
......@@ -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-