|
@@ -18,7 +18,9 @@
|
|
|
#include <config/command_mgr.h>
|
|
|
#include <dhcp/dhcp4.h>
|
|
|
#include <dhcp4/ctrl_dhcp4_srv.h>
|
|
|
+#include <dhcpsrv/cfgmgr.h>
|
|
|
#include <hooks/hooks_manager.h>
|
|
|
+#include <testutils/unix_control_client.h>
|
|
|
|
|
|
#include "marker_file.h"
|
|
|
#include "test_libraries.h"
|
|
@@ -48,18 +50,84 @@ class NakedControlledDhcpv4Srv: public ControlledDhcpv4Srv {
|
|
|
// "Naked" DHCPv4 server, exposes internal fields
|
|
|
public:
|
|
|
NakedControlledDhcpv4Srv():ControlledDhcpv4Srv(DHCP4_SERVER_PORT + 10000) { }
|
|
|
+
|
|
|
+ /// Expose internal methods for the sake of testing
|
|
|
+ using Dhcpv4Srv::receivePacket;
|
|
|
};
|
|
|
|
|
|
-class CtrlDhcpv4SrvTest : public ::testing::Test {
|
|
|
+/// @brief Fixture class intended for testin control channel in the DHCPv4Srv
|
|
|
+class CtrlChannelDhcpv4SrvTest : public ::testing::Test {
|
|
|
public:
|
|
|
- CtrlDhcpv4SrvTest() {
|
|
|
+
|
|
|
+ /// @brief Path to the UNIX socket being used to communicate with the server
|
|
|
+ std::string socket_path_;
|
|
|
+
|
|
|
+ /// @brief Pointer to the tested server object
|
|
|
+ boost::shared_ptr<NakedControlledDhcpv4Srv> server_;
|
|
|
+
|
|
|
+ /// @brief Default constructor
|
|
|
+ ///
|
|
|
+ /// Sets socket path to its default value.
|
|
|
+ CtrlChannelDhcpv4SrvTest() {
|
|
|
+ const char* env = getenv("KEA_SOCKET_TEST_DIR");
|
|
|
+ if (env) {
|
|
|
+ socket_path_ = string(env) + "/kea4.sock";
|
|
|
+ } else {
|
|
|
+ socket_path_ = string(TEST_DATA_BUILDDIR) + "/kea4.sock";
|
|
|
+ }
|
|
|
reset();
|
|
|
}
|
|
|
|
|
|
- ~CtrlDhcpv4SrvTest() {
|
|
|
+ /// @brief Destructor
|
|
|
+ ~CtrlChannelDhcpv4SrvTest() {
|
|
|
+ server_.reset();
|
|
|
reset();
|
|
|
};
|
|
|
|
|
|
+ void createUnixChannelServer() {
|
|
|
+ ::remove(socket_path_.c_str());
|
|
|
+
|
|
|
+ // Just a simple config. The important part here is the socket
|
|
|
+ // location information.
|
|
|
+ std::string header =
|
|
|
+ "{"
|
|
|
+ " \"interfaces-config\": {"
|
|
|
+ " \"interfaces\": [ \"*\" ]"
|
|
|
+ " },"
|
|
|
+ " \"rebind-timer\": 2000, "
|
|
|
+ " \"renew-timer\": 1000, "
|
|
|
+ " \"subnet4\": [ ],"
|
|
|
+ " \"valid-lifetime\": 4000,"
|
|
|
+ " \"control-socket\": {"
|
|
|
+ " \"socket-type\": \"unix\","
|
|
|
+ " \"socket-name\": \"";
|
|
|
+
|
|
|
+ std::string footer =
|
|
|
+ "\" },"
|
|
|
+ " \"lease-database\": {"
|
|
|
+ " \"type\": \"memfile\", \"persist\": false }"
|
|
|
+ "}";
|
|
|
+
|
|
|
+ // Fill in the socket-name value with socket_path_ to
|
|
|
+ // make the actual configuration text.
|
|
|
+ std::string config_txt = header + socket_path_ + footer;
|
|
|
+
|
|
|
+ ASSERT_NO_THROW(server_.reset(new NakedControlledDhcpv4Srv()));
|
|
|
+
|
|
|
+ ConstElementPtr config = Element::fromJSON(config_txt);
|
|
|
+ ConstElementPtr answer = server_->processConfig(config);
|
|
|
+ ASSERT_TRUE(answer);
|
|
|
+
|
|
|
+ int status = 0;
|
|
|
+ ConstElementPtr txt = isc::config::parseAnswer(status, answer);
|
|
|
+ // This should succeed. If not, print the error message.
|
|
|
+ ASSERT_EQ(0, status) << txt->str();
|
|
|
+
|
|
|
+ // Now check that the socket was indeed open.
|
|
|
+ ASSERT_GT(isc::config::CommandMgr::instance().getControlSocketFD(), -1);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
/// @brief Reset hooks data
|
|
|
///
|
|
|
/// Resets the data for the hooks-related portion of the test by ensuring
|
|
@@ -71,75 +139,53 @@ public:
|
|
|
// Get rid of any marker files.
|
|
|
static_cast<void>(remove(LOAD_MARKER_FILE));
|
|
|
static_cast<void>(remove(UNLOAD_MARKER_FILE));
|
|
|
+
|
|
|
+ IfaceMgr::instance().deleteAllExternalSockets();
|
|
|
+ CfgMgr::instance().clear();
|
|
|
+
|
|
|
+ // Remove unix socket file
|
|
|
+ ::remove(socket_path_.c_str());
|
|
|
}
|
|
|
|
|
|
- /// @brief sends commands over specified UNIX socket
|
|
|
+ /// @brief Conducts a command/response exchange via UnixCommandSocket
|
|
|
///
|
|
|
- /// @param command command to be sent (should be valid JSON)
|
|
|
- /// @param response response received (expected to be a valid JSON)
|
|
|
- /// @param socket_path UNIX socket path
|
|
|
+ /// This method connects to the given server over the given socket path.
|
|
|
+ /// If successful, it then sends the given command and retrieves the
|
|
|
+ /// server's response. Note that it calls the server's receivePacket()
|
|
|
+ /// method where needed to cause the server to process IO events on
|
|
|
+ /// control channel the control channel sockets.
|
|
|
///
|
|
|
- /// @return true if send/response exchange was successful, false otherwise
|
|
|
- bool sendCommandUnixSocket(const std::string& command,
|
|
|
- std::string& response,
|
|
|
- const std::string& socket_path) {
|
|
|
-
|
|
|
- // Create UNIX socket
|
|
|
- int socket_fd = socket(AF_UNIX, SOCK_STREAM, 0);
|
|
|
- if (socket_fd < 0) {
|
|
|
- ADD_FAILURE() << "Failed to open unix stream socket.";
|
|
|
- return (false);
|
|
|
- }
|
|
|
-
|
|
|
- // Prepare socket address
|
|
|
- struct sockaddr_un srv_addr;
|
|
|
- memset(&srv_addr, 0, sizeof(struct sockaddr_un));
|
|
|
- srv_addr.sun_family = AF_UNIX;
|
|
|
- strncpy(srv_addr.sun_path, socket_path.c_str(), sizeof(srv_addr.sun_path));
|
|
|
- socklen_t len = sizeof(srv_addr);
|
|
|
-
|
|
|
- // Connect to the specified UNIX socket
|
|
|
- int status = connect(socket_fd, (struct sockaddr*)&srv_addr, len);
|
|
|
- if (status == -1) {
|
|
|
- ADD_FAILURE() << "Failed to connect unix socket: fd=" << socket_fd
|
|
|
- << ", path=" << socket_path;
|
|
|
- close(socket_fd);
|
|
|
- return (false);
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- // Send command
|
|
|
- cout << "Sending command: " << command << endl;
|
|
|
- int bytes_sent = send(socket_fd, command.c_str(), command.length(), 0);
|
|
|
- if (bytes_sent < command.length()) {
|
|
|
- ADD_FAILURE() << "Failed to send " << command.length()
|
|
|
- << " bytes, send() returned " << bytes_sent;
|
|
|
- close(socket_fd);
|
|
|
- return (false);
|
|
|
- }
|
|
|
-
|
|
|
- // Receive response
|
|
|
- /// @todo: this may block if server fails to respond. Some sort of
|
|
|
- /// of a timer is needed.
|
|
|
- char buf[65536];
|
|
|
- memset(buf, 0, sizeof(buf));
|
|
|
- int bytes_rcvd = recv(socket_fd, buf, sizeof(buf), 0);
|
|
|
- if (bytes_rcvd < 0) {
|
|
|
- ADD_FAILURE() << "Failed to receive a response. recv() returned "
|
|
|
- << bytes_rcvd;
|
|
|
- close(socket_fd);
|
|
|
- return (false);
|
|
|
- }
|
|
|
-
|
|
|
- // Convert the response to a string, close the socket and return
|
|
|
- response = string(buf, bytes_rcvd);
|
|
|
- cout << "Received response: " << response << endl;
|
|
|
- close(socket_fd);
|
|
|
- return (true);
|
|
|
+ /// @param command the command text to execute in JSON form
|
|
|
+ /// @param response variable into which the received response should be
|
|
|
+ /// placed.
|
|
|
+ void sendUnixCommand(const std::string& command, std::string& response) {
|
|
|
+ response = "";
|
|
|
+ boost::scoped_ptr<UnixControlClient> client;
|
|
|
+ client.reset(new UnixControlClient());
|
|
|
+ ASSERT_TRUE(client);
|
|
|
+
|
|
|
+ // Connect and then call server's receivePacket() so it can
|
|
|
+ // detect the control socket connect and call the accept handler
|
|
|
+ ASSERT_TRUE(client->connectToServer(socket_path_));
|
|
|
+ ASSERT_NO_THROW(server_->receivePacket(0));
|
|
|
+
|
|
|
+ // Send the command and then call server's receivePacket() so it can
|
|
|
+ // detect the inbound data and call the read handler
|
|
|
+ ASSERT_TRUE(client->sendCommand(command));
|
|
|
+ ASSERT_NO_THROW(server_->receivePacket(0));
|
|
|
+
|
|
|
+ // Read the response generated by the server. Note that getResponse
|
|
|
+ // only fails if there an IO error or no response data was present.
|
|
|
+ // It is not based on the response content.
|
|
|
+ ASSERT_TRUE(client->getResponse(response));
|
|
|
+
|
|
|
+ // Now disconnect and process the close event
|
|
|
+ client->disconnectFromServer();
|
|
|
+ ASSERT_NO_THROW(server_->receivePacket(0));
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-TEST_F(CtrlDhcpv4SrvTest, commands) {
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, commands) {
|
|
|
|
|
|
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
|
|
|
ASSERT_NO_THROW(
|
|
@@ -172,7 +218,7 @@ TEST_F(CtrlDhcpv4SrvTest, commands) {
|
|
|
|
|
|
// Check that the "libreload" command will reload libraries
|
|
|
|
|
|
-TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, libreload) {
|
|
|
|
|
|
// Sending commands for processing now requires a server that can process
|
|
|
// them.
|
|
@@ -223,7 +269,7 @@ TEST_F(CtrlDhcpv4SrvTest, libreload) {
|
|
|
}
|
|
|
|
|
|
// This test checks which commands are registered by the DHCPv4 server.
|
|
|
-TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, commandsRegistration) {
|
|
|
|
|
|
ConstElementPtr list_cmds = createCommand("list-commands");
|
|
|
ConstElementPtr answer;
|
|
@@ -244,11 +290,15 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|
|
EXPECT_NO_THROW(answer = CommandMgr::instance().processCommand(list_cmds));
|
|
|
ASSERT_TRUE(answer);
|
|
|
ASSERT_TRUE(answer->get("arguments"));
|
|
|
- EXPECT_EQ("[ \"list-commands\", \"shutdown\", "
|
|
|
- "\"statistic-get\", \"statistic-get-all\", "
|
|
|
- "\"statistic-remove\", \"statistic-remove-all\", "
|
|
|
- "\"statistic-reset\", \"statistic-reset-all\" ]",
|
|
|
- answer->get("arguments")->str());
|
|
|
+ std::string command_list = answer->get("arguments")->str();
|
|
|
+
|
|
|
+ EXPECT_TRUE(command_list.find("\"list-commands\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-get\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-get-all\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-remove\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-remove-all\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-reset\"") != string::npos);
|
|
|
+ EXPECT_TRUE(command_list.find("\"statistic-reset-all\"") != string::npos);
|
|
|
|
|
|
// Ok, and now delete the server. It should deregister its commands.
|
|
|
srv.reset();
|
|
@@ -260,4 +310,77 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
|
|
|
EXPECT_EQ("[ \"list-commands\" ]", answer->get("arguments")->str());
|
|
|
}
|
|
|
|
|
|
+// Tests that the server properly responds to invalid commands sent
|
|
|
+// via ControlChannel
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelNegative) {
|
|
|
+ createUnixChannelServer();
|
|
|
+ std::string response;
|
|
|
+
|
|
|
+ sendUnixCommand("{ \"command\": \"bogus\" }", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 1,"
|
|
|
+ " \"text\": \"'bogus' command not supported.\" }", response);
|
|
|
+
|
|
|
+ sendUnixCommand("utter nonsense", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 1, "
|
|
|
+ "\"text\": \"error: unexpected character u in <string>:1:2\" }",
|
|
|
+ response);
|
|
|
+}
|
|
|
+
|
|
|
+// Tests that the server properly responds to shtudown command sent
|
|
|
+// via ControlChannel
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelShutdown) {
|
|
|
+ createUnixChannelServer();
|
|
|
+ std::string response;
|
|
|
+
|
|
|
+ sendUnixCommand("{ \"command\": \"shutdown\" }", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"Shutting down.\" }",response);
|
|
|
+}
|
|
|
+
|
|
|
+// Tests that the server properly responds to statistics commands. Note this
|
|
|
+// is really only intended to verify that the appropriate Statistics handler
|
|
|
+// is called based on the command. It is not intended to be an exhaustive
|
|
|
+// test of Dhcpv6 statistics.
|
|
|
+TEST_F(CtrlChannelDhcpv4SrvTest, controlChannelStats) {
|
|
|
+ createUnixChannelServer();
|
|
|
+ std::string response;
|
|
|
+
|
|
|
+ // Check statistic-get
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-get\", "
|
|
|
+ " \"arguments\": {"
|
|
|
+ " \"name\":\"bogus\" }}", response);
|
|
|
+ EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
|
|
|
+
|
|
|
+ // Check statistic-get-all
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-get-all\", "
|
|
|
+ " \"arguments\": {}}", response);
|
|
|
+ EXPECT_EQ("{ \"arguments\": { }, \"result\": 0 }", response);
|
|
|
+
|
|
|
+ // Check statistic-reset
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-reset\", "
|
|
|
+ " \"arguments\": {"
|
|
|
+ " \"name\":\"bogus\" }}", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
|
|
|
+ response);
|
|
|
+
|
|
|
+ // Check statistic-reset-all
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-reset-all\", "
|
|
|
+ " \"arguments\": {}}", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 0, \"text\": "
|
|
|
+ "\"All statistics reset to neutral values.\" }", response);
|
|
|
+
|
|
|
+ // Check statistic-remove
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-remove\", "
|
|
|
+ " \"arguments\": {"
|
|
|
+ " \"name\":\"bogus\" }}", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 1, \"text\": \"No 'bogus' statistic found\" }",
|
|
|
+ response);
|
|
|
+
|
|
|
+ // Check statistic-remove-all
|
|
|
+ sendUnixCommand("{ \"command\" : \"statistic-remove-all\", "
|
|
|
+ " \"arguments\": {}}", response);
|
|
|
+ EXPECT_EQ("{ \"result\": 0, \"text\": \"All statistics removed.\" }",
|
|
|
+ response);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
} // End of anonymous namespace
|