Commit 4afbdcf8 authored by Thomas Markwalder's avatar Thomas Markwalder

[master] Implement set-config command in kea-dhcp4/6

    Merges in branch 'trac5046'
parents 12e3d281 9d26dddc
......@@ -184,6 +184,65 @@ will be sent to Kea and the responses received from Kea printed to standard outp
</para>
</section> <!-- end of command-list-commands -->
<section id="command-set-config">
<title>set-config</title>
<para>
The <emphasis>set-config</emphasis> command instructs the server to replace
its current configuration with the new configuration supplied in the
command's arguments. The supplied configuration is expected to be the full
configuration for the target server along with an optional Logger
configuration. While optional, the Logger configuration is highly
recommended as without it the server will revert to its default logging
configuration. The structure of the command is as follows:
</para>
<screen>
{
"command": "set-config",
"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": "set-config",
"arguments": {
"Dhcp6": {
:
},
"Logging": {
:
}
}
}
</screen>
<para>
If the new configuration proves to be invalid the server will retain
its current configuration. Please note that the new configuration is
retained in memory only. If the server is restarted or a configuration
reload is triggered via a signal, the server will use the configuration
stored in its configuration file.
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 successful." }
or
{"result": 1, "text": "unsupported parameter: BOGUS (&#60;string&#62;:16:26)" }
</screen>
</para>
</section> <!-- end of command-set-config -->
<section id="command-shutdown">
<title>shutdown</title>
......@@ -205,6 +264,8 @@ will be sent to Kea and the responses received from Kea printed to standard outp
</para>
</section> <!-- end of command-shutdown -->
</section> <!-- end of commands supported by both servers -->
</chapter>
......@@ -3649,17 +3649,30 @@ src/lib/dhcpsrv/cfg_host_operations.cc -->
<para>
Communication over control channel is conducted using JSON structures.
See the Control Channel section in the Kea Developer's Guide for more details.
See the Control Channel section in the Kea Developer's Guide for more
details.
</para>
<para>The DHCPv4 server supports the following operational commands:
<itemizedlist>
<listitem>leases-reclaim</listitem>
<listitem>list-commands</listitem>
<listitem>set-config</listitem>
<listitem>shutdown</listitem>
</itemizedlist>
as described in <xref linkend="commands-common"/>. In addition,
it supports the following statistics related commands:
<itemizedlist>
<listitem>statistic-get</listitem>
<listitem>statistic-reset</listitem>
<listitem>statistic-remove</listitem>
<listitem>statistic-get-all</listitem>
<listitem>statistic-reset-all</listitem>
<listitem>statistic-remove-all</listitem>
</itemizedlist>
as described here <xref linkend="command-stats"/>.
</para>
<para>The DHCPv4 server supports <command>statistic-get</command>,
<command>statistic-reset</command>, <command>statistic-remove</command>,
<command>statistic-get-all</command>, <command>statistic-reset-all</command>
and <command>statistic-remove-all</command>, specified in
<xref linkend="command-stats"/>. It also supports
<command>list-commands</command> and <command>shutdown</command>,
specified in <xref linkend="command-list-commands" /> and
<xref linkend="command-shutdown" />, respectively.</para>
</section>
<section id="dhcp4-std">
......
......@@ -4062,16 +4062,27 @@ If not specified, the default value is:
See the Control Channel section in the Kea Developer's Guide for more details.
</para>
<para>The DHCPv6 server supports <command>statistic-get</command>,
<command>statistic-reset</command>, <command>statistic-remove</command>,
<command>statistic-get-all</command>, <command>statistic-reset-all</command>
and <command>statistic-remove-all</command>, specified in
<xref linkend="command-stats"/>. It also supports
<command>list-commands</command> and <command>shutdown</command>,
specified in <xref linkend="command-list-commands" /> and
<xref linkend="command-shutdown" />, respectively.</para>
</section>
<para>The DHCPv6 server supports the following operational commands:
<itemizedlist>
<listitem>leases-reclaim</listitem>
<listitem>list-commands</listitem>
<listitem>set-config</listitem>
<listitem>shutdown</listitem>
</itemizedlist>
as described in <xref linkend="commands-common"/>. In addition,
it supports the following statistics related commands:
<itemizedlist>
<listitem>statistic-get</listitem>
<listitem>statistic-reset</listitem>
<listitem>statistic-remove</listitem>
<listitem>statistic-get-all</listitem>
<listitem>statistic-reset-all</listitem>
<listitem>statistic-remove-all</listitem>
</itemizedlist>
as described here <xref linkend="command-stats"/>.
</para>
</section>
<section>
<title>User context in IPv6 pools</title>
......
// 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
......@@ -62,7 +62,69 @@ ControlledDhcpv4Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
ConstElementPtr
ControlledDhcpv4Srv::commandConfigReloadHandler(const string&,
ConstElementPtr args) {
return (processConfig(args));
// Use set-config as it handles logging and server config
return (commandSetConfigHandler("set-config", args));
}
ConstElementPtr
ControlledDhcpv4Srv::commandSetConfigHandler(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();
// Logging is a sibling element and must be parsed explicitly.
// The call to configureLogger parses the given Logging element if
// not null, into the staging config. Note this does not alter the
// current loggers, they remain in effect until we apply the
// logging config below. If no logging is supplied logging will
// revert to default logging.
Daemon::configureLogger(args->get("Logging"),
CfgMgr::instance().getStagingCfg());
// Now we configure the server proper.
ConstElementPtr result = processConfig(dhcp4);
// If the configuration parsed successfully, apply the new logger
// configuration and the commit the new configuration. We apply
// the logging first in case there's a configuration failure.
int rcode = 0;
isc::config::parseAnswer(rcode, result);
if (rcode == 0) {
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
// Use new configuration.
CfgMgr::instance().commit();
}
return (result);
}
ConstElementPtr
......@@ -116,6 +178,9 @@ ControlledDhcpv4Srv::processCommand(const string& command,
} else if (command == "config-reload") {
return (srv->commandConfigReloadHandler(command, args));
} else if (command == "set-config") {
return (srv->commandSetConfigHandler(command, args));
} else if (command == "leases-reclaim") {
return (srv->commandLeasesReclaimHandler(command, args));
}
......@@ -235,8 +300,6 @@ ControlledDhcpv4Srv::processConfig(isc::data::ConstElementPtr config) {
}
}
return (answer);
}
......@@ -257,6 +320,9 @@ ControlledDhcpv4Srv::ControlledDhcpv4Srv(uint16_t port /*= DHCP4_SERVER_PORT*/)
CommandMgr::instance().registerCommand("libreload",
boost::bind(&ControlledDhcpv4Srv::commandLibReloadHandler, this, _1, _2));
CommandMgr::instance().registerCommand("set-config",
boost::bind(&ControlledDhcpv4Srv::commandSetConfigHandler, this, _1, _2));
CommandMgr::instance().registerCommand("leases-reclaim",
boost::bind(&ControlledDhcpv4Srv::commandLeasesReclaimHandler, this, _1, _2));
......@@ -300,6 +366,7 @@ ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
// Deregister any registered commands
CommandMgr::instance().deregisterCommand("shutdown");
CommandMgr::instance().deregisterCommand("libreload");
CommandMgr::instance().deregisterCommand("set-config");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("statistic-get");
CommandMgr::instance().deregisterCommand("statistic-reset");
......
// Copyright (C) 2012-2015 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-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
......@@ -143,6 +143,19 @@ private:
commandConfigReloadHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief handler for processing 'set-config' command
///
/// This handler processes set-config command, which processes
/// configuration specified in args parameter.
/// @param command (parameter ignored)
/// @param args configuration to be processed. 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
commandSetConfigHandler(const std::string& command,
isc::data::ConstElementPtr args);
/// @brief Handler for processing 'leases-reclaim' command
///
......
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-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
......@@ -497,6 +497,47 @@ void setGlobalParameters4() {
}
}
/// @brief Initialize the command channel based on the staging configuration
///
/// Only close the current channel, if the new channel configuration is
/// different. This avoids disconnecting a client and hence not sending them
/// a command result, unless they specifically alter the channel configuration.
/// In that case the user simply has to accept they'll be disconnected.
///
void configureCommandChannel() {
// Get new socket configuration.
ConstElementPtr sock_cfg =
CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
// Get current socket configuration.
ConstElementPtr current_sock_cfg =
CfgMgr::instance().getCurrentCfg()->getControlSocketInfo();
// Determine if the socket configuration has changed. It has if
// both old and new configuration is specified but respective
// data elements are't equal.
bool sock_changed = (sock_cfg && current_sock_cfg &&
!sock_cfg->equals(*current_sock_cfg));
// If the previous or new socket configuration doesn't exist or
// the new configuration differs from the old configuration we
// close the exisitng socket and open a new socket as appropriate.
// Note that closing an existing socket means the clien will not
// receive the configuration result.
if (!sock_cfg || !current_sock_cfg || sock_changed) {
// Close the existing socket (if any).
isc::config::CommandMgr::instance().closeCommandSocket();
if (sock_cfg) {
// This will create a control socket and install the external
// socket in IfaceMgr. That socket will be monitored when
// Dhcp4Srv::receivePacket() calls IfaceMgr::receive4() and
// callback in CommandMgr will be called, if necessary.
isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
}
}
}
isc::data::ConstElementPtr
configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
......@@ -646,25 +687,8 @@ configureDhcp4Server(Dhcpv4Srv&, isc::data::ConstElementPtr config_set) {
subnet_parser->build(subnet_config->second);
}
// Get command socket configuration from the config file.
// This code expects the following structure:
// {
// "socket-type": "unix",
// "socket-name": "/tmp/kea4.sock"
// }
ConstElementPtr sock_cfg =
CfgMgr::instance().getStagingCfg()->getControlSocketInfo();
// Close existing socket (if any).
isc::config::CommandMgr::instance().closeCommandSocket();
if (sock_cfg) {
// This will create a control socket and will install external socket
// in IfaceMgr. That socket will be monitored when Dhcp4Srv::receivePacket()
// calls IfaceMgr::receive4() and callback in CommandMgr will be called,
// if necessary. If there were previously open command socket, it will
// be closed.
isc::config::CommandMgr::instance().openCommandSocket(sock_cfg);
}
// Setup the command channel.
configureCommandChannel();
// the leases database parser is the last to be run.
std::map<std::string, ConstElementPtr>::const_iterator leases_config =
......
// Copyright (C) 2014-2015 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
......@@ -34,11 +34,6 @@ void configure(const std::string& file_name) {
// This is a configuration backend implementation that reads the
// configuration from a JSON file.
// We are starting the configuration process so we should remove any
// staging configuration that has been created during previous
// configuration attempts.
CfgMgr::instance().rollback();
isc::data::ConstElementPtr json;
isc::data::ConstElementPtr dhcp4;
isc::data::ConstElementPtr logger;
......@@ -70,26 +65,14 @@ void configure(const std::string& file_name) {
" Did you forget to add { } around your configuration?");
}
// If there's no logging element, we'll just pass NULL pointer,
// which will be handled by configureLogger().
Daemon::configureLogger(json->get("Logging"),
CfgMgr::instance().getStagingCfg());
// Get Dhcp4 component from the config
dhcp4 = json->get("Dhcp4");
if (!dhcp4) {
isc_throw(isc::BadValue, "no mandatory 'Dhcp4' entry in"
" the configuration");
}
// Use parsed JSON structures to configure the server
result = ControlledDhcpv4Srv::processCommand("config-reload", dhcp4);
result = ControlledDhcpv4Srv::processCommand("set-config", json);
if (!result) {
// Undetermined status of the configuration. This should never
// happen, but as the configureDhcp4Server returns a pointer, it is
// theoretically possible that it will return NULL.
isc_throw(isc::BadValue, "undefined result of "
"processCommand(\"config-reload\", dhcp4)");
"processCommand(\"set-config\", json)");
}
// Now check is the returned result is successful (rcode=0) or not
......@@ -102,17 +85,6 @@ void configure(const std::string& file_name) {
"no details available";
isc_throw(isc::BadValue, reason);
}
// If configuration was parsed successfully, apply the new logger
// configuration to log4cplus. It is done before commit in case
// something goes wrong.
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
// Use new configuration.
/// @todo: This commit should be moved to
/// CtrlDhcp4Srv::commandConfigReloadHandler.
CfgMgr::instance().commit();
} catch (const std::exception& ex) {
// If configuration failed at any stage, we drop the staging
// configuration and continue to use the previous one.
......@@ -167,7 +139,7 @@ ControlledDhcpv4Srv::init(const std::string& file_name) {
// We don't need to call openActiveSockets() or startD2() as these
// methods are called in processConfig() which is called by
// processCommand("reload-config", ...)
// processCommand("set-config", ...)
// Set signal handlers. When the SIGHUP is received by the process
// the server reconfiguration will be triggered. When SIGTERM or
......
// Copyright (C) 2012-2016 Internet Systems Consortium, Inc. ("ISC")
// Copyright (C) 2012-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
......@@ -15,7 +15,9 @@
#include <dhcpsrv/lease.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <hooks/hooks_manager.h>
#include <log/logger_support.h>
#include <stats/stats_mgr.h>
#include <testutils/io_utils.h>
#include <testutils/unix_control_client.h>
#include "marker_file.h"
......@@ -121,6 +123,13 @@ public:
ConstElementPtr config;
ASSERT_NO_THROW(config = parseDHCP4(config_txt));
ConstElementPtr answer = server_->processConfig(config);
// Commit the configuration so any subsequent reconfigurations
// will only close the command channel if its configuration has
// changed.
CfgMgr::instance().commit();
ASSERT_TRUE(answer);
int status = 0;
......@@ -472,4 +481,147 @@ TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
response);
}
// Check that the "set-config" command will replace current configuration
TEST_F(CtrlChannelDhcpv4SrvTest, set_config) {
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 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 << set_config_txt << ","
<< args_txt
<< dhcp4_cfg_txt
<< bad_subnet
<< subnet_footer
<< control_socket_header
<< socket_path_
<< control_socket_footer
<< "}\n" // close dhcp4
"}}";
// Send the set-config command
sendUnixCommand(os.str(), response);
// Should fail with a syntax error
EXPECT_EQ("{ \"result\": 1, "
"\"text\": \"unsupported parameter: BOGUS (<string>:20:26)\" }",
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.
// It should succeed, client should still receive the response
os.str("");
os << set_config_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 set-config command
sendUnixCommand(os.str(), response);
/* Verify the control channel socket no longer exists */
EXPECT_FALSE(fileExists(socket_path_));
// With no command channel, should still receive the response.
EXPECT_EQ("{ \"result\": 0, \"text\": \"Configuration successful.\" }",
response);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets4()->getAll();
EXPECT_EQ(2, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
} // End of anonymous namespace
// 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
......@@ -24,6 +24,7 @@
#include <dhcpsrv/lease.h>
#include <dhcpsrv/lease_mgr.h>
#include <dhcpsrv/lease_mgr_factory.h>
#include <log/logger_support.h>
#include <stats/stats_mgr.h>
using namespace std;
......@@ -47,6 +48,9 @@ BaseServerTest::~BaseServerTest() {
// Revert to original data directory.
CfgMgr::instance().setDataDir(original_datadir_);
// Revert to unit test logging, in case the test reconfigured it.
isc::log::initLogger();
}