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) {
} 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));
}
}
}
+ // 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();
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",
// 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");
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
}
}
+/// @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) {
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 =
"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.
ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv6Srv()));
ConstElementPtr config = Element::fromJSON(config_txt);
+
ConstElementPtr answer = server_->processConfig(config);
ASSERT_TRUE(answer);
// 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.