Commit fd66d064 authored by Tomek Mrugalski's avatar Tomek Mrugalski 🛰
Browse files

[3880] Unit-test for UNIX control socket written.

parent 7ebca7c0
......@@ -72,6 +72,70 @@ public:
static_cast<void>(unlink(LOAD_MARKER_FILE));
static_cast<void>(unlink(UNLOAD_MARKER_FILE));
}
/// @brief sends commands over specified UNIX socket
///
/// @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
///
/// @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 socket";
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);
}
};
TEST_F(CtrlDhcpv4SrvTest, commands) {
......@@ -193,7 +257,14 @@ TEST_F(CtrlDhcpv4SrvTest, commandsRegistration) {
// Checks if the server is able to parse control socket configuration and
// configures the command socket properly.
TEST_F(CtrlDhcpv4SrvTest, commandSocketBasic) {
/// @todo: This unit-test is disabled, because it causes weird issues, when
/// IfaceMgr::receive4() is called in a separate process. That's a side effect
/// of how we run the test. We should either investigate why IfaceMgr doesn't
/// work correctly after fork or develop a small tool that will send data
/// from stdin to specified UNIX socket, print out the responses on stdout
/// and develop shell tests for this.
TEST_F(CtrlDhcpv4SrvTest, DISABLED_commandSocketBasic) {
string socket_path = string(TEST_DATA_DIR) + "/kea4.sock";
......@@ -211,35 +282,60 @@ TEST_F(CtrlDhcpv4SrvTest, commandSocketBasic) {
" \"control-socket\": {"
" \"socket-type\": \"unix\","
" \"socket-name\": \"" + socket_path + "\""
" }"
" },"
" \"lease-database\": { \"type\": \"memfile\", \"persist\": false }"
"}";
boost::scoped_ptr<ControlledDhcpv4Srv> srv;
ASSERT_NO_THROW(
srv.reset(new ControlledDhcpv4Srv(0));
);
pid_t pid = fork();
if (pid > 0) {
cout << "Created child process: " << pid << endl;
ConstElementPtr config = Element::fromJSON(config_txt);
string command("{ \"command\": \"shutdown\" }");
string response;
ConstElementPtr answer = srv->processConfig(config);
ASSERT_TRUE(answer);
sleep(3);
int status = 0;
isc::config::parseAnswer(status, answer);
EXPECT_EQ(0, status);
EXPECT_TRUE(sendCommandUnixSocket(command, response, socket_path));
// Now check that the socket was indeed open.
EXPECT_TRUE(isc::config::CommandMgr::instance().getControlSocketFD() > 0);
}
kill(pid, SIGTERM);
int status;
waitpid(pid, &status, 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).
ASSERT_NE(0, response.length());
ConstElementPtr rsp;
EXPECT_NO_THROW(rsp = Element::fromJSON(response));
ASSERT_TRUE(rsp);
int status_code;
ConstElementPtr comment = parseAnswer(status_code, rsp);
EXPECT_EQ(0, status_code);
} else {
IfaceMgr::instance().deleteAllExternalSockets();
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.
ASSERT_TRUE(isc::config::CommandMgr::instance().getControlSocketFD() > -1);
cout << "Child process: pid=" << pid << ", running server." << endl;
srv->run();
exit(EXIT_SUCCESS);
}
}
} // End of anonymous namespace
......@@ -319,6 +319,11 @@ IfaceMgr::deleteExternalSocket(int socketfd) {
}
}
void
IfaceMgr::deleteAllExternalSockets() {
callbacks_.clear();
}
void
IfaceMgr::setPacketFilter(const PktFilterPtr& packet_filter) {
// Do not allow NULL pointer.
......
......@@ -973,9 +973,11 @@ public:
void addExternalSocket(int socketfd, SocketCallback callback);
/// @brief Deletes external socket
void deleteExternalSocket(int socketfd);
/// @brief Deletes all external sockets.
void deleteAllExternalSockets();
/// @brief Set packet filter object to handle sending and receiving DHCPv4
/// messages.
///
......
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