Commit a9a3d0d5 authored by Thomas Markwalder's avatar Thomas Markwalder

[5046] kea-dhcp6 now implements set-config command

src/bin/dhcp6/ctrl_dhcp6_srv.h
src/bin/dhcp6/ctrl_dhcp6_srv.cc
    ControlledDhcpv6Srv::commandSetConfigHandler() - new method to process
    the set-config command.

    ControlledDhcpv6Srv::processCommand() - call new set-config handler

    ControlledDhcpv6Srv::processConfig() - added logic to apply logging
    and commit configuration after  successful reconfig

    ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port) - added registration
    of set-config command

    ControlledDhcpv6Srv::~ControlledDhcpv6Srv() - unregisters set-config command

src/bin/dhcp6/json_config_parser.cc
    configureCommandChannel() - extracted logic to reconfigure command channel
    to its own fucntion

src/bin/dhcp6/kea_controller.cc
    configure() - removed logic to apply logging and commit config, now done
    in ControlledDhcpv6Srv::processConfig()

src/bin/dhcp6/tests/ctrl_dhcp6_srv_unittest.cc
    TEST_F(CtrlChannelDhcpv6SrvTest, set_config) - new test to exercise the
    set-config command
parent 16be7fe3
......@@ -67,10 +67,46 @@ ControlledDhcpv6Srv::commandLibReloadHandler(const string&, ConstElementPtr) {
ConstElementPtr
ControlledDhcpv6Srv::commandConfigReloadHandler(const string&, ConstElementPtr args) {
return (processConfig(args));
}
ConstElementPtr
ControlledDhcpv6Srv::commandSetConfigHandler(const string&,
ConstElementPtr args) {
const int status_code = 1; // 1 indicates an error
ConstElementPtr dhcp6;
string message;
// Throw out the preivous staging config that may be present
CfgMgr::instance().rollback();
// Command arguments are expected to be:
// { "Dhcp6": { ... }, "Logging": { ... } }
// The Logging component is technically optional, but very recommended.
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);
}
// Now that we have extracted the configuration, reuse the reload command
// to configure the server.
return (ControlledDhcpv6Srv::processCommand("config-reload", dhcp6));
}
ConstElementPtr
ControlledDhcpv6Srv::commandLeasesReclaimHandler(const string&,
ConstElementPtr args) {
......@@ -122,6 +158,9 @@ ControlledDhcpv6Srv::processCommand(const std::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));
}
......@@ -260,6 +299,14 @@ ControlledDhcpv6Srv::processConfig(isc::data::ConstElementPtr config) {
}
}
// 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.
CfgMgr::instance().commit();
// Finally, we can commit runtime option definitions in libdhcp++. This is
// exception free.
LibDHCP::commitRuntimeOptionDefs();
......@@ -280,6 +327,10 @@ ControlledDhcpv6Srv::ControlledDhcpv6Srv(uint16_t port)
boost::bind(&ControlledDhcpv6Srv::commandShutdownHandler, this, _1, _2));
/// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
CommandMgr::instance().registerCommand("set-config",
boost::bind(&ControlledDhcpv6Srv::commandSetConfigHandler, this, _1, _2));
/// @todo: register libreload (see CtrlDhcpv4Srv::commandLibReloadHandler)
CommandMgr::instance().registerCommand("leases-reclaim",
......@@ -324,6 +375,7 @@ ControlledDhcpv6Srv::~ControlledDhcpv6Srv() {
// Deregister any registered commands
CommandMgr::instance().deregisterCommand("shutdown");
CommandMgr::instance().deregisterCommand("set-config");
CommandMgr::instance().deregisterCommand("leases-reclaim");
CommandMgr::instance().deregisterCommand("statistic-get");
CommandMgr::instance().deregisterCommand("statistic-reset");
......
......@@ -143,6 +143,20 @@ 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 Dhcp6 map that contains DHCPv6 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
///
/// This handler processes leases-reclaim command, which triggers
......
......@@ -770,6 +770,47 @@ void setGlobalParameters6() {
}
}
/// @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
configureDhcp6Server(Dhcpv6Srv&, isc::data::ConstElementPtr config_set) {
if (!config_set) {
......@@ -904,25 +945,8 @@ configureDhcp6Server(Dhcpv6Srv&, 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/kea6.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 lease database parser is the last to be run.
std::map<std::string, ConstElementPtr>::const_iterator leases_config =
......
......@@ -107,15 +107,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.
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.
......
......@@ -146,6 +146,7 @@ public:
ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
ConstElementPtr config = Element::fromJSON(config_txt);
ConstElementPtr answer = server_->processConfig(config);
ASSERT_TRUE(answer);
......@@ -335,13 +336,143 @@ TEST_F(CtrlDhcpv6SrvTest, configReload) {
// Check that the config was indeed applied.
const Subnet6Collection* subnets =
CfgMgr::instance().getStagingCfg()->getCfgSubnets6()->getAll();
CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(3, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
// Check that the "set-config" command will replace current configuration
TEST_F(CtrlChannelDhcpv6SrvTest, 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 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"
" \"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\": \"*\", \n"
" \"severity\": \"INFO\", \n"
" \"debuglevel\": 0, \n"
" \"output_options\": [{ \n"
" \"output\": \"stdout\" \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 << set_config_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< bad_subnet
<< subnet_footer
<< control_socket_header
<< socket_path_
<< control_socket_footer
<< "}\n" // close dhcp6
<< ","
<< logger_txt << "}}";
// Send the set-config command
sendUnixCommand(os.str(), response);
// Should fail with a syntax error
EXPECT_EQ("{ \"result\": 1, "
"\"text\": \"unsupported parameter: BOGUS (<string>:12:33)\" }",
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.
// It should succeed but client will not receive a the response
os.str("");
os << set_config_txt << ","
<< args_txt
<< dhcp6_cfg_txt
<< subnet1
<< ",\n"
<< subnet2
<< subnet_footer
<< "}\n" // close dhcp6
<< ","
<< logger_txt << "}}";
// Send the set-config command
sendUnixCommand(os.str(), response);
// With no command channel, no response
EXPECT_EQ("", response);
// Check that the config was not lost
subnets = CfgMgr::instance().getCurrentCfg()->getCfgSubnets6()->getAll();
EXPECT_EQ(2, subnets->size());
// Clean up after the test.
CfgMgr::instance().clear();
}
typedef std::map<std::string, isc::data::ConstElementPtr> ElementMap;
// This test checks which commands are registered by the DHCPv4 server.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment