#include <hooks/hooks_manager.h>
#include <dhcp4/json_config_parser.h>
#include <dhcpsrv/cfgmgr.h>
+#include <config/command_mgr.h>
using namespace isc::data;
using namespace isc::hooks;
+using namespace isc::config;
using namespace std;
namespace isc {
ConstElementPtr answer = configureDhcp4Server(*srv, config);
-
// Check that configuration was successful. If not, do not reopen sockets
// and don't bother with DDNS stuff.
try {
"There is another Dhcpv4Srv instance already.");
}
server_ = this; // remember this instance for later use in handlers
+
+ // Register supported commands in CommandMgr
+ CommandMgr::instance().registerCommand("shutdown",
+ boost::bind(&ControlledDhcpv4Srv::commandShutdownHandler, this, _1, _2));
+
+ /// @todo: register config-reload (see CtrlDhcpv4Srv::commandConfigReloadHandler)
+ /// @todo: register libreload (see CtrlDhcpv4Srv::commandLibReloadHandler)
+ /// @todo: register statistic-get (see StatsMgr::get(name))
+ /// @todo: register statistic-reset (see StatsMgr::reset(name))
+ /// @todo: register statistic-get-all (see StatsMgr::getAll())
+ /// @todo: register statistic-reset-all (see StatsMgr::resetAll())
+ /// @todo: register statistic-remove (see StatsMgr::del(name))
+ /// @todo: register statistic-remove-all (see StatsMgr::removeAll())
}
void ControlledDhcpv4Srv::shutdown() {
ControlledDhcpv4Srv::~ControlledDhcpv4Srv() {
cleanup();
+ // Close the command socket (if it exists).
+ CommandMgr::instance().closeCommandSocket();
+
+ // Deregister any registered commands
+ CommandMgr::instance().deregisterCommand("shutdown");
+
server_ = NULL; // forget this instance. Noone should call any handlers at
// this stage.
}
#include <dhcpsrv/parsers/host_reservation_parser.h>
#include <dhcpsrv/parsers/host_reservations_list_parser.h>
#include <dhcpsrv/parsers/ifaces_config_parser.h>
+#include <config/command_mgr.h>
#include <util/encode/hex.h>
#include <util/strutil.h>
parser = new D2ClientConfigParser(config_id);
} else if (config_id.compare("match-client-id") == 0) {
parser = new BooleanParser(config_id, globalContext()->boolean_values_);
+ } else if (config_id.compare("control-socket") == 0) {
+ parser = new ControlSocketParser(config_id);
} else {
isc_throw(DhcpConfigError,
"unsupported global configuration parameter: "
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);
+ }
+
// the leases database parser is the last to be run.
std::map<std::string, ConstElementPtr>::const_iterator leases_config =
values_map.find("lease-database");
CfgMgr::instance().getStagingCfg()->applyLoggingCfg();
// Use new configuration.
+ /// @todo: This commit should be moved to
+ /// CtrlDhcp4Srv::commandConfigReloadHandler.
CfgMgr::instance().commit();
} catch (const std::exception& ex) {
signal_set_.reset(new isc::util::SignalSet(SIGINT, SIGHUP, SIGTERM));
// Set the pointer to the handler function.
signal_handler_ = signalHandler;
-
}
void ControlledDhcpv4Srv::cleanup() {
#include <config.h>
#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
#include <dhcp/dhcp4.h>
#include <dhcp4/ctrl_dhcp4_srv.h>
#include <hooks/hooks_manager.h>
EXPECT_TRUE(checkMarkerFile(LOAD_MARKER_FILE, "1212"));
}
+// This test checks which commands are registered by the DHCPv4 server.
+TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
+
+ ConstElementPtr list_cmds = createCommand("list-commands");
+ ConstElementPtr answer;
+
+ // By default the list should be empty (except the standard list-commands
+ // supported by the CommandMgr itself)
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+
+ // Created server should register several additional commands.
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(0));
+ );
+
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\", \"shutdown\" ]", answer->get("arguments")->str());
+
+ // Ok, and now delete the server. It should deregister its commands.
+ srv.reset();
+
+ // The list should be (almost) empty again.
+ EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
+ ASSERT_TRUE(answer);
+ ASSERT_TRUE(answer->get("arguments"));
+ EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
+}
+
+// Checks if the server is able to parse control socket configuration and
+// configures the command socket properly.
+TEST_F(CtrlDhcpv4SrvTest, commandSocketBasic) {
+
+ string socket_path = string(TEST_DATA_DIR) + "/kea4.sock";
+
+ // Just a simple config. The important part here is the socket
+ // location information.
+ std::string config_txt =
+ "{"
+ " \"interfaces-config\": {"
+ " \"interfaces\": [ \"*\" ]"
+ " },"
+ " \"rebind-timer\": 2000, "
+ " \"renew-timer\": 1000, "
+ " \"subnet4\": [ ],"
+ " \"valid-lifetime\": 4000,"
+ " \"control-socket\": {"
+ " \"socket-type\": \"unix\","
+ " \"socket-name\": \"" + socket_path + "\""
+ " }"
+ "}";
+
+ boost::scoped_ptr<ControlledDhcpv4Srv> srv;
+ ASSERT_NO_THROW(
+ srv.reset(new ControlledDhcpv4Srv(0));
+ );
+
+ ConstElementPtr config = Element::fromJSON(config_txt);
+
+ ConstElementPtr answer = srv->processConfig(config);
+ ASSERT_TRUE(answer);
+
+ int status = 0;
+ isc::config::parseAnswer(status, answer);
+ EXPECT_EQ(0, status);
+
+ // Now check that the socket was indeed open.
+ EXPECT_TRUE(isc::config::CommandMgr::instance().getControlSocketFD() > 0);
+}
+
+/// @todo: Implement system tests for the control socket.
+/// It is tricky in unit-tests, as it would require running two processes
+/// (one for the server itself and a second one for the test that sends
+/// command and receives an aswer).
+///
+/// Alternatively, we could use shell tests. It would be much simpler,
+/// but that requires using socat, a tool that is typically not installed.
+/// So we'd need a check in configure to check if it's available and
+/// fail configure process if missing (or disable the tests).
+
} // End of anonymous namespace
#include <asiolink/io_address.h>
#include <cc/command_interpreter.h>
+#include <config/command_mgr.h>
#include <dhcp4/tests/dhcp4_test_utils.h>
#include <dhcp/tests/pkt_captures.h>
#include <dhcp/dhcp4.h>
using namespace isc::data;
using namespace isc::asiolink;
using namespace isc::hooks;
+using namespace isc::config;
using namespace isc::dhcp::test;
using namespace isc::test;
boost::bind(&CommandMgr::listCommandsHandler, this, _1, _2));
}
-int CommandMgr::openCtrlSocket(const isc::data::ConstElementPtr& socket_info) {
+int CommandMgr::openCommandSocket(const isc::data::ConstElementPtr& socket_info) {
if (socket_info_) {
isc_throw(SocketError, "There is already a control socket open");
}
return (socket_);
}
-void CommandMgr::closeCtrlSocket() {
+void CommandMgr::closeCommandSocket() {
if (socket_info_) {
isc::dhcp::IfaceMgr::instance().deleteExternalSocket(socket_);
isc::dhcp::IfaceMgr::instance().addExternalSocket(fd2,
boost::bind(&isc::config::CommandMgr::commandReader, fd2));
+ // Remember this socket descriptor. It will be needed when we shut down the
+ // server.
+ instance().connections_.push_back(fd2);
+
LOG_INFO(command_logger, COMMAND_SOCKET_CONNECTION_OPENED).arg(fd2).arg(sockfd);
}
if (rval < 0) {
// Read failed
LOG_WARN(command_logger, COMMAND_SOCKET_READ_FAIL).arg(rval).arg(sockfd);
+
+ /// @todo: Should we close the connection, similar to what is already
+ /// being done for rval == 0?
return;
} else if (rval == 0) {
// Close the socket.
close(sockfd);
+
+ // Remove it from the active connections list.
+ instance().connections_.remove(sockfd);
+
return;
}
/// Currently supported types are:
/// - unix (required parameters: socket-type: unix, socket-name:/unix/path)
///
- /// @throw CommandSocketError if socket creation fails
+ /// This method will close previously open command socket (if exists).
+ ///
+ /// @throw CommandSocketError if socket creation fails.
+ /// @throw SocketError if command socket is already open.
///
/// @param socket_info describes control socket parameters
/// @return socket descriptor of the socket created
- int openCtrlSocket(const isc::data::ConstElementPtr& socket_info);
+ int openCommandSocket(const isc::data::ConstElementPtr& socket_info);
/// @brief Shuts down any open control sockets
- void closeCtrlSocket();
+ void closeCommandSocket();
/// @brief Registers specified command handler for a given command
///
/// handled at all times.
void deregisterAll();
+
+ /// @brief Returns control socket descriptor
+ ///
+ /// This method should be used only in tests.
+ int getControlSocketFD() const {
+ return (socket_);
+ }
+
private:
/// @brief Private constructor
/// @brief Parameters for control socket
isc::data::ConstElementPtr socket_info_;
+
+ /// @brief Socket descriptors for open connections
+ std::list<int> connections_;
};
}; // end of isc::config namespace
AM_CPPFLAGS = -I$(top_builddir)/src/lib -I$(top_srcdir)/src/lib
AM_CPPFLAGS += $(BOOST_INCLUDES)
+AM_CPPFLAGS += -DTEST_DATA_DIR=\"$(abs_top_srcdir)/src/lib/testutils/testdata\"
AM_CXXFLAGS = $(KEA_CXXFLAGS)
handler_called = false;
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCtrlSocket();
+ CommandMgr::instance().closeCommandSocket();
}
/// Default destructor
~CommandMgrTest() {
CommandMgr::instance().deregisterAll();
- CommandMgr::instance().closeCtrlSocket();
+ CommandMgr::instance().closeCommandSocket();
}
/// @brief A simple command handler that always returns an eror
#include <cc/data.h>
#include <config/command_mgr.h>
#include <config/command_socket_factory.h>
+#include <cstdio>
using namespace isc::config;
using namespace isc::data;
/// Default constructor
CommandSocketFactoryTest() {
- unlink(SOCKET_NAME);
+ // Remove any stale socket files
+ remove(SOCKET_NAME.c_str());
}
/// Default destructor
~CommandSocketFactoryTest() {
- unlink(SOCKET_NAME);
+
+ // Remove any stale socket files
+ remove(SOCKET_NAME.c_str());
}
- static const char* SOCKET_NAME;
+ static const std::string SOCKET_NAME;
};
-const char* CommandSocketFactoryTest::SOCKET_NAME = "test-socket";
+const std::string CommandSocketFactoryTest::SOCKET_NAME =
+ std::string(TEST_DATA_DIR) + "/test-socket";
TEST_F(CommandSocketFactoryTest, unixCreate) {
// Null pointer is obviously a bad idea.
// It should be possible to close the socket.
EXPECT_NO_THROW(CommandSocketFactory::close(fd, socket_info));
}
+
// Nothing to do.
}
+// ******************** ControlSocketParser *************************
+ControlSocketParser::ControlSocketParser(const std::string& param_name) {
+ if (param_name != "control-socket") {
+ isc_throw(BadValue, "Internal error. Control socket parser called "
+ " for wrong parameter:" << param_name);
+ }
+}
+
+void ControlSocketParser::build(isc::data::ConstElementPtr value) {
+ if (value->getType() != Element::map) {
+ isc_throw(BadValue, "Specified control-socket is expected to be a map"
+ ", i.e. a structure defined within { }");
+ }
+ CfgMgr::instance().getStagingCfg()->setControlSocketInfo(value);
+}
+
+/// @brief Does nothing.
+void ControlSocketParser::commit() {
+ // Nothing to do.
+}
+
// ******************** HooksLibrariesParser *************************
HooksLibrariesParser::HooksLibrariesParser(const std::string& param_name)
ParserContextPtr global_context_;
};
+/// @brief Parser for the control-socket structure
+///
+/// It does not parse anything, simply stores the element in
+/// the staging config.
+class ControlSocketParser : public DhcpConfigParser {
+public:
+
+ ControlSocketParser(const std::string& param_name);
+
+ /// @brief Stores contents of the control-socket map in the staging config.
+ ///
+ /// @param value pointer to the content of parsed values
+ virtual void build(isc::data::ConstElementPtr value);
+
+ /// @brief Does nothing.
+ virtual void commit();
+};
/// @brief Parser for hooks library list
///
#include <dhcpsrv/cfg_subnets6.h>
#include <dhcpsrv/cfg_mac_source.h>
#include <dhcpsrv/logging_info.h>
+#include <cc/data.h>
#include <boost/shared_ptr.hpp>
#include <vector>
#include <stdint.h>
return (cfg_mac_source_);
}
+ /// @brief Returns information about control socket
+ /// @return pointer to the Element that holds control-socket map
+ const isc::data::ConstElementPtr getControlSocketInfo() const {
+ return (control_socket_);
+ }
+
+ /// @brief Sets information about the control socket
+ /// @param control_socket Element that holds control-socket map
+ void setControlSocketInfo(const isc::data::ConstElementPtr& control_socket) {
+ control_socket_ = control_socket;
+ }
+
/// @brief Copies the currnet configuration to a new configuration.
///
/// This method copies the parameters stored in the configuration to
/// This object holds a set of RSOO-enabled options. See
/// RFC 6422 for the definition of the RSOO-enabled option.
CfgRSOOPtr cfg_rsoo_;
+
+ /// @brief Pointer to the control-socket information
+ isc::data::ConstElementPtr control_socket_;
};
/// @name Pointers to the @c SrvConfig object.
// Unparseable text that looks like IPv6 address, but has too many colons
EXPECT_THROW(parser->build(json_bogus2), DhcpConfigError);
}
+
+// There's no test for ControlSocketParser, as it is tested in the DHCPv4 code
+// (see CtrlDhcpv4SrvTest.commandSocketBasic in
+// src/bin/dhcp4/tests/ctrl_dhcp4_srv_unittest.cc).